1
0
mirror of https://github.com/etesync/android synced 2024-11-19 22:48:19 +00:00
etesync-android/app/src/main/java/com/etesync/syncadapter/App.java
Tom Hacohen dfb8981752 Remove the ACCOUNT_TYPE constant (now a string resource)
This corresponds to commit 41dae8bcb3335b9e77d9e73b33e9bb14f8900af9 from
DAVdroid, but was done manually.
2017-04-27 11:48:01 +01:00

399 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 com.etesync.syncadapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
import android.os.StrictMode;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import com.etesync.syncadapter.log.LogcatHandler;
import com.etesync.syncadapter.log.PlainTextFormatter;
import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.Models;
import com.etesync.syncadapter.model.ServiceDB;
import com.etesync.syncadapter.model.ServiceEntity;
import com.etesync.syncadapter.model.Settings;
import com.etesync.syncadapter.resource.LocalAddressBook;
import com.etesync.syncadapter.resource.LocalCalendar;
import com.etesync.syncadapter.ui.AccountsActivity;
import com.etesync.syncadapter.utils.HintManager;
import net.fortuna.ical4j.util.UidGenerator;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.vcard4android.ContactsStorageException;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.meta.EntityModel;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import lombok.Cleanup;
import lombok.Getter;
import okhttp3.internal.tls.OkHostnameVerifier;
public class App extends Application {
public static final String FLAVOR_GOOGLE_PLAY = "gplay";
public static final String
DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts",
LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage",
OVERRIDE_PROXY = "overrideProxy",
OVERRIDE_PROXY_HOST = "overrideProxyHost",
OVERRIDE_PROXY_PORT = "overrideProxyPort";
public static final String OVERRIDE_PROXY_HOST_DEFAULT = "localhost";
public static final int OVERRIDE_PROXY_PORT_DEFAULT = 8118;
@Getter
private CustomCertManager certManager;
@Getter
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
@Getter
private static HostnameVerifier hostnameVerifier;
@Getter
private static UidGenerator uidGenerator;
public final static Logger log = Logger.getLogger("syncadapter");
static {
at.bitfire.cert4android.Constants.log = Logger.getLogger("syncadapter.cert4android");
}
@Getter
private static String accountType;
@Getter
private static String addressBookAccountType;
@Getter
private static String addressBooksAuthority;
@Override
@SuppressLint("HardwareIds")
public void onCreate() {
super.onCreate();
reinitCertManager();
reinitLogger();
StrictMode.enableDefaults();
initPrefVersion();
uidGenerator = new UidGenerator(null, android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID));
accountType = getString(R.string.account_type);
addressBookAccountType = getString(R.string.account_type_address_book);
addressBooksAuthority = getString(R.string.address_books_authority);
}
public void reinitCertManager() {
if (BuildConfig.customCerts) {
if (certManager != null)
certManager.close();
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
Settings settings = new Settings(dbHelper.getReadableDatabase());
certManager = new CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false));
sslSocketFactoryCompat = new SSLSocketFactoryCompat(certManager);
hostnameVerifier = certManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
}
}
public void reinitLogger() {
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
Settings settings = new Settings(dbHelper.getReadableDatabase());
boolean logToFile = settings.getBoolean(LOG_TO_EXTERNAL_STORAGE, false),
logVerbose = logToFile || Log.isLoggable(log.getName(), Log.DEBUG);
App.log.info("Verbose logging: " + logVerbose);
// set logging level according to preferences
final Logger rootLogger = Logger.getLogger("");
rootLogger.setLevel(logVerbose ? Level.ALL : Level.INFO);
// remove all handlers and add our own logcat handler
rootLogger.setUseParentHandlers(false);
for (Handler handler : rootLogger.getHandlers())
rootLogger.removeHandler(handler);
rootLogger.addHandler(LogcatHandler.INSTANCE);
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
// log to external file according to preferences
if (logToFile) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder .setSmallIcon(R.drawable.ic_sd_storage_light)
.setLargeIcon(getLauncherBitmap(this))
.setContentTitle(getString(R.string.logging_davdroid_file_logging))
.setLocalOnly(true);
File dir = getExternalFilesDir(null);
if (dir != null)
try {
String fileName = new File(dir, "etesync-" + Process.myPid() + "-" +
DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString();
log.info("Logging to " + fileName);
FileHandler fileHandler = new FileHandler(fileName);
fileHandler.setFormatter(PlainTextFormatter.DEFAULT);
log.addHandler(fileHandler);
builder .setContentText(dir.getPath())
.setSubText(getString(R.string.logging_to_external_storage_warning))
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.logging_to_external_storage, dir.getPath())))
.setOngoing(true);
} catch (IOException e) {
log.log(Level.SEVERE, "Couldn't create external log file", e);
builder .setContentText(getString(R.string.logging_couldnt_create_file, e.getLocalizedMessage()))
.setCategory(NotificationCompat.CATEGORY_ERROR);
}
else
builder.setContentText(getString(R.string.logging_no_external_storage));
nm.notify(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING, builder.build());
} else
nm.cancel(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING);
}
@Nullable
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static Bitmap getLauncherBitmap(@NonNull Context context) {
Bitmap bitmapLogo = null;
Drawable drawableLogo = ContextCompat.getDrawable(context, R.mipmap.ic_launcher);
if (drawableLogo instanceof BitmapDrawable)
bitmapLogo = ((BitmapDrawable)drawableLogo).getBitmap();
return bitmapLogo;
}
public static class ReinitLoggingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
log.info("Received broadcast: re-initializing logger");
App app = (App)context.getApplicationContext();
app.reinitLogger();
}
}
private EntityDataStore<Persistable> dataStore;
/**
* @return {@link EntityDataStore} single instance for the application.
* <p/>
* Note if you're using Dagger you can make this part of your application level module returning
* {@code @Provides @Singleton}.
*/
public EntityDataStore<Persistable> getData() {
if (dataStore == null) {
// override onUpgrade to handle migrating to a new version
DatabaseSource source = new MyDatabaseSource(this, Models.DEFAULT, 4);
Configuration configuration = source.getConfiguration();
dataStore = new EntityDataStore<>(configuration);
}
return dataStore;
}
private static class MyDatabaseSource extends DatabaseSource {
MyDatabaseSource(Context context, EntityModel entityModel, int version) {
super(context, entityModel, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, newVersion);
if (oldVersion < 3) {
db.execSQL("PRAGMA foreign_keys=OFF;");
db.execSQL("CREATE TABLE new_Journal (id integer primary key autoincrement not null, deleted boolean not null, encryptedKey varbinary(255), info varchar(255), owner varchar(255), service integer, serviceModel integer, uid varchar(64) not null, readOnly boolean default false, foreign key (serviceModel) references Service (id) on delete cascade);");
db.execSQL("CREATE TABLE new_Entry (id integer primary key autoincrement not null, content varchar(255), journal integer, uid varchar(64) not null, foreign key (journal) references new_Journal (id) on delete cascade);");
db.execSQL("INSERT INTO new_Journal SELECT id, deleted, encryptedKey, info, owner, service, serviceModel, uid, 0 from Journal;");
db.execSQL("INSERT INTO new_Entry SELECT id, content, journal, uid from Entry;");
db.execSQL("DROP TABLE Journal;");
db.execSQL("DROP TABLE Entry;");
db.execSQL("ALTER TABLE new_Journal RENAME TO Journal;");
db.execSQL("ALTER TABLE new_Entry RENAME TO Entry;");
// Add back indexes
db.execSQL("CREATE UNIQUE INDEX journal_unique_together on Journal (serviceModel, uid);");
db.execSQL("CREATE UNIQUE INDEX entry_unique_together on Entry (journal, uid);");
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
}
// update from previous account settings
private final static String PREF_VERSION = "version";
/** Init the preferences version of the app.
* This is used to initialise the first version if not alrady set. */
private void initPrefVersion() {
SharedPreferences prefs = getSharedPreferences("app", Context.MODE_PRIVATE);
if (prefs.getInt(PREF_VERSION, 0) == 0) {
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply();
}
}
private void update(int fromVersion) {
App.log.info("Updating from version " + fromVersion + " to " + BuildConfig.VERSION_CODE);
if (fromVersion < 6) {
EntityDataStore<Persistable> data = this.getData();
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
List<CollectionInfo> collections = readCollections(dbHelper);
for (CollectionInfo info : collections) {
JournalEntity journalEntity = new JournalEntity(data, info);
data.insert(journalEntity);
}
@Cleanup SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(ServiceDB.Collections._TABLE, null, null);
}
if (fromVersion < 7) {
/* Fix all of the etags to be non-null */
AccountManager am = AccountManager.get(this);
for (Account account : am.getAccountsByType(App.getAccountType())) {
try {
// Generate account settings to make sure account is migrated.
new AccountSettings(this, account);
LocalCalendar calendars[] = (LocalCalendar[]) LocalCalendar.find(account, this.getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI),
LocalCalendar.Factory.INSTANCE, null, null);
for (LocalCalendar calendar : calendars) {
calendar.fixEtags();
}
} catch (CalendarStorageException|InvalidAccountException e) {
e.printStackTrace();
}
}
for (Account account : am.getAccountsByType(App.getAddressBookAccountType())) {
LocalAddressBook addressBook = new LocalAddressBook(this, account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI));
try {
addressBook.fixEtags();
} catch (ContactsStorageException e) {
e.printStackTrace();
}
}
}
if (fromVersion < 10) {
HintManager.setHintSeen(this, AccountsActivity.HINT_ACCOUNT_ADD, true);
}
if (fromVersion < 11) {
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
migrateServices(dbHelper);
}
}
public static class AppUpdatedReceiver extends BroadcastReceiver {
@Override
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
public void onReceive(Context context, Intent intent) {
App.log.info("EteSync was updated, checking for app version");
App app = (App) context.getApplicationContext();
SharedPreferences prefs = app.getSharedPreferences("app", Context.MODE_PRIVATE);
int fromVersion = prefs.getInt(PREF_VERSION, 1);
app.update(fromVersion);
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply();
}
}
@NonNull
private List<CollectionInfo> readCollections(ServiceDB.OpenHelper dbHelper) {
@Cleanup SQLiteDatabase db = dbHelper.getWritableDatabase();
List<CollectionInfo> collections = new LinkedList<>();
@Cleanup Cursor cursor = db.query(ServiceDB.Collections._TABLE, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
collections.add(CollectionInfo.fromDB(values));
}
return collections;
}
public void migrateServices(ServiceDB.OpenHelper dbHelper) {
@Cleanup SQLiteDatabase db = dbHelper.getReadableDatabase();
EntityDataStore<Persistable> data = this.getData();
@Cleanup Cursor cursor = db.query(ServiceDB.Services._TABLE, null, null, null, null, null, null);
while (cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
ServiceEntity service = new ServiceEntity();
service.setAccount(values.getAsString(ServiceDB.Services.ACCOUNT_NAME));
service.setType(CollectionInfo.Type.valueOf(values.getAsString(ServiceDB.Services.SERVICE)));
data.insert(service);
for (JournalEntity journalEntity : data.select(JournalEntity.class).where(JournalEntity.SERVICE.eq(values.getAsLong(ServiceDB.Services.ID))).get()) {
journalEntity.setServiceModel(service);
data.update(journalEntity);
}
}
db.delete(ServiceDB.Services._TABLE, null, null);
}
}