aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/dictionarypack
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/dictionarypack')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java21
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java25
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java12
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java2
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java43
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java68
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java221
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java29
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java18
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java204
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java97
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java22
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataParser.java1
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/PrivateLog.java10
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java177
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java12
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java184
19 files changed, 603 insertions, 551 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3d294acd7..ee142d845 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -120,9 +120,10 @@ public final class ActionBatch {
if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
// The word list is still downloading. Cancel the download and revert the
// word list status to "available".
- manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
- } else if (MetadataDbHelper.STATUS_AVAILABLE != status) {
+ } else if (MetadataDbHelper.STATUS_AVAILABLE != status
+ && MetadataDbHelper.STATUS_RETRYING != status) {
// Should never happen
Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status
+ " for an upgrade action. Fall back to download.");
@@ -171,6 +172,8 @@ public final class ActionBatch {
final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
mWordList.mId, mWordList.mVersion);
+ Log.i(TAG, String.format("Starting the dictionary download with version:"
+ + " %d and Url: %s", mWordList.mVersion, uri));
DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
}
@@ -325,8 +328,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename,
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'available' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -374,9 +377,9 @@ public final class ActionBatch {
final ContentValues values = MetadataDbHelper.makeContentValues(0,
MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
- "", mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ "", mWordList.mRemoteFilename, mWordList.mLastUpdate,
+ mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount,
+ mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
@@ -417,8 +420,8 @@ public final class ActionBatch {
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN),
mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
- mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
- mWordList.mFormatVersion);
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Updating record for " + mWordList.mDescription
+ " and locale " + mWordList.mLocale);
db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 6d6c8f5c6..0fa72c3fd 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -122,19 +122,23 @@ public class ButtonSwitcher extends FrameLayout {
mDeleteButton.setTranslationX(STATUS_DELETE == status ? 0 : width);
}
+ // The helper method for {@link AnimatorListenerAdapter}.
+ void animateButtonIfStatusIsEqual(final View newButton, final int newStatus) {
+ if (newStatus != mStatus) return;
+ animateButton(newButton, ANIMATION_IN);
+ }
+
private void animateButtonPosition(final int oldStatus, final int newStatus) {
final View oldButton = getButton(oldStatus);
final View newButton = getButton(newStatus);
if (null != oldButton && null != newButton) {
// Transition between two buttons : animate out, then in
- animateButton(oldButton, ANIMATION_OUT).setListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (newStatus != mStatus) return;
- animateButton(newButton, ANIMATION_IN);
- }
- });
+ animateButton(oldButton, ANIMATION_OUT).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ animateButtonIfStatusIsEqual(newButton, newStatus);
+ }
+ });
} else if (null != oldButton) {
animateButton(oldButton, ANIMATION_OUT);
} else if (null != newButton) {
@@ -159,9 +163,8 @@ public class ButtonSwitcher extends FrameLayout {
if (ANIMATION_IN == direction) {
button.setClickable(true);
return button.animate().translationX(0);
- } else {
- button.setClickable(false);
- return button.animate().translationX(outerX - innerX);
}
+ button.setClickable(false);
+ return button.animate().translationX(outerX - innerX);
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
index 3d0e29ed0..3cd822a3c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
+++ b/java/src/com/android/inputmethod/dictionarypack/CommonPreferences.java
@@ -22,6 +22,8 @@ import android.content.SharedPreferences;
public final class CommonPreferences {
private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+ public static final String PREF_FORCE_DOWNLOAD_DICT = "pref_key_force_download_dict";
+
public static SharedPreferences getCommonPreferences(final Context context) {
return context.getSharedPreferences(COMMON_PREFERENCES_NAME, 0);
}
@@ -37,4 +39,14 @@ public final class CommonPreferences {
editor.putBoolean(id, false);
editor.apply();
}
+
+ public static boolean isForceDownloadDict(Context context) {
+ return getCommonPreferences(context).getBoolean(PREF_FORCE_DOWNLOAD_DICT, false);
+ }
+
+ public static void setForceDownloadDict(Context context, boolean forceDownload) {
+ SharedPreferences.Editor editor = getCommonPreferences(context).edit();
+ editor.putBoolean(PREF_FORCE_DOWNLOAD_DICT, forceDownload);
+ editor.apply();
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 1d84e5888..759852025 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -148,7 +148,7 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
}
}
- private class UpdateHelper implements Runnable {
+ class UpdateHelper implements Runnable {
private int mProgress;
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index 8e026171d..836340a75 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -32,7 +32,7 @@ import java.util.HashMap;
* in case some dictionaries appeared, disappeared, changed states etc.
*/
public class DictionaryListInterfaceState {
- private static class State {
+ static class State {
public boolean mOpen = false;
public int mStatus = MetadataDbHelper.STATUS_UNKNOWN;
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index f5bd84c8c..659fe5c51 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -31,6 +31,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import java.io.File;
@@ -255,10 +256,9 @@ public final class DictionaryProvider extends ContentProvider {
if (null != dictFiles && dictFiles.size() > 0) {
PrivateLog.log("Returned " + dictFiles.size() + " files");
return new ResourcePathCursor(dictFiles);
- } else {
- PrivateLog.log("No dictionary files for this URL");
- return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
}
+ PrivateLog.log("No dictionary files for this URL");
+ return new ResourcePathCursor(Collections.<WordListInfo>emptyList());
// V2_METADATA and V2_DATAFILE are not supported for query()
default:
return null;
@@ -319,14 +319,13 @@ public final class DictionaryProvider extends ContentProvider {
final AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(
R.raw.empty);
return afd;
- } else {
- final String localFilename =
- wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
- final File f = getContext().getFileStreamPath(localFilename);
- final ParcelFileDescriptor pfd =
- ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
- return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
}
+ final String localFilename =
+ wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
+ final File f = getContext().getFileStreamPath(localFilename);
+ final ParcelFileDescriptor pfd =
+ ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(pfd, 0, pfd.getStatSize());
} catch (FileNotFoundException e) {
// No file : fall through and return null
}
@@ -461,30 +460,32 @@ public final class DictionaryProvider extends ContentProvider {
final String wordlistId = uri.getLastPathSegment();
final String clientId = getClientId(uri);
final ContentValues wordList = getWordlistMetadataForWordlistId(clientId, wordlistId);
- if (null == wordList) return 0;
+ if (null == wordList) {
+ return 0;
+ }
final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
if (MetadataDbHelper.STATUS_DELETING == status) {
UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status);
return 1;
- } else if (MetadataDbHelper.STATUS_INSTALLED == status) {
+ }
+ if (MetadataDbHelper.STATUS_INSTALLED == status) {
final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
if (QUERY_PARAMETER_FAILURE.equals(result)) {
- UpdateHandler.markAsBroken(getContext(), clientId, wordlistId, version);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Dictionary is broken, attempting to retry download & installation.");
+ }
+ UpdateHandler.markAsBrokenOrRetrying(getContext(), clientId, wordlistId, version);
}
final String localFilename =
wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final File f = getContext().getFileStreamPath(localFilename);
// f.delete() returns true if the file was successfully deleted, false otherwise
- if (f.delete()) {
- return 1;
- } else {
- return 0;
- }
- } else {
- Log.e(TAG, "Attempt to delete a file whose status is " + status);
- return 0;
+ return f.delete() ? 1 : 0;
}
+ Log.e(TAG, "Attempt to delete a file whose status is " + status);
+ return 0;
}
/**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 41916b614..bbdf2a380 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,9 +22,12 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
import android.widget.Toast;
+import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
import java.util.Locale;
import java.util.Random;
@@ -32,6 +35,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+
/**
* Service that handles background tasks for the dictionary provider.
*
@@ -50,6 +55,8 @@ import java.util.concurrent.TimeUnit;
* to access, and mark the current state as such.
*/
public final class DictionaryService extends Service {
+ private static final String TAG = DictionaryService.class.getSimpleName();
+
/**
* The package name, to use in the intent actions.
*/
@@ -76,19 +83,29 @@ public final class DictionaryService extends Service {
* How often, in milliseconds, we want to update the metadata. This is a
* floor value; actually, it may happen several hours later, or even more.
*/
- private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
+ private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4);
/**
* We are waked around midnight, local time. We want to wake between midnight and 6 am,
* roughly. So use a random time between 0 and this delay.
*/
- private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
+ private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6);
/**
* How long we consider a "very long time". If no update took place in this time,
* the content provider will trigger an update in the background.
*/
- private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
+ private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14);
+
+ /**
+ * After starting a download, how long we wait before considering it may be stuck. After this
+ * period is elapsed, if the keyboard tries to download again, then we cancel and re-register
+ * the request; if it's within this time, we just leave it be.
+ * It's important to note that we do not re-submit the request merely because the time is up.
+ * This is only to decide whether to cancel the old one and re-requesting when the keyboard
+ * fires a new request for the same data.
+ */
+ public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30);
/**
* An executor that serializes tasks given to it.
@@ -145,9 +162,14 @@ public final class DictionaryService extends Service {
final int startId) {
final DictionaryService self = this;
if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
- // This is a UI action, it can't be run in another thread
- showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
- intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
+ final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT);
+ if (localeString == null) {
+ Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped");
+ } else {
+ // This is a UI action, it can't be run in another thread
+ showStartDownloadingToast(
+ this, LocaleUtils.constructLocaleFromString(localeString));
+ }
} else {
// If it's a command that does not require UI, arrange for the work to be done on a
// separate thread, so that we can return right away. The executor will spawn a thread
@@ -169,15 +191,28 @@ public final class DictionaryService extends Service {
return Service.START_REDELIVER_INTENT;
}
- private static void dispatchBroadcast(final Context context, final Intent intent) {
+ static void dispatchBroadcast(final Context context, final Intent intent) {
if (DATE_CHANGED_INTENT_ACTION.equals(intent.getAction())) {
+ // Do not force download dictionaries on date change updates.
+ CommonPreferences.setForceDownloadDict(context, false);
// This happens when the date of the device changes. This normally happens
// at midnight local time, but it may happen if the user changes the date
// by hand or something similar happens.
checkTimeAndMaybeSetupUpdateAlarm(context);
} else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
// Intent to trigger an update now.
- UpdateHandler.tryUpdate(context, false);
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
+ } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(
+ intent.getAction())) {
+ // Enable force download of dictionaries irrespective of wifi or metered connection.
+ CommonPreferences.setForceDownloadDict(context, true);
+
+ // Initialize the client Db.
+ final String mClientId = context.getString(R.string.dictionary_pack_client_id);
+ BinaryDictionaryFileDumper.initializeClientRecordHelper(context, mClientId);
+
+ // Updates the metadata and the download the dictionaries.
+ UpdateHandler.tryUpdate(context, true);
} else {
UpdateHandler.downloadFinished(context, intent);
}
@@ -188,16 +223,16 @@ public final class DictionaryService extends Service {
*/
private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) {
// Of all clients, if the one that hasn't been updated for the longest
- // is still more recent than UPDATE_FREQUENCY, do nothing.
- if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return;
+ // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing.
+ if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return;
PrivateLog.log("Date changed - registering alarm");
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
- // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning.
+ // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning.
// It doesn't matter too much if this is very inexact.
final long now = System.currentTimeMillis();
- final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
+ final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS);
final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -223,18 +258,19 @@ public final class DictionaryService extends Service {
/**
* Refreshes data if it hasn't been refreshed in a very long time.
*
- * This will check the last update time, and if it's been more than VERY_LONG_TIME,
+ * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS,
* update metadata now - and possibly take subsequent update actions.
*/
public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
- if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
- UpdateHandler.tryUpdate(context, false);
+ if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
+ UpdateHandler.tryUpdate(context, CommonPreferences.isForceDownloadDict(context));
}
/**
* Shows a toast informing the user that an automatic dictionary download is starting.
*/
- private static void showStartDownloadingToast(final Context context, final Locale locale) {
+ private static void showStartDownloadingToast(final Context context,
+ @Nonnull final Locale locale) {
final String toastText = String.format(
context.getString(R.string.toast_downloading_suggestions),
locale.getDisplayName());
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
index 4366348d5..284032beb 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.dictionarypack;
import com.android.inputmethod.latin.utils.FragmentUtils;
+import android.annotation.TargetApi;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
@@ -44,8 +46,8 @@ public final class DictionarySettingsActivity extends PreferenceActivity {
return modIntent;
}
- // TODO: Uncomment the override annotation once we start using SDK version 19.
- // @Override
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
public boolean isValidFragment(String fragmentName) {
return FragmentUtils.isValidFragment(fragmentName);
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 11982fa65..88ea4e6c3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.dictionarypack;
+import com.android.inputmethod.latin.common.LocaleUtils;
+
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -26,12 +28,12 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -105,16 +107,27 @@ public final class DictionarySettingsFragment extends PreferenceFragment
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- final String metadataUri =
- MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
- // We only add the "Refresh" button if we have a non-empty URL to refresh from. If the
- // URL is empty, of course we can't refresh so it makes no sense to display this.
- if (!TextUtils.isEmpty(metadataUri)) {
- mUpdateNowMenu =
- menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
- mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- refreshNetworkState();
- }
+ new AsyncTask<Void, Void, String>() {
+ @Override
+ protected String doInBackground(Void... params) {
+ return MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
+ }
+
+ @Override
+ protected void onPostExecute(String metadataUri) {
+ // We only add the "Refresh" button if we have a non-empty URL to refresh from. If
+ // the URL is empty, of course we can't refresh so it makes no sense to display
+ // this.
+ if (!TextUtils.isEmpty(metadataUri)) {
+ if (mUpdateNowMenu == null) {
+ mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0,
+ R.string.check_for_updates_now);
+ mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
+ refreshNetworkState();
+ }
+ }
+ }.execute();
}
@Override
@@ -123,18 +136,25 @@ public final class DictionarySettingsFragment extends PreferenceFragment
mChangedSettings = false;
UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
- if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
- Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
- final Intent unknownClientBroadcast =
- new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
- unknownClientBroadcast.putExtra(
- DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
- activity.sendBroadcast(unknownClientBroadcast);
- }
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
refreshNetworkState();
+
+ new Thread("onResume") {
+ @Override
+ public void run() {
+ if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
+ Log.i(TAG, "Unknown dictionary pack client: " + mClientId
+ + ". Requesting info.");
+ final Intent unknownClientBroadcast =
+ new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
+ unknownClientBroadcast.putExtra(
+ DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
+ activity.sendBroadcast(unknownClientBroadcast);
+ }
+ }
+ }.start();
}
@Override
@@ -203,25 +223,19 @@ public final class DictionarySettingsFragment extends PreferenceFragment
@Override
public void updateCycleCompleted() {}
- private void refreshNetworkState() {
+ void refreshNetworkState() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
boolean isConnected = null == info ? false : info.isConnected();
if (null != mUpdateNowMenu) mUpdateNowMenu.setEnabled(isConnected);
}
- private void refreshInterface() {
+ void refreshInterface() {
final Activity activity = getActivity();
if (null == activity) return;
- final long lastUpdateDate =
- MetadataDbHelper.getLastUpdateDateForClient(getActivity(), mClientId);
final PreferenceGroup prefScreen = getPreferenceScreen();
final Collection<? extends Preference> prefList =
createInstalledDictSettingsCollection(mClientId);
- final String updateNowSummary = getString(R.string.last_update) + " "
- + DateUtils.formatDateTime(activity, lastUpdateDate,
- DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
-
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -239,14 +253,14 @@ public final class DictionarySettingsFragment extends PreferenceFragment
});
}
- private Preference createErrorMessage(final Activity activity, final int messageResource) {
+ private static Preference createErrorMessage(final Activity activity, final int messageResource) {
final Preference message = new Preference(activity);
message.setTitle(messageResource);
message.setEnabled(false);
return message;
}
- private void removeAnyDictSettings(final PreferenceGroup prefGroup) {
+ static void removeAnyDictSettings(final PreferenceGroup prefGroup) {
for (int i = prefGroup.getPreferenceCount() - 1; i >= 0; --i) {
prefGroup.removePreference(prefGroup.getPreference(i));
}
@@ -276,7 +290,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
.appendQueryParameter(DictionaryProvider.QUERY_PARAMETER_PROTOCOL_VERSION, "2")
.build();
final Activity activity = getActivity();
- final Cursor cursor = null == activity ? null
+ final Cursor cursor = (null == activity) ? null
: activity.getContentResolver().query(contentUri, null, null, null, null);
if (null == cursor) {
@@ -289,61 +303,57 @@ public final class DictionarySettingsFragment extends PreferenceFragment
final ArrayList<Preference> result = new ArrayList<>();
result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
return result;
- } else {
- final String systemLocaleString = Locale.getDefault().toString();
- final TreeMap<String, WordListPreference> prefMap = new TreeMap<>();
- final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
- final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
- final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
- final int descriptionIndex =
- cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN);
- final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
- final int filesizeIndex = cursor.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN);
- do {
- final String wordlistId = cursor.getString(idIndex);
- final int version = cursor.getInt(versionIndex);
- final String localeString = cursor.getString(localeIndex);
- final Locale locale = new Locale(localeString);
- final String description = cursor.getString(descriptionIndex);
- final int status = cursor.getInt(statusIndex);
- final int matchLevel =
- LocaleUtils.getMatchLevel(systemLocaleString, localeString);
- final String matchLevelString =
- LocaleUtils.getMatchLevelSortedString(matchLevel);
- final int filesize = cursor.getInt(filesizeIndex);
- // The key is sorted in lexicographic order, according to the match level, then
- // the description.
- final String key = matchLevelString + "." + description + "." + wordlistId;
- final WordListPreference existingPref = prefMap.get(key);
- if (null == existingPref || existingPref.hasPriorityOver(status)) {
- final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
- final WordListPreference pref;
- if (null != oldPreference
- && oldPreference.mVersion == version
- && oldPreference.hasStatus(status)
- && oldPreference.mLocale.equals(locale)) {
- // If the old preference has all the new attributes, reuse it. Ideally,
- // we should reuse the old pref even if its status is different and call
- // setStatus here, but setStatus calls Preference#setSummary() which
- // needs to be done on the UI thread and we're not on the UI thread
- // here. We could do all this work on the UI thread, but in this case
- // it's probably lighter to stay on a background thread and throw this
- // old preference out.
- pref = oldPreference;
- } else {
- // Otherwise, discard it and create a new one instead.
- // TODO: when the status is different from the old one, we need to
- // animate the old one out before animating the new one in.
- pref = new WordListPreference(activity, mDictionaryListInterfaceState,
- mClientId, wordlistId, version, locale, description, status,
- filesize);
- }
- prefMap.put(key, pref);
- }
- } while (cursor.moveToNext());
- mCurrentPreferenceMap = prefMap;
- return prefMap.values();
}
+ final String systemLocaleString = Locale.getDefault().toString();
+ final TreeMap<String, WordListPreference> prefMap = new TreeMap<>();
+ final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
+ final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
+ final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN);
+ final int descriptionIndex = cursor.getColumnIndex(MetadataDbHelper.DESCRIPTION_COLUMN);
+ final int statusIndex = cursor.getColumnIndex(MetadataDbHelper.STATUS_COLUMN);
+ final int filesizeIndex = cursor.getColumnIndex(MetadataDbHelper.FILESIZE_COLUMN);
+ do {
+ final String wordlistId = cursor.getString(idIndex);
+ final int version = cursor.getInt(versionIndex);
+ final String localeString = cursor.getString(localeIndex);
+ final Locale locale = new Locale(localeString);
+ final String description = cursor.getString(descriptionIndex);
+ final int status = cursor.getInt(statusIndex);
+ final int matchLevel = LocaleUtils.getMatchLevel(systemLocaleString, localeString);
+ final String matchLevelString = LocaleUtils.getMatchLevelSortedString(matchLevel);
+ final int filesize = cursor.getInt(filesizeIndex);
+ // The key is sorted in lexicographic order, according to the match level, then
+ // the description.
+ final String key = matchLevelString + "." + description + "." + wordlistId;
+ final WordListPreference existingPref = prefMap.get(key);
+ if (null == existingPref || existingPref.hasPriorityOver(status)) {
+ final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
+ final WordListPreference pref;
+ if (null != oldPreference
+ && oldPreference.mVersion == version
+ && oldPreference.hasStatus(status)
+ && oldPreference.mLocale.equals(locale)) {
+ // If the old preference has all the new attributes, reuse it. Ideally,
+ // we should reuse the old pref even if its status is different and call
+ // setStatus here, but setStatus calls Preference#setSummary() which
+ // needs to be done on the UI thread and we're not on the UI thread
+ // here. We could do all this work on the UI thread, but in this case
+ // it's probably lighter to stay on a background thread and throw this
+ // old preference out.
+ pref = oldPreference;
+ } else {
+ // Otherwise, discard it and create a new one instead.
+ // TODO: when the status is different from the old one, we need to
+ // animate the old one out before animating the new one in.
+ pref = new WordListPreference(activity, mDictionaryListInterfaceState,
+ mClientId, wordlistId, version, locale, description, status,
+ filesize);
+ }
+ prefMap.put(key, pref);
+ }
+ } while (cursor.moveToNext());
+ mCurrentPreferenceMap = prefMap;
+ return prefMap.values();
} finally {
cursor.close();
}
@@ -384,8 +394,13 @@ public final class DictionarySettingsFragment extends PreferenceFragment
private void cancelRefresh() {
UpdateHandler.unregisterUpdateEventListener(this);
final Context context = getActivity();
- UpdateHandler.cancelUpdate(context, mClientId);
- stopLoadingAnimation();
+ new Thread("cancelByHand") {
+ @Override
+ public void run() {
+ UpdateHandler.cancelUpdate(context, mClientId);
+ stopLoadingAnimation();
+ }
+ }.start();
}
private void startLoadingAnimation() {
@@ -396,26 +411,28 @@ public final class DictionarySettingsFragment extends PreferenceFragment
if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
}
- private void stopLoadingAnimation() {
+ void stopLoadingAnimation() {
final View preferenceView = getView();
final Activity activity = getActivity();
if (null == activity) return;
+ final View loadingView = mLoadingView;
+ final MenuItem updateNowMenu = mUpdateNowMenu;
activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLoadingView.setVisibility(View.GONE);
- preferenceView.setVisibility(View.VISIBLE);
- mLoadingView.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_out));
- preferenceView.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_in));
- // The menu is created by the framework asynchronously after the activity,
- // which means it's possible to have the activity running but the menu not
- // created yet - hence the necessity for a null check here.
- if (null != mUpdateNowMenu) {
- mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
- }
+ @Override
+ public void run() {
+ loadingView.setVisibility(View.GONE);
+ preferenceView.setVisibility(View.VISIBLE);
+ loadingView.startAnimation(AnimationUtils.loadAnimation(
+ activity, android.R.anim.fade_out));
+ preferenceView.startAnimation(AnimationUtils.loadAnimation(
+ activity, android.R.anim.fade_in));
+ // The menu is created by the framework asynchronously after the activity,
+ // which means it's possible to have the activity running but the menu not
+ // created yet - hence the necessity for a null check here.
+ if (null != updateNowMenu) {
+ updateNowMenu.setTitle(R.string.check_for_updates_now);
}
- });
+ }
+ });
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java
new file mode 100644
index 000000000..6247a15e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.dictionarypack;
+
+/**
+ * A simple container of download ID and download start date.
+ */
+public class DownloadIdAndStartDate {
+ public final long mId;
+ public final long mStartDate;
+ public DownloadIdAndStartDate(final long id, final long startDate) {
+ mId = id;
+ mStartDate = startDate;
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
index d3c0a910f..91ed673ae 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
@@ -24,9 +24,11 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
-import java.util.Locale;
+import javax.annotation.Nullable;
/**
* This implements the dialog for asking the user whether it's okay to download dictionaries over
@@ -52,22 +54,30 @@ public final class DownloadOverMeteredDialog extends Activity {
setTexts(localeString, size);
}
- private void setTexts(final String localeString, final long size) {
+ private void setTexts(@Nullable final String localeString, final long size) {
final String promptFormat = getString(R.string.should_download_over_metered_prompt);
final String allowButtonFormat = getString(R.string.download_over_metered);
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- final String language = (null == locale ? "" : locale.getDisplayLanguage());
+ final String language = (null == localeString) ? ""
+ : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt);
prompt.setText(Html.fromHtml(String.format(promptFormat, language)));
final Button allowButton = (Button)findViewById(R.id.allow_button);
allowButton.setText(String.format(allowButtonFormat, ((float)size)/(1024*1024)));
}
+ // This method is externally referenced from layout/download_over_metered.xml using onClick
+ // attribute of Button.
+ @ExternallyReferenced
+ @SuppressWarnings("unused")
public void onClickDeny(final View v) {
UpdateHandler.setDownloadOverMeteredSetting(this, false);
finish();
}
+ // This method is externally referenced from layout/download_over_metered.xml using onClick
+ // attribute of Button.
+ @ExternallyReferenced
+ @SuppressWarnings("unused")
public void onClickAllow(final View v) {
UpdateHandler.setDownloadOverMeteredSetting(this, true);
UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload,
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
deleted file mode 100644
index 4f0805c5c..000000000
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.dictionarypack;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.text.TextUtils;
-
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * A class to help with handling Locales in string form.
- *
- * This file has the same meaning and features (and shares all of its code) with the one with the
- * same name in Latin IME. They need to be kept synchronized; for any update/bugfix to
- * this file, consider also updating/fixing the version in Latin IME.
- */
-public final class LocaleUtils {
- private LocaleUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // Locale match level constants.
- // A higher level of match is guaranteed to have a higher numerical value.
- // Some room is left within constants to add match cases that may arise necessary
- // in the future, for example differentiating between the case where the countries
- // are both present and different, and the case where one of the locales does not
- // specify the countries. This difference is not needed now.
-
- // Nothing matches.
- public static final int LOCALE_NO_MATCH = 0;
- // The languages matches, but the country are different. Or, the reference locale requires a
- // country and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
- // The languages and country match, but the variants are different. Or, the reference locale
- // requires a variant and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
- // The required locale is null or empty so it will accept anything, and the tested locale
- // is non-null and non-empty.
- public static final int LOCALE_ANY_MATCH = 10;
- // The language matches, and the tested locale specifies a country but the reference locale
- // does not require one.
- public static final int LOCALE_LANGUAGE_MATCH = 15;
- // The language and the country match, and the tested locale specifies a variant but the
- // reference locale does not require one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
- // The compared locales are fully identical. This is the best match level.
- public static final int LOCALE_FULL_MATCH = 30;
-
- // The level at which a match is "normally" considered a locale match with standard algorithms.
- // Don't use this directly, use #isMatch to test.
- private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
-
- // Make this match the maximum match level. If this evolves to have more than 2 digits
- // when written in base 10, also adjust the getMatchLevelSortedString method.
- private static final int MATCH_LEVEL_MAX = 30;
-
- /**
- * Return how well a tested locale matches a reference locale.
- *
- * This will check the tested locale against the reference locale and return a measure of how
- * a well it matches the reference. The general idea is that the tested locale has to match
- * every specified part of the required locale. A full match occur when they are equal, a
- * partial match when the tested locale agrees with the reference locale but is more specific,
- * and a difference when the tested locale does not comply with all requirements from the
- * reference locale.
- * In more detail, if the reference locale specifies at least a language and the testedLocale
- * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
- * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
- * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
- * tested locale agree on the language, but not on the country,
- * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
- * and LOCALE_LANGUAGE_MATCH otherwise.
- * If they agree on both the language and the country, but not on the variant,
- * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
- * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
- * LOCALE_FULL_MATCH is returned.
- * Examples:
- * en <=> en_US => LOCALE_LANGUAGE_MATCH
- * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
- * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
- * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
- * sp_US <=> en_US => LOCALE_NO_MATCH
- * de <=> de => LOCALE_FULL_MATCH
- * en_US <=> en_US => LOCALE_FULL_MATCH
- * "" <=> en_US => LOCALE_ANY_MATCH
- *
- * @param referenceLocale the reference locale to test against.
- * @param testedLocale the locale to test.
- * @return a constant that measures how well the tested locale matches the reference locale.
- */
- public static int getMatchLevel(final String referenceLocale, final String testedLocale) {
- if (TextUtils.isEmpty(referenceLocale)) {
- return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
- }
- if (null == testedLocale) return LOCALE_NO_MATCH;
- final String[] referenceParams = referenceLocale.split("_", 3);
- final String[] testedParams = testedLocale.split("_", 3);
- // By spec of String#split, [0] cannot be null and length cannot be 0.
- if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
- switch (referenceParams.length) {
- case 1:
- return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
- case 2:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
- return LOCALE_FULL_MATCH;
- case 3:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- if (!referenceParams[2].equals(testedParams[2]))
- return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- return LOCALE_FULL_MATCH;
- }
- // It should be impossible to come here
- return LOCALE_NO_MATCH;
- }
-
- /**
- * Return a string that represents this match level, with better matches first.
- *
- * The strings are sorted in lexicographic order: a better match will always be less than
- * a worse match when compared together.
- */
- public static String getMatchLevelSortedString(final int matchLevel) {
- // This works because the match levels are 0~99 (actually 0~30)
- // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
- return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
- }
-
- /**
- * Find out whether a match level should be considered a match.
- *
- * This method takes a match level as returned by the #getMatchLevel method, and returns whether
- * it should be considered a match in the usual sense with standard Locale functions.
- *
- * @param level the match level, as returned by getMatchLevel.
- * @return whether this is a match or not.
- */
- public static boolean isMatch(final int level) {
- return LOCALE_MATCH <= level;
- }
-
- /**
- * Sets the system locale for this process.
- *
- * @param res the resources to use. Pass current resources.
- * @param newLocale the locale to change to.
- * @return the old locale.
- */
- public static Locale setSystemLocale(final Resources res, final Locale newLocale) {
- final Configuration conf = res.getConfiguration();
- final Locale saveLocale = conf.locale;
- conf.locale = newLocale;
- res.updateConfiguration(conf, res.getDisplayMetrics());
- return saveLocale;
- }
-
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
-
- /**
- * Creates a locale from a string specification.
- */
- public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
- return null;
- synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
- String[] localeParams = localeStr.split("_", 3);
- if (localeParams.length == 1) {
- retval = new Locale(localeParams[0]);
- } else if (localeParams.length == 2) {
- retval = new Locale(localeParams[0], localeParams[1]);
- } else if (localeParams.length == 3) {
- retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
- }
- if (retval != null) {
- sLocaleCache.put(localeStr, retval);
- }
- return retval;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 17dd781d5..a2789cc1a 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -47,10 +47,14 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// used to identify the versions for upgrades. This should never change going forward.
private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6;
// The current database version.
- private static final int CURRENT_METADATA_DATABASE_VERSION = 9;
+ // This MUST be increased every time the dictionary pack metadata URL changes.
+ private static final int CURRENT_METADATA_DATABASE_VERSION = 14;
private final static long NOT_A_DOWNLOAD_ID = -1;
+ // The number of retries allowed when attempting to download a broken dictionary.
+ public static final int DICTIONARY_RETRY_THRESHOLD = 2;
+
public static final String METADATA_TABLE_NAME = "pendingUpdates";
static final String CLIENT_TABLE_NAME = "clients";
public static final String PENDINGID_COLUMN = "pendingid"; // Download Manager ID
@@ -68,7 +72,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static final String FORMATVERSION_COLUMN = "formatversion";
public static final String FLAGS_COLUMN = "flags";
public static final String RAW_CHECKSUM_COLUMN = "rawChecksum";
- public static final int COLUMN_COUNT = 14;
+ public static final String RETRY_COUNT_COLUMN = "remainingRetries";
+ public static final int COLUMN_COUNT = 15;
private static final String CLIENT_CLIENT_ID_COLUMN = "clientid";
private static final String CLIENT_METADATA_URI_COLUMN = "uri";
@@ -98,6 +103,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// Deleting: the user marked this word list to be deleted, but it has not been yet because
// Latin IME is not up yet.
public static final int STATUS_DELETING = 5;
+ // Retry: dictionary got corrupted, so an attempt must be done to download & install it again.
+ public static final int STATUS_RETRYING = 6;
// Types, for storing in the TYPE_COLUMN
// This is metadata about what is available.
@@ -124,6 +131,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
+ FORMATVERSION_COLUMN + " INTEGER, "
+ FLAGS_COLUMN + " INTEGER, "
+ RAW_CHECKSUM_COLUMN + " TEXT,"
+ + RETRY_COUNT_COLUMN + " INTEGER, "
+ "PRIMARY KEY (" + WORDLISTID_COLUMN + "," + VERSION_COLUMN + "));";
private static final String METADATA_CREATE_CLIENT_TABLE =
"CREATE TABLE IF NOT EXISTS " + CLIENT_TABLE_NAME + " ("
@@ -140,7 +148,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
STATUS_COLUMN, WORDLISTID_COLUMN, LOCALE_COLUMN, DESCRIPTION_COLUMN,
LOCAL_FILENAME_COLUMN, REMOTE_FILENAME_COLUMN, DATE_COLUMN, CHECKSUM_COLUMN,
FILESIZE_COLUMN, VERSION_COLUMN, FORMATVERSION_COLUMN, FLAGS_COLUMN,
- RAW_CHECKSUM_COLUMN };
+ RAW_CHECKSUM_COLUMN, RETRY_COUNT_COLUMN };
// List of all client table columns.
static final String[] CLIENT_TABLE_COLUMNS = { CLIENT_CLIENT_ID_COLUMN,
CLIENT_METADATA_URI_COLUMN, CLIENT_PENDINGID_COLUMN, FLAGS_COLUMN };
@@ -219,7 +227,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
createClientTable(db);
}
- private void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db, final String clientId) {
+ private static void addRawChecksumColumnUnlessPresent(final SQLiteDatabase db) {
try {
db.execSQL("SELECT " + RAW_CHECKSUM_COLUMN + " FROM "
+ METADATA_TABLE_NAME + " LIMIT 0;");
@@ -230,6 +238,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
}
+ private static void addRetryCountColumnUnlessPresent(final SQLiteDatabase db) {
+ try {
+ db.execSQL("SELECT " + RETRY_COUNT_COLUMN + " FROM "
+ + METADATA_TABLE_NAME + " LIMIT 0;");
+ } catch (SQLiteException e) {
+ Log.i(TAG, "No " + RETRY_COUNT_COLUMN + " column : creating it");
+ db.execSQL("ALTER TABLE " + METADATA_TABLE_NAME + " ADD COLUMN "
+ + RETRY_COUNT_COLUMN + " INTEGER DEFAULT " + DICTIONARY_RETRY_THRESHOLD + ";");
+ }
+ }
+
/**
* Upgrade the database. Upgrade from version 3 is supported.
* Version 3 has a DB named METADATA_DATABASE_NAME_STEM containing a table METADATA_TABLE_NAME.
@@ -245,6 +264,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*/
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ // Allow automatic download of dictionaries on upgrading the database.
+ CommonPreferences.setForceDownloadDict(mContext, true);
if (METADATA_DATABASE_INITIAL_VERSION == oldVersion
&& METADATA_DATABASE_VERSION_WITH_CLIENTID <= newVersion
&& CURRENT_METADATA_DATABASE_VERSION >= newVersion) {
@@ -280,7 +301,14 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
// strengthen the system against corrupted dictionary files.
// The most secure way to upgrade a database is to just test for the column presence, and
// add it if it's not there.
- addRawChecksumColumnUnlessPresent(db, mClientId);
+ addRawChecksumColumnUnlessPresent(db);
+
+ // A retry count column that did not exist in the previous versions was added that
+ // corresponds to the number of download & installation attempts that have been made
+ // in order to strengthen the system recovery from corrupted dictionary files.
+ // The most secure way to upgrade a database is to just test for the column presence, and
+ // add it if it's not there.
+ addRetryCountColumnUnlessPresent(db);
}
/**
@@ -408,18 +436,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
*
* @param context a context instance to open the database on
* @param uri the URI to retrieve the metadata download ID of
- * @return the metadata download ID, or NOT_AN_ID if no download is in progress
+ * @return the download id and start date, or null if the URL is not known
*/
- public static long getMetadataDownloadIdForURI(final Context context,
- final String uri) {
+ public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI(
+ final Context context, final String uri) {
SQLiteDatabase defaultDb = getDb(context, null);
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
- new String[] { CLIENT_PENDINGID_COLUMN },
+ new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN },
CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
null, null, null, null);
try {
- if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
- return cursor.getInt(0); // Only one column, return it
+ if (!cursor.moveToFirst()) return null;
+ return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1));
} finally {
cursor.close();
}
@@ -452,8 +480,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
public static ContentValues makeContentValues(final int pendingId, final int type,
final int status, final String wordlistId, final String locale,
final String description, final String filename, final String url, final long date,
- final String rawChecksum, final String checksum, final long filesize, final int version,
- final int formatVersion) {
+ final String rawChecksum, final String checksum, final int retryCount,
+ final long filesize, final int version, final int formatVersion) {
final ContentValues result = new ContentValues(COLUMN_COUNT);
result.put(PENDINGID_COLUMN, pendingId);
result.put(TYPE_COLUMN, type);
@@ -465,6 +493,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
result.put(REMOTE_FILENAME_COLUMN, url);
result.put(DATE_COLUMN, date);
result.put(RAW_CHECKSUM_COLUMN, rawChecksum);
+ result.put(RETRY_COUNT_COLUMN, retryCount);
result.put(CHECKSUM_COLUMN, checksum);
result.put(FILESIZE_COLUMN, filesize);
result.put(VERSION_COLUMN, version);
@@ -502,6 +531,9 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
// Raw checksum unknown unless specified
if (null == result.get(RAW_CHECKSUM_COLUMN)) result.put(RAW_CHECKSUM_COLUMN, "");
+ // Retry column 0 unless specified
+ if (null == result.get(RETRY_COUNT_COLUMN)) result.put(RETRY_COUNT_COLUMN,
+ DICTIONARY_RETRY_THRESHOLD);
// Checksum unknown unless specified
if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
// No filesize unless specified
@@ -551,6 +583,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
putIntResult(result, cursor, DATE_COLUMN);
putStringResult(result, cursor, RAW_CHECKSUM_COLUMN);
putStringResult(result, cursor, CHECKSUM_COLUMN);
+ putIntResult(result, cursor, RETRY_COUNT_COLUMN);
putIntResult(result, cursor, FILESIZE_COLUMN);
putIntResult(result, cursor, VERSION_COLUMN);
putIntResult(result, cursor, FORMATVERSION_COLUMN);
@@ -676,8 +709,16 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final String id, final int version) {
final Cursor cursor = db.query(METADATA_TABLE_NAME,
METADATA_TABLE_COLUMNS,
- WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ?",
- new String[] { id, Integer.toString(version) }, null, null, null);
+ WORDLISTID_COLUMN + "= ? AND " + VERSION_COLUMN + "= ? AND "
+ + FORMATVERSION_COLUMN + "<= ?",
+ new String[]
+ { id,
+ Integer.toString(version),
+ Integer.toString(UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION)
+ },
+ null /* groupBy */,
+ null /* having */,
+ FORMATVERSION_COLUMN + " DESC"/* orderBy */);
if (null == cursor) {
return null;
}
@@ -706,7 +747,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
return null;
}
try {
- // This is a lookup by primary key, so there can't be more than one result.
+ // Return the first result from the list of results.
return getFirstLineAsContentValues(cursor);
} finally {
cursor.close();
@@ -884,6 +925,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final long downloadId) {
final ContentValues values = new ContentValues();
values.put(CLIENT_PENDINGID_COLUMN, downloadId);
+ values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis());
final SQLiteDatabase defaultDb = getDb(context, "");
final Cursor cursor = MetadataDbHelper.queryClientIds(context);
if (null == cursor) return;
@@ -1085,4 +1127,27 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final int version) {
markEntryAs(db, id, version, STATUS_DELETING, NOT_A_DOWNLOAD_ID);
}
+
+ /**
+ * Checks retry counts and marks the word list as retrying if retry is possible.
+ *
+ * @param db the metadata database.
+ * @param id the id of the word list.
+ * @param version the version of the word list.
+ * @return {@code true} if the retry is possible.
+ */
+ public static boolean maybeMarkEntryAsRetrying(final SQLiteDatabase db, final String id,
+ final int version) {
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, id, version);
+ int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
+ if (retryCount > 1) {
+ values.put(STATUS_COLUMN, STATUS_RETRYING);
+ values.put(RETRY_COUNT_COLUMN, retryCount - 1);
+ db.update(METADATA_TABLE_NAME, values,
+ WORDLISTID_COLUMN + " = ? AND " + VERSION_COLUMN + " = ?",
+ new String[] { id, Integer.toString(version) });
+ return true;
+ }
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index d66b69050..329b9f62e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.dictionarypack;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -29,9 +30,6 @@ import java.util.List;
* Helper class to easy up manipulation of dictionary pack metadata.
*/
public class MetadataHandler {
- @SuppressWarnings("unused")
- private static final String TAG = "DictionaryProvider:" + MetadataHandler.class.getSimpleName();
-
// The canonical file name for metadata. This is not the name of a real file on the
// device, but a symbolic name used in the database and in metadata handling. It is never
// tested against, only used for human-readability as the file name for the metadata.
@@ -55,6 +53,7 @@ public class MetadataHandler {
final int rawChecksumIndex =
results.getColumnIndex(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final int checksumIndex = results.getColumnIndex(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCountIndex = results.getColumnIndex(MetadataDbHelper.RETRY_COUNT_COLUMN);
final int localFilenameIndex =
results.getColumnIndex(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final int remoteFilenameIndex =
@@ -70,6 +69,7 @@ public class MetadataHandler {
results.getLong(fileSizeIndex),
results.getString(rawChecksumIndex),
results.getString(checksumIndex),
+ results.getInt(retryCountIndex),
results.getString(localFilenameIndex),
results.getString(remoteFilenameIndex),
results.getInt(versionIndex),
@@ -102,6 +102,22 @@ public class MetadataHandler {
}
/**
+ * Gets the metadata, for a specific dictionary.
+ *
+ * @param context The context to open files over.
+ * @param clientId the client id for retrieving the database. null for default (deprecated).
+ * @param wordListId the word list ID.
+ * @param version the word list version.
+ * @return the current metaData
+ */
+ public static WordListMetadata getCurrentMetadataForWordList(final Context context,
+ final String clientId, final String wordListId, final int version) {
+ final ContentValues contentValues = MetadataDbHelper.getContentValuesByWordListId(
+ MetadataDbHelper.getDb(context, clientId), wordListId, version);
+ return WordListMetadata.createFromContentValues(contentValues);
+ }
+
+ /**
* Read metadata from a stream.
* @param input The stream to read from.
* @return The read metadata.
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
index 52290cadc..2b67ae9ff 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java
@@ -83,6 +83,7 @@ public class MetadataParser {
Long.parseLong(arguments.get(FILESIZE_FIELD_NAME)),
arguments.get(RAW_CHECKSUM_FIELD_NAME),
arguments.get(CHECKSUM_FIELD_NAME),
+ MetadataDbHelper.DICTIONARY_RETRY_THRESHOLD /* retryCount */,
null,
arguments.get(REMOTE_FILENAME_FIELD_NAME),
Integer.parseInt(arguments.get(VERSION_FIELD_NAME)),
diff --git a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java b/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
index 67dd7b9b7..bb64721d5 100644
--- a/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
+++ b/java/src/com/android/inputmethod/dictionarypack/PrivateLog.java
@@ -43,8 +43,8 @@ public class PrivateLog {
+ COLUMN_DATE + " TEXT,"
+ COLUMN_EVENT + " TEXT);";
- private static final SimpleDateFormat sDateFormat =
- new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US);
+ static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
+ "yyyy/MM/dd HH:mm:ss", Locale.ROOT);
private static PrivateLog sInstance = new PrivateLog();
private static DebugHelper sDebugHelper = null;
@@ -62,9 +62,9 @@ public class PrivateLog {
}
}
- private static class DebugHelper extends SQLiteOpenHelper {
+ static class DebugHelper extends SQLiteOpenHelper {
- private DebugHelper(final Context context) {
+ DebugHelper(final Context context) {
super(context, LOG_DATABASE_NAME, null, LOG_DATABASE_VERSION);
}
@@ -84,7 +84,7 @@ public class PrivateLog {
insert(db, "Upgrade finished");
}
- private static void insert(SQLiteDatabase db, String event) {
+ static void insert(SQLiteDatabase db, String event) {
if (!DEBUG) return;
final ContentValues c = new ContentValues(2);
c.put(COLUMN_DATE, sDateFormat.format(new Date(System.currentTimeMillis())));
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 6fbca44c5..30ff0b8ee 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -31,7 +31,6 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.ConnectivityManager;
import android.net.Uri;
-import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
@@ -40,6 +39,8 @@ import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
import com.android.inputmethod.compat.DownloadManagerCompatUtils;
import com.android.inputmethod.compat.NotificationCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
@@ -56,10 +57,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
+import javax.annotation.Nullable;
+
/**
* Handler for the update process.
*
@@ -77,7 +79,8 @@ public final class UpdateHandler {
// DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column
// in SQLite, so it should never return anything < 0.
public static final int NOT_AN_ID = -1;
- public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = 2;
+ public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION =
+ FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION;
// Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long.
private static final int FILE_COPY_BUFFER_SIZE = 8192;
@@ -252,12 +255,16 @@ public final class UpdateHandler {
res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager,
+ DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) {
+ // We already have a recent download in progress. Don't register a new download.
+ return;
+ }
final long downloadId;
synchronized (sSharedIdProtector) {
downloadId = manager.enqueue(metadataRequest);
DebugLogUtils.l("Metadata download requested with id", downloadId);
- // If there is already a download in progress, it's been there for a while and
+ // If there is still a download in progress, it's been there for a while and
// there is probably something wrong with download manager. It's best to just
// overwrite the id and request it again. If the old one happens to finish
// anyway, we don't know about its ID any more, so the downloadFinished
@@ -268,21 +275,29 @@ public final class UpdateHandler {
}
/**
- * Cancels downloading a file, if there is one for this URI.
+ * Cancels downloading a file if there is one for this URI and it's too long.
*
* If we are not currently downloading the file at this URI, this is a no-op.
*
* @param context the context to open the database on
* @param metadataUri the URI to cancel
* @param manager an wrapped instance of DownloadManager
+ * @param graceTime if there was a download started less than this many milliseconds, don't
+ * cancel and return true
+ * @return whether the download is still active
*/
- private static void cancelUpdateWithDownloadManager(final Context context,
- final String metadataUri, final DownloadManagerWrapper manager) {
+ private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context,
+ final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) {
synchronized (sSharedIdProtector) {
- final long metadataDownloadId =
- MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
- if (NOT_AN_ID == metadataDownloadId) return;
- manager.remove(metadataDownloadId);
+ final DownloadIdAndStartDate metadataDownloadIdAndStartDate =
+ MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri);
+ if (null == metadataDownloadIdAndStartDate) return false;
+ if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false;
+ if (metadataDownloadIdAndStartDate.mStartDate + graceTime
+ > System.currentTimeMillis()) {
+ return true;
+ }
+ manager.remove(metadataDownloadIdAndStartDate.mId);
writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
}
// Consider a cancellation as a failure. As such, inform listeners that the download
@@ -290,6 +305,7 @@ public final class UpdateHandler {
for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
listener.downloadedMetadata(false);
}
+ return false;
}
/**
@@ -304,7 +320,7 @@ public final class UpdateHandler {
public static void cancelUpdate(final Context context, final String clientId) {
final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
- cancelUpdateWithDownloadManager(context, metadataUri, manager);
+ maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */);
}
/**
@@ -388,7 +404,7 @@ public final class UpdateHandler {
// If any of these is metadata, we should update the DB
boolean hasMetadata = false;
for (DownloadRecord record : downloadRecords) {
- if (null == record.mAttributes) {
+ if (record.isMetadata()) {
hasMetadata = true;
break;
}
@@ -433,6 +449,8 @@ public final class UpdateHandler {
// download, so we are pretty sure it's alive. It's theoretically possible that it's
// disabled right inbetween the firing of the intent and the control reaching here.
+ boolean dictionaryDownloaded = false;
+
for (final DownloadRecord record : recordList) {
// downloadSuccessful is not final because we may still have exceptions from now on
boolean downloadSuccessful = false;
@@ -447,9 +465,15 @@ public final class UpdateHandler {
final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId);
publishUpdateWordListCompleted(context, downloadSuccessful, fileId,
db, record.mAttributes, record.mClientId);
+ dictionaryDownloaded = true;
}
}
}
+
+ if (dictionaryDownloaded) {
+ // Disable the force download after downloading the dictionaries.
+ CommonPreferences.setForceDownloadDict(context, false);
+ }
// Now that we're done using it, we can remove this download from DLManager
manager.remove(fileId);
}
@@ -738,19 +762,22 @@ public final class UpdateHandler {
* @return an ordered list of runnables to be called to upgrade.
*/
private static ActionBatch compareMetadataForUpgrade(final Context context,
- final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) {
+ final String clientId, @Nullable final List<WordListMetadata> from,
+ @Nullable final List<WordListMetadata> to) {
final ActionBatch actions = new ActionBatch();
// Upgrade existing word lists
DebugLogUtils.l("Comparing dictionaries");
final Set<String> wordListIds = new TreeSet<>();
// TODO: Can these be null?
- if (null == from) from = new ArrayList<>();
- if (null == to) to = new ArrayList<>();
- for (WordListMetadata wlData : from) wordListIds.add(wlData.mId);
- for (WordListMetadata wlData : to) wordListIds.add(wlData.mId);
+ final List<WordListMetadata> fromList = (from == null) ? new ArrayList<WordListMetadata>()
+ : from;
+ final List<WordListMetadata> toList = (to == null) ? new ArrayList<WordListMetadata>()
+ : to;
+ for (WordListMetadata wlData : fromList) wordListIds.add(wlData.mId);
+ for (WordListMetadata wlData : toList) wordListIds.add(wlData.mId);
for (String id : wordListIds) {
- final WordListMetadata currentInfo = MetadataHandler.findWordListById(from, id);
- final WordListMetadata metadataInfo = MetadataHandler.findWordListById(to, id);
+ final WordListMetadata currentInfo = MetadataHandler.findWordListById(fromList, id);
+ final WordListMetadata metadataInfo = MetadataHandler.findWordListById(toList, id);
// TODO: Remove the following unnecessary check, since we are now doing the filtering
// inside findWordListById.
final WordListMetadata newInfo = null == metadataInfo
@@ -785,6 +812,10 @@ public final class UpdateHandler {
} else {
final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
if (newInfo.mVersion == currentInfo.mVersion) {
+ if (TextUtils.equals(newInfo.mRemoteFilename, currentInfo.mRemoteFilename)) {
+ // If the dictionary url hasn't changed, we should preserve the retryCount.
+ newInfo.mRetryCount = currentInfo.mRetryCount;
+ }
// If it's the same id/version, we update the DB with the new values.
// It doesn't matter too much if they didn't change.
actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo));
@@ -797,7 +828,8 @@ public final class UpdateHandler {
actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo));
if (status == MetadataDbHelper.STATUS_INSTALLED
|| status == MetadataDbHelper.STATUS_DISABLED) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo, false));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, newInfo, CommonPreferences.isForceDownloadDict(context)));
} else {
// Pass true to ForgetAction: this is indeed an update to a non-installed
// word list, so activate status == AVAILABLE check
@@ -856,8 +888,8 @@ public final class UpdateHandler {
// None of those are expected to happen, but just in case...
if (null == notificationIntent || null == notificationManager) return;
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- final String language = (null == locale ? "" : locale.getDisplayLanguage());
+ final String language = (null == localeString) ? ""
+ : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final String titleFormat = context.getString(R.string.dict_available_notification_title);
final String notificationTitle = String.format(titleFormat, language);
final Notification.Builder builder = new Notification.Builder(context)
@@ -950,8 +982,10 @@ public final class UpdateHandler {
// change the shared preferences. So there is no way for a word list that has been
// auto-installed once to get auto-installed again, and that's what we want.
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.StartDownloadAction(clientId,
- WordListMetadata.createFromContentValues(installCandidate), false));
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId,
+ WordListMetadata.createFromContentValues(installCandidate),
+ CommonPreferences.isForceDownloadDict(context)));
final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN);
// We are in a content provider: we can't do any UI at all. We have to defer the displaying
// itself to the service. Also, we only display this when the user does not have a
@@ -987,17 +1021,19 @@ public final class UpdateHandler {
public static void markAsUsed(final Context context, final String clientId,
final String wordlistId, final int version,
final int status, final boolean allowDownloadOnMeteredData) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- WordListMetadata wordList = MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
if (MetadataDbHelper.STATUS_DISABLED == status
|| MetadataDbHelper.STATUS_DELETING == status) {
- actions.add(new ActionBatch.EnableAction(clientId, wordList));
+ actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData));
} else if (MetadataDbHelper.STATUS_AVAILABLE == status) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordList,
- allowDownloadOnMeteredData));
+ boolean forceDownloadDict = CommonPreferences.isForceDownloadDict(context);
+ actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData,
+ forceDownloadDict || allowDownloadOnMeteredData));
} else {
Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status);
}
@@ -1022,13 +1058,13 @@ public final class UpdateHandler {
// markAsUsed for consistency.
public static void markAsUnused(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1051,14 +1087,14 @@ public final class UpdateHandler {
*/
public static void markAsDeleting(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordList));
- actions.add(new ActionBatch.StartDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
+ actions.add(new ActionBatch.StartDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
@@ -1076,33 +1112,48 @@ public final class UpdateHandler {
*/
public static void markAsDeleted(final Context context, final String clientId,
final String wordlistId, final int version, final int status) {
- final List<WordListMetadata> currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- final WordListMetadata wordList =
- MetadataHandler.findWordListById(currentMetadata, wordlistId);
- if (null == wordList) return;
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ if (null == wordListMetaData) return;
+
final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.FinishDeleteAction(clientId, wordList));
+ actions.add(new ActionBatch.FinishDeleteAction(clientId, wordListMetaData));
actions.execute(context, new LogProblemReporter(TAG));
signalNewDictionaryState(context);
}
/**
- * Marks the word list with the passed id as broken.
- *
- * This effectively deletes the entry from the metadata. It doesn't prevent the same
- * word list to be downloaded again at a later time if the same or a new version is
- * available the next time we download the metadata.
+ * Checks whether the word list should be downloaded again; in which case an download &
+ * installation attempt is made. Otherwise the word list is marked broken.
*
* @param context the context to open the database on.
* @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as broken.
- * @param version the version of the word list to mark as deleted.
+ * @param wordlistId the id of the word list which is broken.
+ * @param version the version of the broken word list.
*/
- public static void markAsBroken(final Context context, final String clientId,
+ public static void markAsBrokenOrRetrying(final Context context, final String clientId,
final String wordlistId, final int version) {
- // TODO: do this on another thread to avoid blocking the UI.
- MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
- wordlistId, version);
+ boolean isRetryPossible = MetadataDbHelper.maybeMarkEntryAsRetrying(
+ MetadataDbHelper.getDb(context, clientId), wordlistId, version);
+
+ if (isRetryPossible) {
+ if (DEBUG) {
+ Log.d(TAG, "Attempting to download & install the wordlist again.");
+ }
+ final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
+ context, clientId, wordlistId, version);
+
+ final ActionBatch actions = new ActionBatch();
+ actions.add(new ActionBatch.StartDownloadAction(
+ clientId, wordListMetaData, CommonPreferences.isForceDownloadDict(context)));
+ actions.execute(context, new LogProblemReporter(TAG));
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Retries for wordlist exhausted, deleting the wordlist from table.");
+ }
+ MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
+ wordlistId, version);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
index 9e510a68b..59f75e4ed 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
@@ -36,6 +36,7 @@ public class WordListMetadata {
public final String mRemoteFilename;
public final int mVersion; // version of this word list
public final int mFlags; // Always 0 in this version, reserved for future use
+ public int mRetryCount;
// The locale is matched against the locale requested by the client. The matching algorithm
// is a standard locale matching with fallback; it is implemented in
@@ -51,8 +52,9 @@ public class WordListMetadata {
public WordListMetadata(final String id, final int type,
final String description, final long lastUpdate, final long fileSize,
- final String rawChecksum, final String checksum, final String localFilename,
- final String remoteFilename, final int version, final int formatVersion,
+ final String rawChecksum, final String checksum, final int retryCount,
+ final String localFilename, final String remoteFilename,
+ final int version, final int formatVersion,
final int flags, final String locale) {
mId = id;
mType = type;
@@ -61,6 +63,7 @@ public class WordListMetadata {
mFileSize = fileSize;
mRawChecksum = rawChecksum;
mChecksum = checksum;
+ mRetryCount = retryCount;
mLocalFilename = localFilename;
mRemoteFilename = remoteFilename;
mVersion = version;
@@ -82,6 +85,7 @@ public class WordListMetadata {
final Long fileSize = values.getAsLong(MetadataDbHelper.FILESIZE_COLUMN);
final String rawChecksum = values.getAsString(MetadataDbHelper.RAW_CHECKSUM_COLUMN);
final String checksum = values.getAsString(MetadataDbHelper.CHECKSUM_COLUMN);
+ final int retryCount = values.getAsInteger(MetadataDbHelper.RETRY_COUNT_COLUMN);
final String localFilename = values.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
final String remoteFilename = values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN);
final Integer version = values.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
@@ -103,7 +107,8 @@ public class WordListMetadata {
throw new IllegalArgumentException();
}
return new WordListMetadata(id, type, description, lastUpdate, fileSize, rawChecksum,
- checksum, localFilename, remoteFilename, version, formatVersion, flags, locale);
+ checksum, retryCount, localFilename, remoteFilename, version, formatVersion,
+ flags, locale);
}
@Override
@@ -116,6 +121,7 @@ public class WordListMetadata {
sb.append("\nFileSize : ").append(mFileSize);
sb.append("\nRawChecksum : ").append(mRawChecksum);
sb.append("\nChecksum : ").append(mChecksum);
+ sb.append("\nRetryCount: ").append(mRetryCount);
sb.append("\nLocalFilename : ").append(mLocalFilename);
sb.append("\nRemoteFilename : ").append(mRemoteFilename);
sb.append("\nVersion : ").append(mVersion);
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index aea16af0d..500e39e0e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -38,45 +38,39 @@ import java.util.Locale;
* enable or delete it as appropriate for the current state of the word list.
*/
public final class WordListPreference extends Preference {
- static final private String TAG = WordListPreference.class.getSimpleName();
+ private static final String TAG = WordListPreference.class.getSimpleName();
// What to display in the "status" field when we receive unknown data as a status from
// the content provider. Empty string sounds sensible.
- static final private String NO_STATUS_MESSAGE = "";
+ private static final String NO_STATUS_MESSAGE = "";
/// Actions
- static final private int ACTION_UNKNOWN = 0;
- static final private int ACTION_ENABLE_DICT = 1;
- static final private int ACTION_DISABLE_DICT = 2;
- static final private int ACTION_DELETE_DICT = 3;
+ private static final int ACTION_UNKNOWN = 0;
+ private static final int ACTION_ENABLE_DICT = 1;
+ private static final int ACTION_DISABLE_DICT = 2;
+ private static final int ACTION_DELETE_DICT = 3;
// Members
- // The context to get resources
- final Context mContext;
- // The id of the client for which this preference is.
- final String mClientId;
// The metadata word list id and version of this word list.
public final String mWordlistId;
public final int mVersion;
public final Locale mLocale;
public final String mDescription;
+
+ // The id of the client for which this preference is.
+ private final String mClientId;
// The status
private int mStatus;
// The size of the dictionary file
private final int mFilesize;
private final DictionaryListInterfaceState mInterfaceState;
- private final OnWordListPreferenceClick mPreferenceClickHandler =
- new OnWordListPreferenceClick();
- private final OnActionButtonClick mActionButtonClickHandler =
- new OnActionButtonClick();
public WordListPreference(final Context context,
final DictionaryListInterfaceState dictionaryListInterfaceState, final String clientId,
final String wordlistId, final int version, final Locale locale,
final String description, final int status, final int filesize) {
super(context, null);
- mContext = context;
mInterfaceState = dictionaryListInterfaceState;
mClientId = clientId;
mVersion = version;
@@ -116,22 +110,23 @@ public final class WordListPreference extends Preference {
}
private String getSummary(final int status) {
+ final Context context = getContext();
switch (status) {
- // If we are deleting the word list, for the user it's like it's already deleted.
- // It should be reinstallable. Exposing to the user the whole complexity of
- // the delayed deletion process between the dictionary pack and Android Keyboard
- // would only be confusing.
- case MetadataDbHelper.STATUS_DELETING:
- case MetadataDbHelper.STATUS_AVAILABLE:
- return mContext.getString(R.string.dictionary_available);
- case MetadataDbHelper.STATUS_DOWNLOADING:
- return mContext.getString(R.string.dictionary_downloading);
- case MetadataDbHelper.STATUS_INSTALLED:
- return mContext.getString(R.string.dictionary_installed);
- case MetadataDbHelper.STATUS_DISABLED:
- return mContext.getString(R.string.dictionary_disabled);
- default:
- return NO_STATUS_MESSAGE;
+ // If we are deleting the word list, for the user it's like it's already deleted.
+ // It should be reinstallable. Exposing to the user the whole complexity of
+ // the delayed deletion process between the dictionary pack and Android Keyboard
+ // would only be confusing.
+ case MetadataDbHelper.STATUS_DELETING:
+ case MetadataDbHelper.STATUS_AVAILABLE:
+ return context.getString(R.string.dictionary_available);
+ case MetadataDbHelper.STATUS_DOWNLOADING:
+ return context.getString(R.string.dictionary_downloading);
+ case MetadataDbHelper.STATUS_INSTALLED:
+ return context.getString(R.string.dictionary_installed);
+ case MetadataDbHelper.STATUS_DISABLED:
+ return context.getString(R.string.dictionary_disabled);
+ default:
+ return NO_STATUS_MESSAGE;
}
}
@@ -154,7 +149,7 @@ public final class WordListPreference extends Preference {
{ ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }
};
- private int getButtonSwitcherStatus(final int status) {
+ static int getButtonSwitcherStatus(final int status) {
if (status >= sStatusActionList.length) {
Log.e(TAG, "Unknown status " + status);
return ButtonSwitcher.STATUS_NO_BUTTON;
@@ -162,7 +157,7 @@ public final class WordListPreference extends Preference {
return sStatusActionList[status][0];
}
- private static int getActionIdFromStatusAndMenuEntry(final int status) {
+ static int getActionIdFromStatusAndMenuEntry(final int status) {
if (status >= sStatusActionList.length) {
Log.e(TAG, "Unknown status " + status);
return ACTION_UNKNOWN;
@@ -171,9 +166,10 @@ public final class WordListPreference extends Preference {
}
private void disableDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
- UpdateHandler.markAsUnused(mContext, mClientId, mWordlistId, mVersion, mStatus);
+ UpdateHandler.markAsUnused(context, mClientId, mWordlistId, mVersion, mStatus);
if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) {
setStatus(MetadataDbHelper.STATUS_AVAILABLE);
} else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) {
@@ -184,11 +180,13 @@ public final class WordListPreference extends Preference {
Log.e(TAG, "Unexpected state of the word list for disabling " + mStatus);
}
}
+
private void enableDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.enable(prefs, mWordlistId);
// Explicit enabling by the user : allow downloading on metered data connection.
- UpdateHandler.markAsUsed(mContext, mClientId, mWordlistId, mVersion, mStatus, true);
+ UpdateHandler.markAsUsed(context, mClientId, mWordlistId, mVersion, mStatus, true);
if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) {
setStatus(MetadataDbHelper.STATUS_DOWNLOADING);
} else if (MetadataDbHelper.STATUS_DISABLED == mStatus
@@ -203,11 +201,13 @@ public final class WordListPreference extends Preference {
Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus);
}
}
+
private void deleteDict() {
- SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
+ final Context context = getContext();
+ final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
setStatus(MetadataDbHelper.STATUS_DELETING);
- UpdateHandler.markAsDeleting(mContext, mClientId, mWordlistId, mVersion, mStatus);
+ UpdateHandler.markAsDeleting(context, mClientId, mWordlistId, mVersion, mStatus);
}
@Override
@@ -225,8 +225,8 @@ public final class WordListPreference extends Preference {
status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE);
- final ButtonSwitcher buttonSwitcher =
- (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
+ final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)view.findViewById(
+ R.id.wordlist_button_switcher);
// We need to clear the state of the button switcher, because we reuse views; if we didn't
// reset it would animate from whatever its old state was.
buttonSwitcher.reset(mInterfaceState);
@@ -244,63 +244,67 @@ public final class WordListPreference extends Preference {
// The button is closed.
buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
}
- buttonSwitcher.setInternalOnClickListener(mActionButtonClickHandler);
- view.setOnClickListener(mPreferenceClickHandler);
+ buttonSwitcher.setInternalOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onActionButtonClicked();
+ }
+ });
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onWordListClicked(v);
+ }
+ });
}
- private class OnWordListPreferenceClick implements View.OnClickListener {
- @Override
- public void onClick(final View v) {
- // Note : v is the preference view
- final ViewParent parent = v.getParent();
- // Just in case something changed in the framework, test for the concrete class
- if (!(parent instanceof ListView)) return;
- final ListView listView = (ListView)parent;
- final int indexToOpen;
- // Close all first, we'll open back any item that needs to be open.
- final boolean wasOpen = mInterfaceState.isOpen(mWordlistId);
- mInterfaceState.closeAll();
- if (wasOpen) {
- // This button being shown. Take note that we don't want to open any button in the
- // loop below.
- indexToOpen = -1;
+ void onWordListClicked(final View v) {
+ // Note : v is the preference view
+ final ViewParent parent = v.getParent();
+ // Just in case something changed in the framework, test for the concrete class
+ if (!(parent instanceof ListView)) return;
+ final ListView listView = (ListView)parent;
+ final int indexToOpen;
+ // Close all first, we'll open back any item that needs to be open.
+ final boolean wasOpen = mInterfaceState.isOpen(mWordlistId);
+ mInterfaceState.closeAll();
+ if (wasOpen) {
+ // This button being shown. Take note that we don't want to open any button in the
+ // loop below.
+ indexToOpen = -1;
+ } else {
+ // This button was not being shown. Open it, and remember the index of this
+ // child as the one to open in the following loop.
+ mInterfaceState.setOpen(mWordlistId, mStatus);
+ indexToOpen = listView.indexOfChild(v);
+ }
+ final int lastDisplayedIndex =
+ listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
+ // The "lastDisplayedIndex" is actually displayed, hence the <=
+ for (int i = 0; i <= lastDisplayedIndex; ++i) {
+ final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i)
+ .findViewById(R.id.wordlist_button_switcher);
+ if (i == indexToOpen) {
+ buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
} else {
- // This button was not being shown. Open it, and remember the index of this
- // child as the one to open in the following loop.
- mInterfaceState.setOpen(mWordlistId, mStatus);
- indexToOpen = listView.indexOfChild(v);
- }
- final int lastDisplayedIndex =
- listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
- // The "lastDisplayedIndex" is actually displayed, hence the <=
- for (int i = 0; i <= lastDisplayedIndex; ++i) {
- final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i)
- .findViewById(R.id.wordlist_button_switcher);
- if (i == indexToOpen) {
- buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
- } else {
- buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
- }
+ buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
}
}
}
- private class OnActionButtonClick implements View.OnClickListener {
- @Override
- public void onClick(final View v) {
- switch (getActionIdFromStatusAndMenuEntry(mStatus)) {
- case ACTION_ENABLE_DICT:
- enableDict();
- break;
- case ACTION_DISABLE_DICT:
- disableDict();
- break;
- case ACTION_DELETE_DICT:
- deleteDict();
- break;
- default:
- Log.e(TAG, "Unknown menu item pressed");
- }
+ void onActionButtonClicked() {
+ switch (getActionIdFromStatusAndMenuEntry(mStatus)) {
+ case ACTION_ENABLE_DICT:
+ enableDict();
+ break;
+ case ACTION_DISABLE_DICT:
+ disableDict();
+ break;
+ case ACTION_DELETE_DICT:
+ deleteDict();
+ break;
+ default:
+ Log.e(TAG, "Unknown menu item pressed");
}
}
}