mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-23 18:02:15 -05:00
Add activity that is displayed during database upgrades
See UpgradeDatabases.java for a detailed description of the upgrade process.
This commit is contained in:
parent
f67d543510
commit
b2098c8d1c
@ -421,5 +421,13 @@ otherwise it would make K-9 start at the wrong time
|
||||
<activity
|
||||
android:name=".activity.AccountList">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.UpgradeDatabases"
|
||||
android:label="@string/upgrade_databases_title">
|
||||
</activity>
|
||||
<service
|
||||
android:name=".service.DatabaseUpgradeService"
|
||||
android:exported="false">
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
20
res/layout/upgrade_databases.xml
Normal file
20
res/layout/upgrade_databases.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/databaseUpgradeProgress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/databaseUpgradeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/upgrade_databases_unspecified" />
|
||||
|
||||
</LinearLayout>
|
@ -1093,4 +1093,8 @@ http://k9mail.googlecode.com/
|
||||
|
||||
<string name="global_settings_threaded_view_label">Threaded view</string>
|
||||
<string name="global_settings_threaded_view_summary">Group messages by conversation</string>
|
||||
|
||||
<string name="upgrade_databases_title">Upgrading databases</string>
|
||||
<string name="upgrade_databases_unspecified">Upgrading databases…</string>
|
||||
<string name="upgrade_database_format">Upgrading database of account \"<xliff:g id="account">%s</xliff:g>\"</string>
|
||||
</resources>
|
||||
|
@ -15,6 +15,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
@ -27,12 +28,14 @@ import android.util.Log;
|
||||
|
||||
import com.fsck.k9.Account.SortType;
|
||||
import com.fsck.k9.activity.MessageCompose;
|
||||
import com.fsck.k9.activity.UpgradeDatabases;
|
||||
import com.fsck.k9.controller.MessagingController;
|
||||
import com.fsck.k9.controller.MessagingListener;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.provider.UnreadWidgetProvider;
|
||||
import com.fsck.k9.service.BootReceiver;
|
||||
import com.fsck.k9.service.MailService;
|
||||
@ -64,6 +67,23 @@ public class K9 extends Application {
|
||||
public static File tempDirectory;
|
||||
public static final String LOG_TAG = "k9";
|
||||
|
||||
/**
|
||||
* Name of the {@link SharedPreferences} file used to store the last known version of the
|
||||
* accounts' databases.
|
||||
*
|
||||
* <p>
|
||||
* See {@link UpgradeDatabases} for a detailed explanation of the database upgrade process.
|
||||
* </p>
|
||||
*/
|
||||
private static final String DATABASE_VERSION_CACHE = "database_version_cache";
|
||||
|
||||
/**
|
||||
* Key used to store the last known database version of the accounts' databases.
|
||||
*
|
||||
* @see #DATABASE_VERSION_CACHE
|
||||
*/
|
||||
private static final String KEY_LAST_ACCOUNT_DATABASE_VERSION = "last_account_database_version";
|
||||
|
||||
/**
|
||||
* Components that are interested in knowing when the K9 instance is
|
||||
* available and ready.
|
||||
@ -150,6 +170,16 @@ public class K9 extends Application {
|
||||
public static boolean ENABLE_ERROR_FOLDER = true;
|
||||
public static String ERROR_FOLDER_NAME = "K9mail-errors";
|
||||
|
||||
/**
|
||||
* A reference to the {@link SharedPreferences} used for caching the last known database
|
||||
* version.
|
||||
*
|
||||
* @see #checkCachedDatabaseVersion()
|
||||
* @see #setDatabasesUpToDate(boolean)
|
||||
*/
|
||||
private static SharedPreferences sDatabaseVersionCache;
|
||||
|
||||
|
||||
private static boolean mAnimations = true;
|
||||
|
||||
private static boolean mConfirmDelete = false;
|
||||
@ -208,6 +238,11 @@ public class K9 extends Application {
|
||||
private static boolean sUseBackgroundAsUnreadIndicator = true;
|
||||
private static boolean sThreadedViewEnabled = true;
|
||||
|
||||
/**
|
||||
* @see #areDatabasesUpToDate()
|
||||
*/
|
||||
private static boolean sDatabasesUpToDate = false;
|
||||
|
||||
|
||||
/**
|
||||
* The MIME type(s) of attachments we're willing to view.
|
||||
@ -487,6 +522,8 @@ public class K9 extends Application {
|
||||
|
||||
galleryBuggy = checkForBuggyGallery();
|
||||
|
||||
checkCachedDatabaseVersion();
|
||||
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
loadPrefs(prefs);
|
||||
|
||||
@ -581,6 +618,31 @@ public class K9 extends Application {
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last known database version of the accounts' databases from a
|
||||
* {@link SharedPreference}.
|
||||
*
|
||||
* <p>
|
||||
* If the stored version matches {@link LocalStore#DB_VERSION} we know that the databases are
|
||||
* up to date.<br>
|
||||
* Using {@code SharedPreferences} should be a lot faster than opening all SQLite databases to
|
||||
* get the current database version.
|
||||
* </p><p>
|
||||
* See {@link UpgradeDatabases} for a detailed explanation of the database upgrade process.
|
||||
* </p>
|
||||
*
|
||||
* @see #areDatabasesUpToDate()
|
||||
*/
|
||||
public void checkCachedDatabaseVersion() {
|
||||
sDatabaseVersionCache = getSharedPreferences(DATABASE_VERSION_CACHE, MODE_PRIVATE);
|
||||
|
||||
int cachedVersion = sDatabaseVersionCache.getInt(KEY_LAST_ACCOUNT_DATABASE_VERSION, 0);
|
||||
|
||||
if (cachedVersion < LocalStore.DB_VERSION) {
|
||||
K9.setDatabasesUpToDate(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadPrefs(Preferences prefs) {
|
||||
SharedPreferences sprefs = prefs.getPreferences();
|
||||
DEBUG = sprefs.getBoolean("enableDebugLogging", false);
|
||||
@ -1150,4 +1212,38 @@ public class K9 extends Application {
|
||||
public static synchronized void setThreadedViewEnabled(boolean enable) {
|
||||
sThreadedViewEnabled = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we already know whether all databases are using the current database schema.
|
||||
*
|
||||
* <p>
|
||||
* This method is only used for optimizations. If it returns {@code true} we can be certain that
|
||||
* getting a {@link LocalStore} instance won't trigger a schema upgrade.
|
||||
* </p>
|
||||
*
|
||||
* @return {@code true}, if we know that all databases are using the current database schema.
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
public static synchronized boolean areDatabasesUpToDate() {
|
||||
return sDatabasesUpToDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember that all account databases are using the most recent database schema.
|
||||
*
|
||||
* @param save
|
||||
* Whether or not to write the current database version to the
|
||||
* {@code SharedPreferences} {@link #DATABASE_VERSION_CACHE}.
|
||||
*
|
||||
* @see #areDatabasesUpToDate()
|
||||
*/
|
||||
public static synchronized void setDatabasesUpToDate(boolean save) {
|
||||
sDatabasesUpToDate = true;
|
||||
|
||||
if (save) {
|
||||
Editor editor = sDatabaseVersionCache.edit();
|
||||
editor.putInt(KEY_LAST_ACCOUNT_DATABASE_VERSION, LocalStore.DB_VERSION);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -371,6 +371,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
||||
if (accounts.length < 1) {
|
||||
WelcomeMessage.showWelcomeMessage(this);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (UpgradeDatabases.actionUpgradeDatabases(this, intent)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean startup = intent.getBooleanExtra(EXTRA_STARTUP, true);
|
||||
|
220
src/com/fsck/k9/activity/UpgradeDatabases.java
Normal file
220
src/com/fsck/k9/activity/UpgradeDatabases.java
Normal file
@ -0,0 +1,220 @@
|
||||
package com.fsck.k9.activity;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.service.DatabaseUpgradeService;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
/**
|
||||
* This activity triggers a database upgrade if necessary and displays the current upgrade progress.
|
||||
*
|
||||
* <p>
|
||||
* The current upgrade process works as follows:
|
||||
* <ol>
|
||||
* <li>Activities that access an account's database call
|
||||
* {@link #actionUpgradeDatabases(Context, Intent)} in their {@link Activity#onCreate(Bundle)}
|
||||
* method.</li>
|
||||
* <li>{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
|
||||
* to check if we already know whether the databases have been upgraded.</li>
|
||||
* <li>{@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a
|
||||
* {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.LocalStore#DB_VERSION}. This
|
||||
* is done as an optimization because it's faster than opening all of the accounts' databases
|
||||
* one by one.</li>
|
||||
* <li>If there was an error reading the cached database version or if it shows the databases need
|
||||
* upgrading this activity ({@code UpgradeDatabases}) is started.</li>
|
||||
* <li>This activity will display a spinning progress indicator and start
|
||||
* {@link DatabaseUpgradeService}.</li>
|
||||
* <li>{@link DatabaseUpgradeService} will acquire a partial wake lock (with a 10 minute timeout),
|
||||
* start a background thread to perform the database upgrades, and report the progress using
|
||||
* {@link LocalBroadcastManager} to this activity which will update the UI accordingly.</li>
|
||||
* <li>Once the upgrade is complete {@link DatabaseUpgradeService} will notify this activity,
|
||||
* release the wake lock, and stop itself.</li>
|
||||
* <li>This activity will start the original activity using the intent supplied when calling
|
||||
* {@link #actionUpgradeDatabases(Context, Intent)}.</li>
|
||||
* </ol>
|
||||
* </p><p>
|
||||
* Currently we make no attempts to stop the background code (e.g. {@link MessagingController}) from
|
||||
* opening the accounts' databases. If this happens the upgrade is performed in one of the
|
||||
* background threads and not by {@link DatabaseUpgradeService}. But this is not a problem. Due to
|
||||
* the locking in {@link Store#getLocalInstance(Account, android.app.Application)} the upgrade
|
||||
* service will block in the {@link Account#getLocalStore()} call and from the outside (especially
|
||||
* for this activity) it will appear as if {@link DatabaseUpgradeService} is performing the upgrade.
|
||||
* </p>
|
||||
*/
|
||||
public class UpgradeDatabases extends K9Activity {
|
||||
private static final String ACTION_UPGRADE_DATABASES = "upgrade_databases";
|
||||
private static final String EXTRA_START_INTENT = "start_intent";
|
||||
|
||||
|
||||
/**
|
||||
* Start the {@link UpgradeDatabases} activity if necessary.
|
||||
*
|
||||
* @param context
|
||||
* The {@link Context} used to start the activity.
|
||||
* @param startIntent
|
||||
* After the database upgrade is complete an activity is started using this intent.
|
||||
* Usually this is the intent that was used to start the calling activity.
|
||||
* Never {@code null}.
|
||||
*
|
||||
* @return {@code true}, if the {@code UpgradeDatabases} activity was started. In this case the
|
||||
* calling activity is expected to finish itself.<br>
|
||||
* {@code false}, if the account databases don't need upgrading.
|
||||
*/
|
||||
public static boolean actionUpgradeDatabases(Context context, Intent startIntent) {
|
||||
if (K9.areDatabasesUpToDate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(context, UpgradeDatabases.class);
|
||||
intent.setAction(ACTION_UPGRADE_DATABASES);
|
||||
intent.putExtra(EXTRA_START_INTENT, startIntent);
|
||||
|
||||
// Make sure this activity is only running once
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private Intent mStartIntent;
|
||||
|
||||
private TextView mUpgradeText;
|
||||
|
||||
private LocalBroadcastManager mLocalBroadcastManager;
|
||||
private UpgradeDatabaseBroadcastReceiver mBroadcastReceiver;
|
||||
private IntentFilter mIntentFilter;
|
||||
private Preferences mPreferences;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// If the databases have already been upgraded there's no point in displaying this activity.
|
||||
if (K9.areDatabasesUpToDate()) {
|
||||
launchOriginalActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
mPreferences = Preferences.getPreferences(getApplicationContext());
|
||||
|
||||
initializeLayout();
|
||||
|
||||
decodeExtras();
|
||||
|
||||
setupBroadcastReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the activity's layout
|
||||
*/
|
||||
private void initializeLayout() {
|
||||
setContentView(R.layout.upgrade_databases);
|
||||
|
||||
mUpgradeText = (TextView) findViewById(R.id.databaseUpgradeText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode extras in the intent used to start this activity.
|
||||
*/
|
||||
private void decodeExtras() {
|
||||
Intent intent = getIntent();
|
||||
mStartIntent = intent.getParcelableExtra(EXTRA_START_INTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the broadcast receiver used to receive progress updates from
|
||||
* {@link DatabaseUpgradeService}.
|
||||
*/
|
||||
private void setupBroadcastReceiver() {
|
||||
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
mBroadcastReceiver = new UpgradeDatabaseBroadcastReceiver();
|
||||
|
||||
mIntentFilter = new IntentFilter(DatabaseUpgradeService.ACTION_UPGRADE_PROGRESS);
|
||||
mIntentFilter.addAction(DatabaseUpgradeService.ACTION_UPGRADE_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Check if the upgrade was completed while the activity was paused.
|
||||
if (K9.areDatabasesUpToDate()) {
|
||||
launchOriginalActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the broadcast receiver to listen for progress reports from
|
||||
// DatabaseUpgradeService.
|
||||
mLocalBroadcastManager.registerReceiver(mBroadcastReceiver, mIntentFilter);
|
||||
|
||||
// Now that the broadcast receiver was registered start DatabaseUpgradeService.
|
||||
DatabaseUpgradeService.startService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
// The activity is being paused, so there's no point in listening to the progress of the
|
||||
// database upgrade service.
|
||||
mLocalBroadcastManager.unregisterReceiver(mBroadcastReceiver);
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish this activity and launch the original activity using the supplied intent.
|
||||
*/
|
||||
private void launchOriginalActivity() {
|
||||
finish();
|
||||
startActivity(mStartIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver for broadcasts send by {@link DatabaseUpgradeService}.
|
||||
*/
|
||||
class UpgradeDatabaseBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if (DatabaseUpgradeService.ACTION_UPGRADE_PROGRESS.equals(action)) {
|
||||
/*
|
||||
* Information on the current upgrade progress
|
||||
*/
|
||||
|
||||
String accountUuid = intent.getStringExtra(
|
||||
DatabaseUpgradeService.EXTRA_ACCOUNT_UUID);
|
||||
|
||||
Account account = mPreferences.getAccount(accountUuid);
|
||||
|
||||
if (account != null) {
|
||||
String formatString = getString(R.string.upgrade_database_format);
|
||||
String upgradeStatus = String.format(formatString, account.getDescription());
|
||||
mUpgradeText.setText(upgradeStatus);
|
||||
}
|
||||
|
||||
} else if (DatabaseUpgradeService.ACTION_UPGRADE_COMPLETE.equals(action)) {
|
||||
/*
|
||||
* Upgrade complete
|
||||
*/
|
||||
|
||||
launchOriginalActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package com.fsck.k9.mail;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
@ -33,10 +34,16 @@ public abstract class Store {
|
||||
private static HashMap<String, Store> sStores = new HashMap<String, Store>();
|
||||
|
||||
/**
|
||||
* Local stores indexed by UUid because the Uri may change due to migration to/from SD-card.
|
||||
* Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
|
||||
*/
|
||||
private static HashMap<String, Store> sLocalStores = new HashMap<String, Store>();
|
||||
private static ConcurrentHashMap<String, Store> sLocalStores = new ConcurrentHashMap<String, Store>();
|
||||
|
||||
/**
|
||||
* Lock objects indexed by account UUID.
|
||||
*
|
||||
* @see #getLocalInstance(Account, Application)
|
||||
*/
|
||||
private static ConcurrentHashMap<String, Object> sAccountLocks = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* Get an instance of a remote mail store.
|
||||
@ -72,17 +79,37 @@ public abstract class Store {
|
||||
|
||||
/**
|
||||
* Get an instance of a local mail store.
|
||||
* @throws UnavailableStorageException if not {@link StorageProvider#isReady(Context)}
|
||||
*
|
||||
* @throws UnavailableStorageException
|
||||
* if not {@link StorageProvider#isReady(Context)}
|
||||
*/
|
||||
public synchronized static LocalStore getLocalInstance(Account account, Application application) throws MessagingException {
|
||||
Store store = sLocalStores.get(account.getUuid());
|
||||
public static LocalStore getLocalInstance(Account account, Application application)
|
||||
throws MessagingException {
|
||||
|
||||
String accountUuid = account.getUuid();
|
||||
|
||||
// Create new per-account lock object if necessary
|
||||
sAccountLocks.putIfAbsent(accountUuid, new Object());
|
||||
|
||||
// Get the account's lock object
|
||||
Object lock = sAccountLocks.get(accountUuid);
|
||||
|
||||
// Use per-account locks so DatabaseUpgradeService always knows which account database is
|
||||
// currently upgraded.
|
||||
synchronized (lock) {
|
||||
Store store = sLocalStores.get(accountUuid);
|
||||
|
||||
if (store == null) {
|
||||
// Creating a LocalStore instance will create or upgrade the database if
|
||||
// necessary. This could take some time.
|
||||
store = new LocalStore(account, application);
|
||||
sLocalStores.put(account.getUuid(), store);
|
||||
|
||||
sLocalStores.put(accountUuid, store);
|
||||
}
|
||||
|
||||
return (LocalStore) store;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the contents of store-specific URIs and puts them into a {@link ServerSettings}
|
||||
|
@ -111,7 +111,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
*/
|
||||
private static final int UID_CHECK_BATCH_SIZE = 500;
|
||||
|
||||
protected static final int DB_VERSION = 45;
|
||||
public static final int DB_VERSION = 45;
|
||||
|
||||
protected String uUid = null;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user