mirror of
https://github.com/etesync/android
synced 2024-11-15 20:38:58 +00:00
Refactoring
* move AccountSettings up to package context * HttpClient: take authentication from AccountSettings in the constructor * App: provide global instance of MemorizingTrustManager * App: provide global Java logger, optionally with verbose and external file logging * LoginCredentials: moved from inner-class into setup package
This commit is contained in:
parent
50f7006e59
commit
552f6b6936
@ -26,7 +26,7 @@ public class HttpClientTest extends InstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
public void setUp() throws IOException {
|
||||
httpClient = HttpClient.create(getInstrumentation().getTargetContext().getApplicationContext());
|
||||
httpClient = HttpClient.create(getInstrumentation().getTargetContext().getApplicationContext(), null);
|
||||
|
||||
server = new MockWebServer();
|
||||
server.start();
|
||||
|
@ -3,12 +3,12 @@ package at.bitfire.davdroid.resource;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder;
|
||||
import at.bitfire.davdroid.ui.setup.LoginCredentialsFragment;
|
||||
import at.bitfire.davdroid.ui.setup.LoginCredentials;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
@ -30,7 +30,7 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||
|
||||
public void testGetCurrentUserPrincipal() throws IOException, HttpException, DavException {
|
||||
HttpUrl url = server.url("/dav");
|
||||
LoginCredentialsFragment.LoginCredentials credentials = new LoginCredentialsFragment.LoginCredentials(url.uri(), "admin", "12345", true);
|
||||
LoginCredentials credentials = new LoginCredentials(url.uri(), "admin", "12345", true);
|
||||
DavResourceFinder finder = new DavResourceFinder(getInstrumentation().getTargetContext().getApplicationContext(), credentials);
|
||||
|
||||
// positive test case
|
||||
@ -51,8 +51,8 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||
server.enqueue(new MockResponse() // OPTIONS response
|
||||
.setResponseCode(200)
|
||||
.setHeader("DAV", "addressbook"));
|
||||
HttpUrl principal = finder.getCurrentUserPrincipal(url, DavResourceFinder.Service.CARDDAV);
|
||||
assertEquals(url.resolve("/principals/myself"), principal);
|
||||
URI principal = finder.getCurrentUserPrincipal(url, DavResourceFinder.Service.CARDDAV);
|
||||
assertEquals(url.resolve("/principals/myself").uri(), principal);
|
||||
|
||||
// negative test case: no current-user-principal
|
||||
server.enqueue(new MockResponse()
|
||||
|
@ -43,6 +43,7 @@
|
||||
|
||||
<!-- ical4android declares task access permissions -->
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
@ -5,7 +5,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
@ -33,8 +33,6 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
@ -46,8 +44,6 @@ public class AccountSettings {
|
||||
|
||||
KEY_USERNAME = "user_name",
|
||||
KEY_AUTH_PREEMPTIVE = "auth_preemptive",
|
||||
KEY_LOG_TO_EXTERNAL_FILE = "log_external_file",
|
||||
KEY_LOG_VERBOSE = "log_verbose",
|
||||
KEY_LAST_ANDROID_VERSION = "last_android_version";
|
||||
|
||||
public final static long SYNC_INTERVAL_MANUALLY = -1;
|
||||
@ -129,15 +125,6 @@ public class AccountSettings {
|
||||
public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); }
|
||||
|
||||
|
||||
// logging settings
|
||||
|
||||
public boolean logToExternalFile() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_TO_EXTERNAL_FILE)); }
|
||||
public void logToExternalFile(boolean newValue) { accountManager.setUserData(account, KEY_LOG_TO_EXTERNAL_FILE, Boolean.toString(newValue)); }
|
||||
|
||||
public boolean logVerbose() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_VERBOSE)); }
|
||||
public void logVerbose(boolean newValue) { accountManager.setUserData(account, KEY_LOG_VERBOSE, Boolean.toString(newValue)); }
|
||||
|
||||
|
||||
// sync. settings
|
||||
|
||||
public Long getSyncInterval(String authority) {
|
110
app/src/main/java/at/bitfire/davdroid/App.java
Normal file
110
app/src/main/java/at/bitfire/davdroid/App.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.SimpleFormatter;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import at.bitfire.dav4android.*;
|
||||
import at.bitfire.davdroid.log.LogcatHandler;
|
||||
import de.duenndns.ssl.MemorizingTrustManager;
|
||||
import lombok.Getter;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
|
||||
public class App extends Application implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public static final String
|
||||
PREF_FILE = "global",
|
||||
PREF_LOG_TO_FILE = "log_to_file",
|
||||
PREF_VERBOSE_LOGGING = "verbose_logging";
|
||||
|
||||
@Getter
|
||||
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
|
||||
|
||||
@Getter
|
||||
private static HostnameVerifier hostnameVerifier;
|
||||
|
||||
public final static Logger log = Logger.getLogger("davdroid");
|
||||
|
||||
@Getter
|
||||
private SharedPreferences preferences;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
preferences = getSharedPreferences(PREF_FILE, MODE_PRIVATE);
|
||||
|
||||
// initialize MemorizingTrustManager
|
||||
MemorizingTrustManager mtm = new MemorizingTrustManager(this);
|
||||
sslSocketFactoryCompat = new SSLSocketFactoryCompat(mtm);
|
||||
hostnameVerifier = mtm.wrapHostnameVerifier(OkHostnameVerifier.INSTANCE);
|
||||
|
||||
reinitLogger();
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
// will never be called on production devices
|
||||
super.onTerminate();
|
||||
|
||||
preferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
reinitLogger();
|
||||
}
|
||||
|
||||
|
||||
public void reinitLogger() {
|
||||
// don't use Android default logging, we have our own handlers
|
||||
log.setUseParentHandlers(false);
|
||||
|
||||
// set logging level according to preferences
|
||||
log.setLevel(preferences.getBoolean(PREF_VERBOSE_LOGGING, false) ? Level.ALL : Level.INFO);
|
||||
|
||||
// remove all handlers
|
||||
for (Handler handler : log.getHandlers())
|
||||
log.removeHandler(handler);
|
||||
|
||||
// add logcat handler
|
||||
log.addHandler(LogcatHandler.INSTANCE);
|
||||
|
||||
// log to external file according to preferences
|
||||
if (preferences.getBoolean(PREF_LOG_TO_FILE, false)) {
|
||||
File dir = getExternalFilesDir(null);
|
||||
if (dir != null)
|
||||
try {
|
||||
String pattern = new File(dir, "davdroid-%u.txt").toString();
|
||||
log.info("Logging to external file: " + pattern);
|
||||
|
||||
FileHandler fileHandler = new FileHandler(pattern);
|
||||
fileHandler.setFormatter(new SimpleFormatter());
|
||||
log.addHandler(fileHandler);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Can't create external log file", e);
|
||||
}
|
||||
else
|
||||
log.severe("No external media found, can't create external log file");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -40,7 +40,6 @@ import at.bitfire.dav4android.property.CalendarHomeSet;
|
||||
import at.bitfire.dav4android.property.GroupMembership;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB.*;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -277,11 +276,7 @@ public class DavService extends Service {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.ACCOUNT_NAME}, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
|
||||
AccountSettings settings = new AccountSettings(DavService.this, account);
|
||||
|
||||
OkHttpClient httpClient = HttpClient.create(DavService.this);
|
||||
httpClient = HttpClient.addAuthentication(httpClient, settings.username(), settings.password(), settings.preemptiveAuth());
|
||||
return httpClient;
|
||||
return HttpClient.create(DavService.this, account);
|
||||
} else
|
||||
throw new IllegalArgumentException("Service not found");
|
||||
}
|
||||
|
@ -8,11 +8,10 @@
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
@ -20,23 +19,24 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import at.bitfire.dav4android.BasicDigestAuthenticator;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import de.duenndns.ssl.MemorizingTrustManager;
|
||||
import at.bitfire.davdroid.log.ExternalFileLogger;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
|
||||
public class HttpClient {
|
||||
private static final int MAX_LOG_LINE_LENGTH = 85;
|
||||
|
||||
private static final OkHttpClient client = new OkHttpClient();
|
||||
private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
|
||||
|
||||
@ -49,15 +49,14 @@ public class HttpClient {
|
||||
private HttpClient() {
|
||||
}
|
||||
|
||||
public static OkHttpClient create(Context context) {
|
||||
public static OkHttpClient create(Context context, Account account) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
|
||||
if (context != null) {
|
||||
// use MemorizingTrustManager to manage self-signed certificates
|
||||
MemorizingTrustManager mtm = new MemorizingTrustManager(context);
|
||||
builder.sslSocketFactory(new SSLSocketFactoryCompat(mtm));
|
||||
builder.hostnameVerifier(mtm.wrapHostnameVerifier(OkHostnameVerifier.INSTANCE));
|
||||
}
|
||||
// use MemorizingTrustManager to manage self-signed certificates
|
||||
if (App.getSslSocketFactoryCompat() != null)
|
||||
builder.sslSocketFactory(App.getSslSocketFactoryCompat());
|
||||
if (App.getHostnameVerifier() != null)
|
||||
builder.hostnameVerifier(App.getHostnameVerifier());
|
||||
|
||||
// set timeouts
|
||||
builder.connectTimeout(30, TimeUnit.SECONDS);
|
||||
@ -73,58 +72,57 @@ public class HttpClient {
|
||||
// add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
||||
builder.cookieJar(MemoryCookieStore.INSTANCE);
|
||||
|
||||
if (context != null && account != null) {
|
||||
// use account settings for authentication and logging
|
||||
AccountSettings settings = new AccountSettings(context, account);
|
||||
|
||||
if (settings.preemptiveAuth())
|
||||
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(settings.username(), settings.password()));
|
||||
else
|
||||
builder.authenticator(new BasicDigestAuthenticator(null, settings.username(), settings.password()));
|
||||
|
||||
}
|
||||
|
||||
if (App.log.isLoggable(Level.FINEST))
|
||||
addLogger(builder, App.log);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String username, @NonNull String password, boolean preemptive) {
|
||||
OkHttpClient.Builder builder = httpClient.newBuilder();
|
||||
private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @NonNull String username, @NonNull String password, boolean preemptive) {
|
||||
if (preemptive)
|
||||
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(username, password));
|
||||
else
|
||||
builder.authenticator(new BasicDigestAuthenticator(null, username, password));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password, boolean preemptive) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
addAuthentication(builder, username, password, preemptive);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull AccountSettings accountSettings) {
|
||||
return addAuthentication(httpClient, accountSettings.username(), accountSettings.password(), accountSettings.preemptiveAuth());
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String host, @NonNull String username, @NonNull String password) {
|
||||
return httpClient.newBuilder()
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
|
||||
return client.newBuilder()
|
||||
.authenticator(new BasicDigestAuthenticator(host, username, password))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient addLogger(@NonNull OkHttpClient httpClient, @NonNull final Logger logger) {
|
||||
// enable verbose logs, if requested
|
||||
if (logger.isTraceEnabled()) {
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||
@Override
|
||||
public void log(String message) {
|
||||
BufferedReader reader = new BufferedReader(new StringReader(message));
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
int len = line.length();
|
||||
for (int pos = 0; pos < len; pos += MAX_LOG_LINE_LENGTH)
|
||||
if (pos < len - MAX_LOG_LINE_LENGTH)
|
||||
logger.trace(line.substring(pos, pos + MAX_LOG_LINE_LENGTH) + "\\");
|
||||
else
|
||||
logger.trace(line.substring(pos));
|
||||
}
|
||||
} catch(IOException e) {
|
||||
// for some reason, we couldn't split our message
|
||||
logger.trace(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
private static OkHttpClient.Builder addLogger(@NonNull OkHttpClient.Builder builder, @NonNull final Logger logger) {
|
||||
// trace-level logging → add network traffic interceptor
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||
@Override
|
||||
public void log(String message) {
|
||||
logger.finest(message);
|
||||
}
|
||||
});
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
return builder.addInterceptor(loggingInterceptor);
|
||||
}
|
||||
|
||||
return httpClient.newBuilder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build();
|
||||
} else
|
||||
return httpClient;
|
||||
public static OkHttpClient addLogger(@NonNull OkHttpClient client, @NonNull final Logger logger) {
|
||||
return addLogger(client.newBuilder(), logger).build();
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -25,12 +26,10 @@ import java.util.Locale;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import de.duenndns.ssl.MemorizingTrustManager;
|
||||
import lombok.Cleanup;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class SSLSocketFactoryCompat extends SSLSocketFactory {
|
||||
|
||||
@ -51,7 +50,7 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
|
||||
for (String protocol : socket.getSupportedProtocols())
|
||||
if (!protocol.toUpperCase(Locale.US).contains("SSL"))
|
||||
protocols.add(protocol);
|
||||
Constants.log.debug("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
|
||||
Constants.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
|
||||
SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]);
|
||||
|
||||
/* set up reasonable cipher suites */
|
||||
@ -92,7 +91,7 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
|
||||
HashSet<String> enabledCiphers = preferredCiphers;
|
||||
enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites())));
|
||||
|
||||
Constants.log.debug("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers));
|
||||
Constants.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers));
|
||||
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]);
|
||||
}
|
||||
}
|
||||
@ -112,15 +111,11 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
|
||||
}
|
||||
|
||||
private void upgradeTLS(SSLSocket ssl) {
|
||||
if (protocols != null) {
|
||||
Constants.log.trace("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
|
||||
if (protocols != null)
|
||||
ssl.setEnabledProtocols(protocols);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 20 && cipherSuites != null) {
|
||||
Constants.log.trace("Setting allowed TLS ciphers: " + TextUtils.join(", ", cipherSuites));
|
||||
if (cipherSuites != null)
|
||||
ssl.setEnabledCipherSuites(cipherSuites);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -34,7 +34,6 @@ public abstract class CustomLogger implements Logger {
|
||||
protected String name;
|
||||
|
||||
protected PrintWriter writer;
|
||||
protected boolean verbose;
|
||||
|
||||
|
||||
// CUSTOM LOGGING METHODS
|
||||
@ -69,36 +68,32 @@ public abstract class CustomLogger implements Logger {
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
return verbose;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg) {
|
||||
if (verbose)
|
||||
log(PREFIX_TRACE, msg);
|
||||
log(PREFIX_TRACE, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object arg) {
|
||||
if (verbose)
|
||||
log(PREFIX_TRACE, format, arg);
|
||||
log(PREFIX_TRACE, format, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object arg1, Object arg2) {
|
||||
if (verbose) log(PREFIX_TRACE, format, arg1, arg2);
|
||||
log(PREFIX_TRACE, format, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String format, Object... arguments) {
|
||||
if (verbose)
|
||||
log(PREFIX_TRACE, format, arguments);
|
||||
log(PREFIX_TRACE, format, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(String msg, Throwable t) {
|
||||
if (verbose)
|
||||
log(PREFIX_TRACE, msg, t);
|
||||
log(PREFIX_TRACE, msg, t);
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,9 +19,7 @@ import java.io.PrintWriter;
|
||||
|
||||
public class ExternalFileLogger extends CustomLogger implements Closeable {
|
||||
|
||||
public ExternalFileLogger(Context context, String fileName, boolean verbose) throws IOException {
|
||||
this.verbose = verbose;
|
||||
|
||||
public ExternalFileLogger(Context context, String fileName) throws IOException {
|
||||
File dir = getDirectory(context);
|
||||
if (dir == null)
|
||||
throw new IOException("External media not available for log creation");
|
||||
|
67
app/src/main/java/at/bitfire/davdroid/log/LogcatHandler.java
Normal file
67
app/src/main/java/at/bitfire/davdroid/log/LogcatHandler.java
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class LogcatHandler extends Handler {
|
||||
private static final String TAG = "davdroid";
|
||||
|
||||
public static final LogcatHandler INSTANCE = new LogcatHandler();
|
||||
|
||||
private LogcatHandler() {
|
||||
super();
|
||||
setFormatter(AdbFormatter.INSTANCE);
|
||||
setLevel(Level.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
String line = getFormatter().format(record);
|
||||
int level = record.getLevel().intValue();
|
||||
|
||||
if (level >= Level.SEVERE.intValue())
|
||||
Log.e(TAG, line);
|
||||
else if (level >= Level.WARNING.intValue())
|
||||
Log.w(TAG, line);
|
||||
else if (level >= Level.CONFIG.intValue())
|
||||
Log.i(TAG, line);
|
||||
else if (level >= Level.FINER.intValue())
|
||||
Log.d(TAG, line);
|
||||
else
|
||||
Log.v(TAG, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
|
||||
private static class AdbFormatter extends Formatter {
|
||||
public static AdbFormatter INSTANCE = new AdbFormatter();
|
||||
|
||||
private AdbFormatter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogRecord r) {
|
||||
return String.format("[%s] %s", r.getSourceClassName(), r.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,16 +14,12 @@ import java.io.StringWriter;
|
||||
import lombok.Getter;
|
||||
|
||||
public class StringLogger extends CustomLogger {
|
||||
|
||||
@Getter protected final String name;
|
||||
|
||||
protected final StringWriter stringWriter = new StringWriter();
|
||||
|
||||
|
||||
public StringLogger(String name, boolean verbose) {
|
||||
public StringLogger(String name) {
|
||||
this.name = name;
|
||||
this.verbose = verbose;
|
||||
|
||||
writer = new PrintWriter(stringWriter);
|
||||
}
|
||||
|
||||
@ -31,5 +27,4 @@ public class StringLogger extends CustomLogger {
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ public class ContactsSyncManager extends SyncManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
OkHttpClient resourceClient = HttpClient.create(context);
|
||||
OkHttpClient resourceClient = HttpClient.create(context, null);
|
||||
|
||||
// authenticate only against a certain host, and only upon request
|
||||
resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password());
|
||||
|
@ -39,6 +39,7 @@ package at.bitfire.davdroid.syncadapter;
|
||||
import at.bitfire.dav4android.exception.UnauthorizedException;
|
||||
import at.bitfire.dav4android.property.GetCTag;
|
||||
import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.R;
|
||||
@ -107,21 +108,11 @@ abstract public class SyncManager {
|
||||
this.authority = authority;
|
||||
this.syncResult = syncResult;
|
||||
|
||||
// get account settings and log to file (if requested)
|
||||
// get account settings (for sync interval etc.)
|
||||
settings = new AccountSettings(context, account);
|
||||
try {
|
||||
if (settings.logToExternalFile())
|
||||
log = new ExternalFileLogger(context, "davdroid-SyncManager-" + account.name + "-" + authority + ".txt", settings.logVerbose());
|
||||
} catch(IOException e) {
|
||||
Constants.log.error("Couldn't log to external file", e);
|
||||
}
|
||||
if (log == null)
|
||||
log = Constants.log;
|
||||
|
||||
// create HttpClient with given logger
|
||||
httpClient = HttpClient.create(context);
|
||||
httpClient = HttpClient.addLogger(httpClient, log);
|
||||
httpClient = HttpClient.addAuthentication(httpClient, settings.username(), settings.password(), settings.preemptiveAuth());
|
||||
httpClient = HttpClient.create(context, account);
|
||||
|
||||
// dismiss previous error notifications
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
@ -19,7 +19,7 @@ import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.SwitchPreferenceCompat;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
|
||||
public class AccountSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
@ -20,6 +20,7 @@ import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||
|
@ -50,7 +50,6 @@ import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.Service;
|
||||
import at.bitfire.davdroid.model.HomeSet;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -195,8 +194,7 @@ public class CreateAddressBookActivity extends AppCompatActivity implements Load
|
||||
|
||||
@Override
|
||||
public Exception loadInBackground() {
|
||||
OkHttpClient client = HttpClient.create(getContext());
|
||||
client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
|
||||
OkHttpClient client = HttpClient.create(getContext(), account);
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
|
@ -16,7 +16,6 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
@ -39,7 +38,6 @@ import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -126,11 +124,6 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
|
||||
|
||||
@Override
|
||||
public Exception loadInBackground() {
|
||||
Constants.log.info("MKCOl !!!!");
|
||||
|
||||
OkHttpClient client = HttpClient.create(getContext());
|
||||
client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
XmlSerializer serializer = XmlUtils.newSerializer();
|
||||
@ -212,6 +205,8 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
|
||||
}
|
||||
|
||||
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
|
||||
OkHttpClient client = HttpClient.create(getContext(), account);
|
||||
DavResource collection = new DavResource(null, client, HttpUrl.parse(info.url));
|
||||
try {
|
||||
// create collection on remote server
|
||||
|
@ -24,6 +24,7 @@ import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
@ -40,7 +41,7 @@ import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
|
||||
public class DebugInfoActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
|
||||
public static final String
|
||||
@ -173,8 +174,7 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
|
||||
|
||||
if (exception != null) {
|
||||
report.append("\nSTACK TRACE:\n");
|
||||
for (String stackTrace : ExceptionUtils.getRootCauseStackTrace(exception))
|
||||
report.append(stackTrace + "\n");
|
||||
report.append(Log.getStackTraceString(exception));
|
||||
report.append("\n");
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,6 @@ import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@ -109,8 +108,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
|
||||
|
||||
@Override
|
||||
public Exception loadInBackground() {
|
||||
OkHttpClient httpClient = HttpClient.create(getContext());
|
||||
httpClient = HttpClient.addAuthentication(httpClient, new AccountSettings(getContext(), account));
|
||||
OkHttpClient httpClient = HttpClient.create(getContext(), account);
|
||||
|
||||
DavResource collection = new DavResource(null, httpClient, HttpUrl.parse(collectionInfo.url));
|
||||
try {
|
||||
|
@ -35,7 +35,7 @@ import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.HomeSets;
|
||||
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.xbill.DNS.Lookup;
|
||||
@ -46,7 +46,6 @@ import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.log.StringLogger;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import okhttp3.HttpUrl;
|
||||
@ -60,21 +59,21 @@ public class DavResourceFinder {
|
||||
final String name;
|
||||
Service(String name) { this.name = name;}
|
||||
@Override public String toString() { return name; }
|
||||
};
|
||||
}
|
||||
|
||||
protected final Context context;
|
||||
protected final LoginCredentialsFragment.LoginCredentials credentials;
|
||||
protected final LoginCredentials credentials;
|
||||
|
||||
protected final Logger log = new StringLogger("DavResourceFinder", true);
|
||||
protected final Logger log = new StringLogger("DavResourceFinder");
|
||||
protected OkHttpClient httpClient;
|
||||
|
||||
public DavResourceFinder(@NonNull Context context, @NonNull LoginCredentialsFragment.LoginCredentials credentials) {
|
||||
public DavResourceFinder(@NonNull Context context, @NonNull LoginCredentials credentials) {
|
||||
this.context = context;
|
||||
this.credentials = credentials;
|
||||
|
||||
httpClient = HttpClient.create(context);
|
||||
httpClient = HttpClient.addLogger(httpClient, log);
|
||||
httpClient = HttpClient.create(context, null);
|
||||
httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password, credentials.authPreemptive);
|
||||
//httpClient = HttpClient.addLogger(httpClient, log);
|
||||
}
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
@ -31,9 +32,17 @@ import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
|
||||
protected static final String ARG_LOGIN_CREDENTIALS = "credentials";
|
||||
|
||||
static final String ARG_LOGIN_CREDENTIALS = "credentials";
|
||||
public static DetectConfigurationFragment newInstance(LoginCredentials credentials) {
|
||||
DetectConfigurationFragment frag = new DetectConfigurationFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog dialog = new ProgressDialog(getActivity());
|
||||
@ -55,7 +64,7 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
|
||||
|
||||
@Override
|
||||
public Loader<Configuration> onCreateLoader(int id, Bundle args) {
|
||||
return new ServerConfigurationLoader(getContext(), args);
|
||||
return new ServerConfigurationLoader(getContext(), (LoginCredentials)args.getParcelable(ARG_LOGIN_CREDENTIALS));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,12 +126,12 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
|
||||
|
||||
static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> {
|
||||
final Context context;
|
||||
final LoginCredentialsFragment.LoginCredentials credentials;
|
||||
final LoginCredentials credentials;
|
||||
|
||||
public ServerConfigurationLoader(Context context, Bundle args) {
|
||||
public ServerConfigurationLoader(Context context, LoginCredentials credentials) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
credentials = (LoginCredentialsFragment.LoginCredentials)args.getParcelable(ARG_LOGIN_CREDENTIALS);
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LoginCredentials implements Parcelable {
|
||||
public final URI uri;
|
||||
public final String userName, password;
|
||||
public final boolean authPreemptive;
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(uri);
|
||||
dest.writeString(userName);
|
||||
dest.writeString(password);
|
||||
dest.writeValue(authPreemptive);
|
||||
}
|
||||
|
||||
public static final Creator CREATOR = new Creator<LoginCredentials>() {
|
||||
@Override
|
||||
public LoginCredentials createFromParcel(Parcel source) {
|
||||
LoginCredentials credentials = new LoginCredentials(
|
||||
(URI)source.readSerializable(),
|
||||
source.readString(), source.readString(),
|
||||
(boolean)source.readValue(null)
|
||||
);
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginCredentials[] newArray(int size) {
|
||||
return new LoginCredentials[size];
|
||||
}
|
||||
};
|
||||
}
|
@ -10,9 +10,6 @@ package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.AppCompatCheckBox;
|
||||
import android.support.v7.widget.AppCompatRadioButton;
|
||||
@ -30,7 +27,6 @@ import java.net.URISyntaxException;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.widget.EditPassword;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public class LoginCredentialsFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
@ -73,15 +69,8 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LoginCredentials credentials = validateLoginData();
|
||||
if (credentials != null) {
|
||||
// login data OK, continue with DetectConfigurationFragment
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(DetectConfigurationFragment.ARG_LOGIN_CREDENTIALS, credentials);
|
||||
|
||||
DialogFragment dialog = new DetectConfigurationFragment();
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getFragmentManager(), DetectConfigurationFragment.class.getName());
|
||||
}
|
||||
if (credentials != null)
|
||||
DetectConfigurationFragment.newInstance(credentials).show(getFragmentManager(), null);
|
||||
}
|
||||
});
|
||||
|
||||
@ -126,20 +115,17 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
||||
URI uri = null;
|
||||
boolean valid = true;
|
||||
|
||||
String host = null, path = null;
|
||||
int port = -1;
|
||||
|
||||
Uri baseUrl = Uri.parse(editBaseURL.getText().toString());
|
||||
String scheme = baseUrl.getScheme();
|
||||
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
|
||||
host = IDN.toASCII(baseUrl.getHost());
|
||||
String host = IDN.toASCII(baseUrl.getHost());
|
||||
if (host.isEmpty()) {
|
||||
editBaseURL.setError(getString(R.string.login_url_host_name_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
path = baseUrl.getEncodedPath();
|
||||
port = baseUrl.getPort();
|
||||
String path = baseUrl.getEncodedPath();
|
||||
int port = baseUrl.getPort();
|
||||
try {
|
||||
uri = new URI(baseUrl.getScheme(), null, host, port, path, null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
@ -169,42 +155,4 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class LoginCredentials implements Parcelable {
|
||||
public final URI uri;
|
||||
public final String userName, password;
|
||||
public final boolean authPreemptive;
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(uri);
|
||||
dest.writeString(userName);
|
||||
dest.writeString(password);
|
||||
dest.writeInt(authPreemptive ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<LoginCredentials>() {
|
||||
@Override
|
||||
public LoginCredentials createFromParcel(Parcel source) {
|
||||
LoginCredentials credentials = new LoginCredentials(
|
||||
(URI)source.readSerializable(),
|
||||
source.readString(), source.readString(),
|
||||
source.readInt() != 0 ? true : false
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginCredentials[] newArray(int size) {
|
||||
return new LoginCredentials[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user