You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
etesync-android/app/src/main/java/com/etesync/syncadapter/App.kt

284 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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.AccountManager
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Application
import android.content.*
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.StrictMode
import android.provider.CalendarContract
import android.provider.ContactsContract
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider.Companion.TASK_PROVIDERS
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.*
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 com.etesync.syncadapter.utils.LanguageUtils
import com.etesync.syncadapter.utils.NotificationUtils
import com.etesync.syncadapter.utils.TaskProviderHandling
import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource
import io.requery.meta.EntityModel
import io.requery.sql.EntityDataStore
import org.acra.ACRA
import org.jetbrains.anko.doAsync
import java.util.*
class App : Application() {
/**
* @return [EntityDataStore] single instance for the application.
*
*
* Note if you're using Dagger you can make this part of your application level module returning
* `@Provides @Singleton`.
*/
// override onUpgrade to handle migrating to a new version
val data: MyEntityDataStore
get() = initDataStore()
fun initDataStore(): MyEntityDataStore {
val source = MyDatabaseSource(this, Models.DEFAULT, 6)
val configuration = source.configuration
return MyEntityDataStore(configuration)
}
@SuppressLint("HardwareIds")
override fun onCreate() {
super.onCreate()
reinitLogger()
StrictMode.enableDefaults()
initPrefVersion()
NotificationUtils.createChannels(this)
appName = getString(R.string.app_name)
accountType = getString(R.string.account_type)
addressBookAccountType = getString(R.string.account_type_address_book)
addressBooksAuthority = getString(R.string.address_books_authority)
loadLanguage()
// don't block UI for some background checks
doAsync {
// watch installed/removed apps
val tasksFilter = IntentFilter()
tasksFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
tasksFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
tasksFilter.addDataScheme("package")
registerReceiver(PackageChangedReceiver(), tasksFilter)
TASK_PROVIDERS.forEach {
// check whether a tasks app is currently installed
TaskProviderHandling.updateTaskSync(this@App, it)
}
}
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
if (!BuildConfig.DEBUG) {
// The following line triggers the initialization of ACRA
ACRA.init(this, AcraConfiguration.getConfig(this))
val pm = base.packageManager
val installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID)
ACRA.getErrorReporter().putCustomData("installedFrom", installedFrom);
}
}
private fun loadLanguage() {
val serviceDB = ServiceDB.OpenHelper(this)
val lang = Settings(serviceDB.readableDatabase).getString(App.FORCE_LANGUAGE, null)
if (lang != null && lang != DEFAULT_LANGUAGE) {
LanguageUtils.setLanguage(this, lang)
}
serviceDB.close()
}
fun reinitLogger() {
Logger.initialize(this)
}
private class MyDatabaseSource internal constructor(context: Context, entityModel: EntityModel, version: Int) : DatabaseSource(context, entityModel, version) {
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
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;")
}
}
}
/** Init the preferences version of the app.
* This is used to initialise the first version if not alrady set. */
private fun initPrefVersion() {
val prefs = getSharedPreferences("app", Context.MODE_PRIVATE)
if (prefs.getInt(PREF_VERSION, 0) == 0) {
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply()
}
}
private fun update(fromVersion: Int) {
Logger.log.info("Updating from version " + fromVersion + " to " + BuildConfig.VERSION_CODE)
if (fromVersion < 7) {
/* Fix all of the etags to be non-null */
val am = AccountManager.get(this)
for (account in am.getAccountsByType(App.accountType)) {
try {
// Generate account settings to make sure account is migrated.
AccountSettings(this, account)
val calendars = AndroidCalendar.find(account, this.contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)!!,
LocalCalendar.Factory, null, null)
for (calendar in calendars) {
calendar.fixEtags()
}
} catch (e: CalendarStorageException) {
e.printStackTrace()
} catch (e: InvalidAccountException) {
e.printStackTrace()
}
}
for (account in am.getAccountsByType(App.addressBookAccountType)) {
val addressBook = LocalAddressBook(this, account, this.contentResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI))
try {
addressBook.fixEtags()
} catch (e: ContactsStorageException) {
e.printStackTrace()
}
}
}
if (fromVersion < 10) {
HintManager.setHintSeen(this, AccountsActivity.HINT_ACCOUNT_ADD, true)
}
if (fromVersion < 11) {
val dbHelper = ServiceDB.OpenHelper(this)
migrateServices(dbHelper)
dbHelper.close()
}
}
class AppUpdatedReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
Logger.log.info("EteSync was updated, checking for app version")
val app = context.applicationContext as App
val prefs = app.getSharedPreferences("app", Context.MODE_PRIVATE)
val fromVersion = prefs.getInt(PREF_VERSION, 1)
app.update(fromVersion)
prefs.edit().putInt(PREF_VERSION, BuildConfig.VERSION_CODE).apply()
}
}
fun migrateServices(dbHelper: ServiceDB.OpenHelper) {
val db = dbHelper.readableDatabase
val data = this.data
val cursor = db.query(ServiceDB.Services._TABLE, null, null, null, null, null, null)
while (cursor.moveToNext()) {
val values = ContentValues()
DatabaseUtils.cursorRowToContentValues(cursor, values)
val service = ServiceEntity()
service.account = values.getAsString(ServiceDB.Services.ACCOUNT_NAME)
service.type = CollectionInfo.Type.valueOf(values.getAsString(ServiceDB.Services.SERVICE))
data.insert(service)
for (journalEntity in data.select(JournalEntity::class.java).where(JournalEntity.SERVICE.eq(values.getAsLong(ServiceDB.Services.ID))).get()) {
journalEntity.serviceModel = service
data.update(journalEntity)
}
}
db.delete(ServiceDB.Services._TABLE, null, null)
db.close()
cursor.close()
}
companion object {
val DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts"
val LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage"
val OVERRIDE_PROXY = "overrideProxy"
val OVERRIDE_PROXY_HOST = "overrideProxyHost"
val OVERRIDE_PROXY_PORT = "overrideProxyPort"
val PREFER_TASKSORG = "preferTasksOrg"
val FORCE_LANGUAGE = "forceLanguage"
val CHANGE_NOTIFICATION = "show_change_notification"
val OVERRIDE_PROXY_HOST_DEFAULT = "localhost"
val OVERRIDE_PROXY_PORT_DEFAULT = 8118
val DEFAULT_LANGUAGE = "default"
var sDefaultLocacle = Locale.getDefault()
var appName: String = "EteSync"
lateinit var accountType: String
private set
lateinit var addressBookAccountType: String
private set
lateinit var addressBooksAuthority: String
private set
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun getLauncherBitmap(context: Context): Bitmap? {
var bitmapLogo: Bitmap? = null
val drawableLogo = ContextCompat.getDrawable(context, R.mipmap.ic_launcher)
if (drawableLogo is BitmapDrawable)
bitmapLogo = drawableLogo.bitmap
return bitmapLogo
}
// update from previous account settings
private val PREF_VERSION = "version"
}
}