diff options
Diffstat (limited to 'java/src/com/android/inputmethod/dictionarypack')
13 files changed, 249 insertions, 278 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java index bf2230553..d5e638e7e 100644 --- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java +++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.inputmethod.compat.DownloadManagerCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.ApplicationUtils; +import com.android.inputmethod.latin.utils.DebugLogUtils; import java.util.LinkedList; import java.util.Queue; @@ -98,7 +100,7 @@ public final class ActionBatch { final boolean mForceStartNow; public StartDownloadAction(final String clientId, final WordListMetadata wordList, final boolean forceStartNow) { - Utils.l("New download action for client ", clientId, " : ", wordList); + DebugLogUtils.l("New download action for client ", clientId, " : ", wordList); mClientId = clientId; mWordList = wordList; mForceStartNow = forceStartNow; @@ -110,7 +112,7 @@ public final class ActionBatch { Log.e(TAG, "UpdateAction with a null parameter!"); return; } - Utils.l("Downloading word list"); + DebugLogUtils.l("Downloading word list"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -132,7 +134,7 @@ public final class ActionBatch { + " for an upgrade action. Fall back to download."); } // Download it. - Utils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); + DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); // TODO: if DownloadManager is disabled or not installed, download by ourselves if (null == manager) return; @@ -142,7 +144,7 @@ public final class ActionBatch { // DownloadManager also stupidly cuts the extension to replace with its own that it // gets from the content-type. We need to circumvent this. final String disambiguator = "#" + System.currentTimeMillis() - + com.android.inputmethod.latin.Utils.getVersionName(context) + ".dict"; + + ApplicationUtils.getVersionName(context) + ".dict"; final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator); final Request request = new Request(uri); @@ -178,7 +180,7 @@ public final class ActionBatch { final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db, mWordList.mId, mWordList.mVersion); - Utils.l("Starting download of", uri, "with id", downloadId); + DebugLogUtils.l("Starting download of", uri, "with id", downloadId); PrivateLog.log("Starting download of " + uri + ", id : " + downloadId); } } @@ -195,7 +197,8 @@ public final class ActionBatch { public InstallAfterDownloadAction(final String clientId, final ContentValues wordListValues) { - Utils.l("New InstallAfterDownloadAction for client ", clientId, " : ", wordListValues); + DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ", + wordListValues); mClientId = clientId; mWordListValues = wordListValues; } @@ -213,7 +216,7 @@ public final class ActionBatch { + " for an InstallAfterDownload action. Bailing out."); return; } - Utils.l("Setting word list as installed"); + DebugLogUtils.l("Setting word list as installed"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues); } @@ -229,7 +232,7 @@ public final class ActionBatch { final WordListMetadata mWordList; public EnableAction(final String clientId, final WordListMetadata wordList) { - Utils.l("New EnableAction for client ", clientId, " : ", wordList); + DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList); mClientId = clientId; mWordList = wordList; } @@ -240,7 +243,7 @@ public final class ActionBatch { Log.e(TAG, "EnableAction with a null parameter!"); return; } - Utils.l("Enabling word list"); + DebugLogUtils.l("Enabling word list"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -264,7 +267,7 @@ public final class ActionBatch { // The word list to disable. May not be null. final WordListMetadata mWordList; public DisableAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New Disable action for client ", clientId, " : ", wordlist); + DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -275,7 +278,7 @@ public final class ActionBatch { Log.e(TAG, "DisableAction with a null word list!"); return; } - Utils.l("Disabling word list : " + mWordList); + DebugLogUtils.l("Disabling word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -311,7 +314,7 @@ public final class ActionBatch { // The word list to make available. May not be null. final WordListMetadata mWordList; public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New MakeAvailable action", clientId, " : ", wordlist); + DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -328,7 +331,7 @@ public final class ActionBatch { Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " + " for a makeavailable action. Marking as available anyway."); } - Utils.l("Making word list available : " + mWordList); + DebugLogUtils.l("Making word list available : " + mWordList); // If mLocalFilename is null, then it's a remote file that hasn't been downloaded // yet, so we set the local filename to the empty string. final ContentValues values = MetadataDbHelper.makeContentValues(0, @@ -360,7 +363,7 @@ public final class ActionBatch { // The word list to mark pre-installed. May not be null. final WordListMetadata mWordList; public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New MarkPreInstalled action", clientId, " : ", wordlist); + DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -377,7 +380,7 @@ public final class ActionBatch { Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " + " for a markpreinstalled action. Marking as preinstalled anyway."); } - Utils.l("Marking word list preinstalled : " + mWordList); + DebugLogUtils.l("Marking word list preinstalled : " + mWordList); // This word list is pre-installed : we don't have its file. We should reset // the local file name to the empty string so that we don't try to open it // accidentally. The remote filename may be set by the application if it so wishes. @@ -401,7 +404,7 @@ public final class ActionBatch { private final String mClientId; final WordListMetadata mWordList; public UpdateDataAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New UpdateData action for client ", clientId, " : ", wordlist); + DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -419,7 +422,7 @@ public final class ActionBatch { Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out."); return; } - Utils.l("Updating data about a word list : " + mWordList); + DebugLogUtils.l("Updating data about a word list : " + mWordList); final ContentValues values = MetadataDbHelper.makeContentValues( oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN), oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN), @@ -453,7 +456,7 @@ public final class ActionBatch { final boolean mHasNewerVersion; public ForgetAction(final String clientId, final WordListMetadata wordlist, final boolean hasNewerVersion) { - Utils.l("New TryRemove action for client ", clientId, " : ", wordlist); + DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; mHasNewerVersion = hasNewerVersion; @@ -465,7 +468,7 @@ public final class ActionBatch { Log.e(TAG, "TryRemoveAction with a null word list!"); return; } - Utils.l("Trying to remove word list : " + mWordList); + DebugLogUtils.l("Trying to remove word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -525,7 +528,7 @@ public final class ActionBatch { // The word list to delete. May not be null. final WordListMetadata mWordList; public StartDeleteAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New StartDelete action for client ", clientId, " : ", wordlist); + DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -536,7 +539,7 @@ public final class ActionBatch { Log.e(TAG, "StartDeleteAction with a null word list!"); return; } - Utils.l("Trying to delete word list : " + mWordList); + DebugLogUtils.l("Trying to delete word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -564,7 +567,7 @@ public final class ActionBatch { // The word list to delete. May not be null. final WordListMetadata mWordList; public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New FinishDelete action for client", clientId, " : ", wordlist); + DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -575,7 +578,7 @@ public final class ActionBatch { Log.e(TAG, "FinishDeleteAction with a null word list!"); return; } - Utils.l("Trying to delete word list : " + mWordList); + DebugLogUtils.l("Trying to delete word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -632,7 +635,7 @@ public final class ActionBatch { * @param reporter a Reporter to send errors to. */ public void execute(final Context context, final ProblemReporter reporter) { - Utils.l("Executing a batch of actions"); + DebugLogUtils.l("Executing a batch of actions"); Queue<Action> remainingActions = mActions; while (!remainingActions.isEmpty()) { final Action a = remainingActions.poll(); diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java index 5ab94a429..6d6c8f5c6 100644 --- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java +++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java @@ -47,6 +47,7 @@ public class ButtonSwitcher extends FrameLayout { private Button mInstallButton; private Button mCancelButton; private Button mDeleteButton; + private DictionaryListInterfaceState mInterfaceState; private OnClickListener mOnClickListener; public ButtonSwitcher(Context context, AttributeSet attrs) { @@ -57,6 +58,12 @@ public class ButtonSwitcher extends FrameLayout { super(context, attrs, defStyle); } + public void reset(final DictionaryListInterfaceState interfaceState) { + mStatus = NOT_INITIALIZED; + mAnimateToStatus = NOT_INITIALIZED; + mInterfaceState = interfaceState; + } + @Override protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { @@ -64,9 +71,7 @@ public class ButtonSwitcher extends FrameLayout { mInstallButton = (Button)findViewById(R.id.dict_install_button); mCancelButton = (Button)findViewById(R.id.dict_cancel_button); mDeleteButton = (Button)findViewById(R.id.dict_delete_button); - mInstallButton.setOnClickListener(mOnClickListener); - mCancelButton.setOnClickListener(mOnClickListener); - mDeleteButton.setOnClickListener(mOnClickListener); + setInternalOnClickListener(mOnClickListener); setButtonPositionWithoutAnimation(mStatus); if (mAnimateToStatus != NOT_INITIALIZED) { // We have been asked to animate before we were ready, so we took a note of it. @@ -139,11 +144,18 @@ public class ButtonSwitcher extends FrameLayout { public void setInternalOnClickListener(final OnClickListener listener) { mOnClickListener = listener; + if (null != mInstallButton) { + // Already laid out : do it now + mInstallButton.setOnClickListener(mOnClickListener); + mCancelButton.setOnClickListener(mOnClickListener); + mDeleteButton.setOnClickListener(mOnClickListener); + } } private ViewPropertyAnimator animateButton(final View button, final int direction) { final float outerX = getWidth(); final float innerX = button.getX() - button.getTranslationX(); + mInterfaceState.removeFromCache((View)getParent()); if (ANIMATION_IN == direction) { button.setClickable(true); return button.animate().translationX(0); diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java index de3711c27..13c07de35 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java @@ -16,8 +16,11 @@ package com.android.inputmethod.dictionarypack; -import com.android.inputmethod.latin.CollectionUtils; +import android.view.View; +import com.android.inputmethod.latin.utils.CollectionUtils; + +import java.util.ArrayList; import java.util.HashMap; /** @@ -37,6 +40,7 @@ public class DictionaryListInterfaceState { } private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap(); + private ArrayList<View> mViewCache = CollectionUtils.newArrayList(); public boolean isOpen(final String wordlistId) { final State state = mWordlistToState.get(wordlistId); @@ -64,4 +68,20 @@ public class DictionaryListInterfaceState { state.mOpen = false; } } + + public View findFirstOrphanedView() { + for (final View v : mViewCache) { + if (null == v.getParent()) return v; + } + return null; + } + + public View addToCacheAndReturnView(final View view) { + mViewCache.add(view); + return view; + } + + public void removeFromCache(final View view) { + mViewCache.remove(view); + } } diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java index 69615887f..df0e3f0e1 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java @@ -28,13 +28,13 @@ public class DictionaryPackConstants { * The root domain for the dictionary pack, upon which authorities and actions will append * their own distinctive strings. */ - private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack"; + private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp"; /** * Authority for the ContentProvider protocol. */ // TODO: find some way to factorize this string with the one in the resources - public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp"; + public static final String AUTHORITY = DICTIONARY_DOMAIN; /** * The action of the intent for publishing that new dictionary data is available. @@ -52,7 +52,14 @@ public class DictionaryPackConstants { */ public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN + ".UNKNOWN_CLIENT"; + // In the above intents, the name of the string extra that contains the name of the client // we want information about. public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client"; + + /** + * The action of the intent to tell the dictionary provider to update now. + */ + public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN + + ".UPDATE_NOW"; } diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java index 4fbe16233..1d9b9991e 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.utils.DebugLogUtils; import java.io.File; import java.io.FileNotFoundException; @@ -53,7 +54,6 @@ public final class DictionaryProvider extends ContentProvider { private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt"; private static final String QUERY_PARAMETER_TRUE = "true"; private static final String QUERY_PARAMETER_DELETE_RESULT = "result"; - private static final String QUERY_PARAMETER_SUCCESS = "success"; private static final String QUERY_PARAMETER_FAILURE = "failure"; public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol"; private static final int NO_MATCH = 0; @@ -219,7 +219,7 @@ public final class DictionaryProvider extends ContentProvider { @Override public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { - Utils.l("Uri =", uri); + DebugLogUtils.l("Uri =", uri); PrivateLog.log("Query : " + uri); final String clientId = getClientId(uri); final int match = matchUri(uri); @@ -227,7 +227,7 @@ public final class DictionaryProvider extends ContentProvider { case DICTIONARY_V1_WHOLE_LIST: case DICTIONARY_V2_WHOLE_LIST: final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId); - Utils.l("List of dictionaries with count", c.getCount()); + DebugLogUtils.l("List of dictionaries with count", c.getCount()); PrivateLog.log("Returned a list of " + c.getCount() + " items"); return c; case DICTIONARY_V2_DICT_INFO: diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java index 46bb5543a..41916b614 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -22,14 +22,15 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; -import android.text.format.DateUtils; -import android.util.Log; import android.widget.Toast; import com.android.inputmethod.latin.R; import java.util.Locale; import java.util.Random; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * Service that handles background tasks for the dictionary provider. @@ -49,17 +50,10 @@ import java.util.Random; * to access, and mark the current state as such. */ public final class DictionaryService extends Service { - private static final String TAG = DictionaryService.class.getName(); - /** * The package name, to use in the intent actions. */ - private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin"; - - /** - * The action of the intent to tell the dictionary provider to update now. - */ - private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW"; + private static final String PACKAGE_NAME = "com.android.inputmethod.latin"; /** * The action of the date changing, used to schedule a periodic freshness check @@ -82,36 +76,42 @@ 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 = 4 * DateUtils.DAY_IN_MILLIS; + private static final long UPDATE_FREQUENCY = 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 = 6 * ((int)AlarmManager.INTERVAL_HOUR); + private static final int MAX_ALARM_DELAY = (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 = 14 * DateUtils.DAY_IN_MILLIS; - - /** - * The last seen start Id. This must be stored because we must only call stopSelfResult() with - * the last seen Id, or the service won't stop. - */ - private int mLastSeenStartId; + private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14); /** - * The command count. We need this because we need to not call stopSelfResult() while we still - * have commands running. + * An executor that serializes tasks given to it. */ - private int mCommandCount; + private ThreadPoolExecutor mExecutor; + private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15; @Override public void onCreate() { - mLastSeenStartId = 0; - mCommandCount = 0; + // By default, a thread pool executor does not timeout its core threads, so it will + // never kill them when there isn't any work to do any more. That would mean the service + // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow + // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing + // the process to be reclaimed by the system any time after that if it's not doing + // anything else. + // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the + // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method, + // so we can't use that. + mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, + WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */, + TimeUnit.SECONDS /* unit for keepAliveTime */, + new LinkedBlockingQueue<Runnable>() /* workQueue */); + mExecutor.allowCoreThreadTimeOut(true); } @Override @@ -136,33 +136,35 @@ public final class DictionaryService extends Service { * - Handle a finished download. * This executes the actions that must be taken after a file (metadata or dictionary data * has been downloaded (or failed to download). + * The commands that can be spun an another thread will be executed serially, in order, on + * a worker thread that is created on demand and terminates after a short while if there isn't + * any work left to do. */ @Override public synchronized int onStartCommand(final Intent intent, final int flags, final int startId) { final DictionaryService self = this; - mLastSeenStartId = startId; - mCommandCount += 1; 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))); } else { - // If it's a command that does not require UI, create a thread to do the work - // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands. - new Thread("updateOrFinishDownload") { + // 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 + // if necessary, or reuse a thread that has become idle as appropriate. + // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another + // thread. + mExecutor.submit(new Runnable() { @Override public void run() { dispatchBroadcast(self, intent); - synchronized(self) { - if (--mCommandCount <= 0) { - if (!stopSelfResult(mLastSeenStartId)) { - Log.e(TAG, "Can't stop ourselves"); - } - } - } + // Since calls to onStartCommand are serialized, the submissions to the executor + // are serialized. That means we are guaranteed to call the stopSelfResult() + // in the same order that we got them, so we don't need to take care of the + // order. + stopSelfResult(startId); } - }.start(); + }); } return Service.START_REDELIVER_INTENT; } @@ -173,9 +175,9 @@ public final class DictionaryService extends Service { // at midnight local time, but it may happen if the user changes the date // by hand or something similar happens. checkTimeAndMaybeSetupUpdateAlarm(context); - } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { + } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { // Intent to trigger an update now. - UpdateHandler.update(context, false); + UpdateHandler.tryUpdate(context, false); } else { UpdateHandler.downloadFinished(context, intent); } @@ -196,7 +198,7 @@ public final class DictionaryService extends Service { // 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 Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION); + final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); @@ -226,7 +228,7 @@ public final class DictionaryService extends Service { */ public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return; - UpdateHandler.update(context, false); + UpdateHandler.tryUpdate(context, false); } /** diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java index 618322357..7bbd041e7 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java @@ -30,6 +30,7 @@ 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.animation.AnimationUtils; @@ -104,9 +105,16 @@ public final class DictionarySettingsFragment extends PreferenceFragment @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { - mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now); - mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - refreshNetworkState(); + 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(); + } } @Override @@ -222,7 +230,9 @@ public final class DictionarySettingsFragment extends PreferenceFragment refreshNetworkState(); removeAnyDictSettings(prefScreen); + int i = 0; for (Preference preference : prefList) { + preference.setOrder(i++); prefScreen.addPreference(preference); } } @@ -302,7 +312,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment // the description. final String key = matchLevelString + "." + description + "." + wordlistId; final WordListPreference existingPref = prefMap.get(key); - if (null == existingPref || hasPriority(status, existingPref.mStatus)) { + if (null == existingPref || existingPref.hasPriorityOver(status)) { final WordListPreference oldPreference = mCurrentPreferenceMap.get(key); final WordListPreference pref; if (null != oldPreference @@ -313,7 +323,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment // need to be the same, others have been tested through the key of the // map. Also, status may differ so we don't want to use #equals() here. pref = oldPreference; - pref.mStatus = status; + pref.setStatus(status); } else { // Otherwise, discard it and create a new one instead. pref = new WordListPreference(activity, mDictionaryListInterfaceState, @@ -329,18 +339,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment } } - /** - * Finds out if a given status has priority over another for display order. - * - * @param newStatus - * @param oldStatus - * @return whether newStatus has priority over oldStatus. - */ - private static boolean hasPriority(final int newStatus, final int oldStatus) { - // Both of these should be one of MetadataDbHelper.STATUS_* - return newStatus > oldStatus; - } - @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -363,7 +361,12 @@ public final class DictionarySettingsFragment extends PreferenceFragment new Thread("updateByHand") { @Override public void run() { - UpdateHandler.update(activity, true); + // We call tryUpdate(), which returns whether we could successfully start an update. + // If we couldn't, we'll never receive the end callback, so we stop the loading + // animation and return to the previous screen. + if (!UpdateHandler.tryUpdate(activity, true)) { + stopLoadingAnimation(); + } } }.start(); } @@ -378,7 +381,9 @@ public final class DictionarySettingsFragment extends PreferenceFragment private void startLoadingAnimation() { mLoadingView.setVisibility(View.VISIBLE); getView().setVisibility(View.GONE); - mUpdateNowMenu.setTitle(R.string.cancel); + // We come here when the menu element is pressed so presumably it can't be null. But + // better safe than sorry. + if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel); } private void stopLoadingAnimation() { diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java index d8aa33bb8..859f1b35b 100644 --- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java @@ -21,8 +21,6 @@ import android.content.Context; import android.content.Intent; public final class EventHandler extends BroadcastReceiver { - private static final String TAG = EventHandler.class.getName(); - /** * Receives a intent broadcast. * diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java index d0e8446f5..77f67b8a3 100644 --- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java +++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java @@ -144,7 +144,7 @@ public final class LocaleUtils { 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("%02d", MATCH_LEVEL_MAX - matchLevel); + return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); } /** diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java index 03ed267c3..ff5aba6d8 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java @@ -25,6 +25,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.DebugLogUtils; import java.io.File; import java.util.ArrayList; @@ -36,8 +37,6 @@ import java.util.TreeMap; * Various helper functions for the state database */ public class MetadataDbHelper extends SQLiteOpenHelper { - - @SuppressWarnings("unused") private static final String TAG = MetadataDbHelper.class.getSimpleName(); // This was the initial release version of the database. It should never be @@ -200,6 +199,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { final ContentValues defaultMetadataValues = new ContentValues(); defaultMetadataValues.put(CLIENT_CLIENT_ID_COLUMN, ""); defaultMetadataValues.put(CLIENT_METADATA_URI_COLUMN, defaultMetadataUri); + defaultMetadataValues.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID); db.insert(CLIENT_TABLE_NAME, null, defaultMetadataValues); } } @@ -359,21 +359,21 @@ public class MetadataDbHelper extends SQLiteOpenHelper { } /** - * Get the metadata download ID for a client ID. + * Get the metadata download ID for a metadata URI. * - * This will retrieve the download ID for the metadata file associated with a client ID. - * If there is no metadata download in progress for this client, it will return NOT_AN_ID. + * This will retrieve the download ID for the metadata file that has the passed URI. + * If this URI is not being downloaded right now, it will return NOT_AN_ID. * * @param context a context instance to open the database on - * @param clientId the client ID to retrieve the metadata download ID of + * @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 */ - public static long getMetadataDownloadIdForClient(final Context context, - final String clientId) { + public static long getMetadataDownloadIdForURI(final Context context, + final String uri) { SQLiteDatabase defaultDb = getDb(context, null); final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, new String[] { CLIENT_PENDINGID_COLUMN }, - CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId }, + CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri }, null, null, null, null); try { if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID; @@ -437,37 +437,37 @@ public class MetadataDbHelper extends SQLiteOpenHelper { */ public static ContentValues completeWithDefaultValues(final ContentValues result) throws BadFormatException { - if (!result.containsKey(WORDLISTID_COLUMN) || !result.containsKey(LOCALE_COLUMN)) { + if (null == result.get(WORDLISTID_COLUMN) || null == result.get(LOCALE_COLUMN)) { throw new BadFormatException(); } // 0 for the pending id, because there is none - if (!result.containsKey(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0); + if (null == result.get(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0); // This is a binary blob of a dictionary - if (!result.containsKey(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK); + if (null == result.get(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK); // This word list is unknown, but it's present, else we wouldn't be here, so INSTALLED - if (!result.containsKey(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED); + if (null == result.get(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED); // No description unless specified, because we can't guess it - if (!result.containsKey(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, ""); + if (null == result.get(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, ""); // File name - this is an asset, so it works as an already deleted file. // hence, we need to supply a non-existent file name. Anything will // do as long as it returns false when tested with File#exist(), and // the empty string does not, so it's set to "_". - if (!result.containsKey(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_"); + if (null == result.get(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_"); // No remote file name : this can't be downloaded. Unless specified. - if (!result.containsKey(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, ""); + if (null == result.get(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, ""); // 0 for the update date : 1970/1/1. Unless specified. - if (!result.containsKey(DATE_COLUMN)) result.put(DATE_COLUMN, 0); + if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0); // Checksum unknown unless specified - if (!result.containsKey(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, ""); + if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, ""); // No filesize unless specified - if (!result.containsKey(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0); + if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0); // Smallest possible version unless specified - if (!result.containsKey(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1); + if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1); // Assume current format unless specified - if (!result.containsKey(FORMATVERSION_COLUMN)) + if (null == result.get(FORMATVERSION_COLUMN)) result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION); // No flags unless specified - if (!result.containsKey(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0); + if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0); return result; } @@ -572,7 +572,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper { * If several clients use the same metadata URL, we know to only download it once, and * dispatch the update process across all relevant clients when the download ends. This means * several clients may share a single download ID if they share a metadata URI. - * The dispatching is done in {@link UpdateHandler#downloadFinished(Context, Intent)}, which + * The dispatching is done in + * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which * finds out about the list of relevant clients by calling this method. * * @param context a context instance to open the databases @@ -773,15 +774,17 @@ public class MetadataDbHelper extends SQLiteOpenHelper { if (TextUtils.isEmpty(valuesClientId) || null == valuesMetadataUri || null == valuesMetadataAdditionalId) { // We need all these columns to be filled in - Utils.l("Missing parameter for updateClientInfo"); + DebugLogUtils.l("Missing parameter for updateClientInfo"); return; } if (!clientId.equals(valuesClientId)) { // Mismatch! The client violates the protocol. - Utils.l("Received an updateClientInfo request for ", clientId, " but the values " - + "contain a different ID : ", valuesClientId); + DebugLogUtils.l("Received an updateClientInfo request for ", clientId, + " but the values " + "contain a different ID : ", valuesClientId); return; } + // Default value for a pending ID is NOT_AN_ID + values.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID); final SQLiteDatabase defaultDb = getDb(context, ""); if (-1 == defaultDb.insert(CLIENT_TABLE_NAME, null, values)) { defaultDb.update(CLIENT_TABLE_NAME, values, @@ -848,7 +851,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { final ContentValues r) { switch (r.getAsInteger(TYPE_COLUMN)) { case TYPE_BULK: - Utils.l("Ended processing a wordlist"); + DebugLogUtils.l("Ended processing a wordlist"); // Updating a bulk word list is a three-step operation: // - Add the new entry to the table // - Remove the old entry from the table @@ -863,17 +866,20 @@ public class MetadataDbHelper extends SQLiteOpenHelper { r.getAsString(WORDLISTID_COLUMN), Integer.toString(STATUS_INSTALLED) }, null, null, null); - if (c.moveToFirst()) { - // There should never be more than one file, but if there are, it's a bug - // and we should remove them all. I think it might happen if the power of the - // phone is suddenly cut during an update. - final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN); - do { - Utils.l("Setting for removal", c.getString(filenameIndex)); - filenames.add(c.getString(filenameIndex)); - } while (c.moveToNext()); + try { + if (c.moveToFirst()) { + // There should never be more than one file, but if there are, it's a bug + // and we should remove them all. I think it might happen if the power of + // the phone is suddenly cut during an update. + final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN); + do { + DebugLogUtils.l("Setting for removal", c.getString(filenameIndex)); + filenames.add(c.getString(filenameIndex)); + } while (c.moveToNext()); + } + } finally { + c.close(); } - r.put(STATUS_COLUMN, STATUS_INSTALLED); db.beginTransactionNonExclusive(); // Delete all old entries. There should never be any stalled entries, but if diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java index 3f917f13f..0e7c3bb7e 100644 --- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java @@ -38,6 +38,8 @@ import android.util.Log; import com.android.inputmethod.compat.ConnectivityManagerCompatUtils; import com.android.inputmethod.compat.DownloadManagerCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.ApplicationUtils; +import com.android.inputmethod.latin.utils.DebugLogUtils; import java.io.File; import java.io.FileInputStream; @@ -171,25 +173,27 @@ public final class UpdateHandler { * Download latest metadata from the server through DownloadManager for all known clients * @param context The context for retrieving resources * @param updateNow Whether we should update NOW, or respect bandwidth policies + * @return true if an update successfully started, false otherwise. */ - public static void update(final Context context, final boolean updateNow) { + public static boolean tryUpdate(final Context context, final boolean updateNow) { // TODO: loop through all clients instead of only doing the default one. final TreeSet<String> uris = new TreeSet<String>(); final Cursor cursor = MetadataDbHelper.queryClientIds(context); - if (null == cursor) return; + if (null == cursor) return false; try { - if (!cursor.moveToFirst()) return; + if (!cursor.moveToFirst()) return false; do { final String clientId = cursor.getString(0); final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); - PrivateLog.log("Update for clientId " + Utils.s(clientId)); - Utils.l("Update for clientId", clientId, " which uses URI ", metadataUri); + PrivateLog.log("Update for clientId " + DebugLogUtils.s(clientId)); + DebugLogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri); uris.add(metadataUri); } while (cursor.moveToNext()); } finally { cursor.close(); } + boolean started = false; for (final String metadataUri : uris) { if (!TextUtils.isEmpty(metadataUri)) { // If the metadata URI is empty, that means we should never update it at all. @@ -198,8 +202,10 @@ public final class UpdateHandler { // is a bug and it happens anyway, doing nothing is the right thing to do. // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}. updateClientsWithMetadataUri(context, updateNow, metadataUri); + started = true; } } + return started; } /** @@ -211,14 +217,14 @@ public final class UpdateHandler { */ private static void updateClientsWithMetadataUri(final Context context, final boolean updateNow, final String metadataUri) { - PrivateLog.log("Update for metadata URI " + Utils.s(metadataUri)); + PrivateLog.log("Update for metadata URI " + DebugLogUtils.s(metadataUri)); // Adding a disambiguator to circumvent a bug in older versions of DownloadManager. // DownloadManager also stupidly cuts the extension to replace with its own that it // gets from the content-type. We need to circumvent this. final String disambiguator = "#" + System.currentTimeMillis() - + com.android.inputmethod.latin.Utils.getVersionName(context) + ".json"; + + ApplicationUtils.getVersionName(context) + ".json"; final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator)); - Utils.l("Request =", metadataRequest); + DebugLogUtils.l("Request =", metadataRequest); final Resources res = context.getResources(); // By default, download over roaming is allowed and all network types are allowed too. @@ -254,7 +260,7 @@ public final class UpdateHandler { final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(metadataRequest); - Utils.l("Metadata download requested with id", downloadId); + DebugLogUtils.l("Metadata download requested with id", downloadId); // If there is already 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 @@ -266,23 +272,22 @@ public final class UpdateHandler { } /** - * Cancels a pending update, if there is one. + * Cancels downloading a file, if there is one for this URI. * - * If none, this is a no-op. + * 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 clientId the id of the client + * @param metadataUri the URI to cancel * @param manager an instance of DownloadManager */ private static void cancelUpdateWithDownloadManager(final Context context, - final String clientId, final DownloadManager manager) { + final String metadataUri, final DownloadManager manager) { synchronized (sSharedIdProtector) { final long metadataDownloadId = - MetadataDbHelper.getMetadataDownloadIdForClient(context, clientId); + MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri); if (NOT_AN_ID == metadataDownloadId) return; manager.remove(metadataDownloadId); - writeMetadataDownloadId(context, - MetadataDbHelper.getMetadataUriAsString(context, clientId), NOT_AN_ID); + writeMetadataDownloadId(context, metadataUri, NOT_AN_ID); } // Consider a cancellation as a failure. As such, inform listeners that the download // has failed. @@ -292,10 +297,10 @@ public final class UpdateHandler { } /** - * Cancels a pending update, if there is one. + * Cancels a pending update for this client, if there is one. * - * If there is none, this is a no-op. This is a helper method that gets the - * download manager service. + * If we are not currently updating metadata for this client, this is a no-op. This is a helper + * method that gets the download manager service and the metadata URI for this client. * * @param context the context, to get an instance of DownloadManager * @param clientId the ID of the client we want to cancel the update of @@ -303,7 +308,8 @@ public final class UpdateHandler { public static void cancelUpdate(final Context context, final String clientId) { final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - if (null != manager) cancelUpdateWithDownloadManager(context, clientId, manager); + final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); + if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager); } /** @@ -326,11 +332,11 @@ public final class UpdateHandler { */ public static long registerDownloadRequest(final DownloadManager manager, final Request request, final SQLiteDatabase db, final String id, final int version) { - Utils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version); + DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version); final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(request); - Utils.l("Download requested with id", downloadId); + DebugLogUtils.l("Download requested with id", downloadId); MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId); } return downloadId; @@ -416,7 +422,7 @@ public final class UpdateHandler { // Get and check the ID of the file that was downloaded final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID); PrivateLog.log("Download finished with id " + fileId); - Utils.l("DownloadFinished with id", fileId); + DebugLogUtils.l("DownloadFinished with id", fileId); if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore final DownloadManager manager = @@ -426,7 +432,7 @@ public final class UpdateHandler { final ArrayList<DownloadRecord> recordList = getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo); if (null == recordList) return; // It was someone else's download. - Utils.l("Received result for download ", fileId); + DebugLogUtils.l("Received result for download ", fileId); // TODO: handle gracefully a null pointer here. This is practically impossible because // we come here only when DownloadManager explicitly called us when it ended a @@ -503,7 +509,7 @@ public final class UpdateHandler { private static void publishUpdateCycleCompletedEvent(final Context context) { // Even if this is not successful, we have to publish the new state. PrivateLog.log("Publishing update cycle completed event"); - Utils.l("Publishing update cycle completed event"); + DebugLogUtils.l("Publishing update cycle completed event"); for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { listener.updateCycleCompleted(); } @@ -517,12 +523,12 @@ public final class UpdateHandler { // {@link handleWordList(Context,InputStream,ContentValues)}. // Handle the downloaded file according to its type if (downloadRecord.isMetadata()) { - Utils.l("Data D/L'd is metadata for", downloadRecord.mClientId); + DebugLogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId); // #handleMetadata() closes its InputStream argument handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream( manager.openDownloadedFile(fileId)), downloadRecord.mClientId); } else { - Utils.l("Data D/L'd is a word list"); + DebugLogUtils.l("Data D/L'd is a word list"); final int wordListStatus = downloadRecord.mAttributes.getAsInteger( MetadataDbHelper.STATUS_COLUMN); if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) { @@ -582,7 +588,7 @@ public final class UpdateHandler { */ private static void handleMetadata(final Context context, final InputStream stream, final String clientId) throws IOException, BadFormatException { - Utils.l("Entering handleMetadata"); + DebugLogUtils.l("Entering handleMetadata"); final List<WordListMetadata> newMetadata; final InputStreamReader reader = new InputStreamReader(stream); try { @@ -592,7 +598,7 @@ public final class UpdateHandler { reader.close(); } - Utils.l("Downloaded metadata :", newMetadata); + DebugLogUtils.l("Downloaded metadata :", newMetadata); PrivateLog.log("Downloaded metadata\n" + newMetadata); final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata); @@ -617,7 +623,7 @@ public final class UpdateHandler { // DownloadManager does not have the ability to put the file directly where we want // it, so we had it download to a temporary place. Now we move it. It will be deleted // automatically by DownloadManager. - Utils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( + DebugLogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId); PrivateLog.log("Downloaded a new word list with description : " + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN) @@ -676,9 +682,9 @@ public final class UpdateHandler { */ private static void copyFile(final InputStream in, final OutputStream out) throws IOException { - Utils.l("Copying files"); + DebugLogUtils.l("Copying files"); if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) { - Utils.l("Not the right types"); + DebugLogUtils.l("Not the right types"); copyFileFallback(in, out); } else { try { @@ -687,7 +693,7 @@ public final class UpdateHandler { sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel); } catch (IOException e) { // Can't work with channels, or something went wrong. Copy by hand. - Utils.l("Won't work"); + DebugLogUtils.l("Won't work"); copyFileFallback(in, out); } } @@ -702,7 +708,7 @@ public final class UpdateHandler { */ private static void copyFileFallback(final InputStream in, final OutputStream out) throws IOException { - Utils.l("Falling back to slow copy"); + DebugLogUtils.l("Falling back to slow copy"); final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE]; for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer)) out.write(buffer, 0, readBytes); @@ -717,10 +723,10 @@ public final class UpdateHandler { */ private static String getTempFileName(final Context context, final String locale) throws IOException { - Utils.l("Entering openTempFileOutput"); + DebugLogUtils.l("Entering openTempFileOutput"); final File dir = context.getFilesDir(); final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir); - Utils.l("File name is", f.getName()); + DebugLogUtils.l("File name is", f.getName()); return f.getName(); } @@ -741,7 +747,7 @@ public final class UpdateHandler { final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) { final ActionBatch actions = new ActionBatch(); // Upgrade existing word lists - Utils.l("Comparing dictionaries"); + DebugLogUtils.l("Comparing dictionaries"); final Set<String> wordListIds = new TreeSet<String>(); // TODO: Can these be null? if (null == from) from = new ArrayList<WordListMetadata>(); @@ -756,7 +762,7 @@ public final class UpdateHandler { final WordListMetadata newInfo = null == metadataInfo || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION ? null : metadataInfo; - Utils.l("Considering updating ", id, "currentInfo =", currentInfo); + DebugLogUtils.l("Considering updating ", id, "currentInfo =", currentInfo); if (null == currentInfo && null == newInfo) { // This may happen if a new word list appeared that we can't handle. @@ -767,7 +773,7 @@ public final class UpdateHandler { // We may come here if there is a new word list that we can't handle. Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format" + " version " + metadataInfo.mFormatVersion + " and the maximum version" - + "we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION); + + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION); } continue; } else if (null == currentInfo) { diff --git a/java/src/com/android/inputmethod/dictionarypack/Utils.java b/java/src/com/android/inputmethod/dictionarypack/Utils.java deleted file mode 100644 index c4a42dbbf..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/Utils.java +++ /dev/null @@ -1,104 +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.util.Log; - -/** - * A class for various utility methods, especially debugging. - */ -public final class Utils { - private final static String TAG = Utils.class.getSimpleName() + ":DEBUG --"; - private final static boolean DEBUG = DictionaryProvider.DEBUG; - - /** - * Calls .toString() on its non-null argument or returns "null" - * @param o the object to convert to a string - * @return the result of .toString() or null - */ - public static String s(final Object o) { - return null == o ? "null" : o.toString(); - } - - /** - * Get the string representation of the current stack trace, for debugging purposes. - * @return a readable, carriage-return-separated string for the current stack trace. - */ - public static String getStackTrace() { - final StringBuilder sb = new StringBuilder(); - try { - throw new RuntimeException(); - } catch (RuntimeException e) { - StackTraceElement[] frames = e.getStackTrace(); - // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - } - return sb.toString(); - } - - /** - * Get the stack trace contained in an exception as a human-readable string. - * @param e the exception - * @return the human-readable stack trace - */ - public static String getStackTrace(final Exception e) { - final StringBuilder sb = new StringBuilder(); - final StackTraceElement[] frames = e.getStackTrace(); - for (int j = 0; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - return sb.toString(); - } - - /** - * Helper log method to ease null-checks and adding spaces. - * - * This sends all arguments to the log, separated by spaces. Any null argument is converted - * to the "null" string. It uses a very visible tag and log level for debugging purposes. - * - * @param args the stuff to send to the log - */ - public static void l(final Object... args) { - if (!DEBUG) return; - final StringBuilder sb = new StringBuilder(); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - Log.e(TAG, sb.toString()); - } - - /** - * Helper log method to put stuff in red. - * - * This does the same as #l but prints in red - * - * @param args the stuff to send to the log - */ - public static void r(final Object... args) { - if (!DEBUG) return; - final StringBuilder sb = new StringBuilder("\u001B[31m"); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - sb.append("\u001B[0m"); - Log.e(TAG, sb.toString()); - } -} diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java index 451a0fb82..ba1fce1a8 100644 --- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java +++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java @@ -61,7 +61,7 @@ public final class WordListPreference extends Preference { public final Locale mLocale; public final String mDescription; // The status - public int mStatus; + private int mStatus; // The size of the dictionary file private final int mFilesize; @@ -92,12 +92,25 @@ public final class WordListPreference extends Preference { setKey(wordlistId); } - private void setStatus(final int status) { + public void setStatus(final int status) { if (status == mStatus) return; mStatus = status; setSummary(getSummary(status)); } + @Override + public View onCreateView(final ViewGroup parent) { + final View orphanedView = mInterfaceState.findFirstOrphanedView(); + if (null != orphanedView) return orphanedView; // Will be sent to onBindView + final View newView = super.onCreateView(parent); + return mInterfaceState.addToCacheAndReturnView(newView); + } + + public boolean hasPriorityOver(final int otherPrefStatus) { + // Both of these should be one of MetadataDbHelper.STATUS_* + return mStatus > otherPrefStatus; + } + private String getSummary(final int status) { switch (status) { // If we are deleting the word list, for the user it's like it's already deleted. @@ -209,6 +222,9 @@ public final class WordListPreference extends Preference { 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); if (mInterfaceState.isOpen(mWordlistId)) { // The button is open. final int previousStatus = mInterfaceState.getStatus(mWordlistId); |