| /* |
| * 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.app.DownloadManager; |
| import android.app.DownloadManager.Request; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.net.Uri; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.inputmethod.compat.DownloadManagerCompatUtils; |
| import com.android.inputmethod.latin.R; |
| |
| import java.util.LinkedList; |
| import java.util.Queue; |
| |
| /** |
| * Object representing an upgrade from one state to another. |
| * |
| * This implementation basically encapsulates a list of Runnable objects. In the future |
| * it may manage dependencies between them. Concretely, it does not use Runnable because the |
| * actions need an argument. |
| */ |
| /* |
| |
| The state of a word list follows the following scheme. |
| |
| | ^ |
| MakeAvailable | |
| | .------------Forget--------' |
| V | |
| STATUS_AVAILABLE <-------------------------. |
| | | |
| StartDownloadAction FinishDeleteAction |
| | | |
| V | |
| STATUS_DOWNLOADING EnableAction-- STATUS_DELETING |
| | | ^ |
| InstallAfterDownloadAction | | |
| | .---------------' StartDeleteAction |
| | | | |
| V V | |
| STATUS_INSTALLED <--EnableAction-- STATUS_DISABLED |
| --DisableAction--> |
| |
| It may also be possible that DisableAction or StartDeleteAction or |
| DownloadAction run when the file is still downloading. This cancels |
| the download and returns to STATUS_AVAILABLE. |
| Also, an UpdateDataAction may apply in any state. It does not affect |
| the state in any way (nor type, local filename, id or version) but |
| may update other attributes like description or remote filename. |
| |
| Forget is an DB maintenance action that removes the entry if it is not installed or disabled. |
| This happens when the word list information disappeared from the server, or when a new version |
| is available and we should forget about the old one. |
| */ |
| public final class ActionBatch { |
| /** |
| * A piece of update. |
| * |
| * Action is basically like a Runnable that takes an argument. |
| */ |
| public interface Action { |
| /** |
| * Execute this action NOW. |
| * @param context the context to get system services, resources, databases |
| */ |
| public void execute(final Context context); |
| } |
| |
| /** |
| * An action that starts downloading an available word list. |
| */ |
| public static final class StartDownloadAction implements Action { |
| static final String TAG = "DictionaryProvider:" + StartDownloadAction.class.getSimpleName(); |
| |
| private final String mClientId; |
| // The data to download. May not be null. |
| final WordListMetadata mWordList; |
| final boolean mForceStartNow; |
| public StartDownloadAction(final String clientId, |
| final WordListMetadata wordList, final boolean forceStartNow) { |
| Utils.l("New download action for client ", clientId, " : ", wordList); |
| mClientId = clientId; |
| mWordList = wordList; |
| mForceStartNow = forceStartNow; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "UpdateAction with a null parameter!"); |
| return; |
| } |
| Utils.l("Downloading word list"); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| final DownloadManager manager = |
| (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); |
| if (MetadataDbHelper.STATUS_DOWNLOADING == status) { |
| // The word list is still downloading. Cancel the download and revert the |
| // word list status to "available". |
| if (null != manager) { |
| // DownloadManager is disabled (or not installed?). We can't cancel - there |
| // is nothing we can do. We still need to mark the entry as available. |
| manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN)); |
| } |
| MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); |
| } else if (MetadataDbHelper.STATUS_AVAILABLE != status) { |
| // Should never happen |
| Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status |
| + " for an upgrade action. Fall back to download."); |
| } |
| // Download it. |
| Utils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); |
| |
| // TODO: if DownloadManager is disabled or not installed, download by ourselves |
| if (null == manager) return; |
| |
| // This is an upgraded word list: we should download it. |
| // 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) + ".dict"; |
| final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator); |
| final Request request = new Request(uri); |
| |
| final Resources res = context.getResources(); |
| if (!mForceStartNow) { |
| if (DownloadManagerCompatUtils.hasSetAllowedOverMetered()) { |
| final boolean allowOverMetered; |
| switch (UpdateHandler.getDownloadOverMeteredSetting(context)) { |
| case UpdateHandler.DOWNLOAD_OVER_METERED_DISALLOWED: |
| // User said no: don't allow. |
| allowOverMetered = false; |
| break; |
| case UpdateHandler.DOWNLOAD_OVER_METERED_ALLOWED: |
| // User said yes: allow. |
| allowOverMetered = true; |
| break; |
| default: // UpdateHandler.DOWNLOAD_OVER_METERED_SETTING_UNKNOWN |
| // Don't know: use the default value from configuration. |
| allowOverMetered = res.getBoolean(R.bool.allow_over_metered); |
| } |
| DownloadManagerCompatUtils.setAllowedOverMetered(request, allowOverMetered); |
| } else { |
| request.setAllowedNetworkTypes(Request.NETWORK_WIFI); |
| } |
| request.setAllowedOverRoaming(res.getBoolean(R.bool.allow_over_roaming)); |
| } // if mForceStartNow, then allow all network types and roaming, which is the default. |
| request.setTitle(mWordList.mDescription); |
| request.setNotificationVisibility( |
| res.getBoolean(R.bool.display_notification_for_auto_update) |
| ? Request.VISIBILITY_VISIBLE : Request.VISIBILITY_HIDDEN); |
| request.setVisibleInDownloadsUi( |
| res.getBoolean(R.bool.dict_downloads_visible_in_download_UI)); |
| |
| final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db, |
| mWordList.mId, mWordList.mVersion); |
| Utils.l("Starting download of", uri, "with id", downloadId); |
| PrivateLog.log("Starting download of " + uri + ", id : " + downloadId); |
| } |
| } |
| |
| /** |
| * An action that updates the database to reflect the status of a newly installed word list. |
| */ |
| public static final class InstallAfterDownloadAction implements Action { |
| static final String TAG = "DictionaryProvider:" |
| + InstallAfterDownloadAction.class.getSimpleName(); |
| private final String mClientId; |
| // The state to upgrade from. May not be null. |
| final ContentValues mWordListValues; |
| |
| public InstallAfterDownloadAction(final String clientId, |
| final ContentValues wordListValues) { |
| Utils.l("New InstallAfterDownloadAction for client ", clientId, " : ", wordListValues); |
| mClientId = clientId; |
| mWordListValues = wordListValues; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordListValues) { |
| Log.e(TAG, "InstallAfterDownloadAction with a null parameter!"); |
| return; |
| } |
| final int status = mWordListValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (MetadataDbHelper.STATUS_DOWNLOADING != status) { |
| final String id = mWordListValues.getAsString(MetadataDbHelper.WORDLISTID_COLUMN); |
| Log.e(TAG, "Unexpected state of the word list '" + id + "' : " + status |
| + " for an InstallAfterDownload action. Bailing out."); |
| return; |
| } |
| Utils.l("Setting word list as installed"); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues); |
| } |
| } |
| |
| /** |
| * An action that enables an existing word list. |
| */ |
| public static final class EnableAction implements Action { |
| static final String TAG = "DictionaryProvider:" + EnableAction.class.getSimpleName(); |
| private final String mClientId; |
| // The state to upgrade from. May not be null. |
| final WordListMetadata mWordList; |
| |
| public EnableAction(final String clientId, final WordListMetadata wordList) { |
| Utils.l("New EnableAction for client ", clientId, " : ", wordList); |
| mClientId = clientId; |
| mWordList = wordList; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { |
| Log.e(TAG, "EnableAction with a null parameter!"); |
| return; |
| } |
| Utils.l("Enabling word list"); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (MetadataDbHelper.STATUS_DISABLED != status |
| && MetadataDbHelper.STATUS_DELETING != status) { |
| Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + " : " + status |
| + " for an enable action. Cancelling"); |
| return; |
| } |
| MetadataDbHelper.markEntryAsEnabled(db, mWordList.mId, mWordList.mVersion); |
| } |
| } |
| |
| /** |
| * An action that disables a word list. |
| */ |
| public static final class DisableAction implements Action { |
| static final String TAG = "DictionaryProvider:" + DisableAction.class.getSimpleName(); |
| private final String mClientId; |
| // 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); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "DisableAction with a null word list!"); |
| return; |
| } |
| Utils.l("Disabling word list : " + mWordList); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (MetadataDbHelper.STATUS_INSTALLED == status) { |
| // Disabling an installed word list |
| MetadataDbHelper.markEntryAsDisabled(db, mWordList.mId, mWordList.mVersion); |
| } else { |
| if (MetadataDbHelper.STATUS_DOWNLOADING != status) { |
| Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " |
| + status + " for a disable action. Fall back to marking as available."); |
| } |
| // The word list is still downloading. Cancel the download and revert the |
| // word list status to "available". |
| final DownloadManager manager = |
| (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); |
| if (null != manager) { |
| // If we can't cancel the download because DownloadManager is not available, |
| // we still need to mark the entry as available. |
| manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN)); |
| } |
| MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); |
| } |
| } |
| } |
| |
| /** |
| * An action that makes a word list available. |
| */ |
| public static final class MakeAvailableAction implements Action { |
| static final String TAG = "DictionaryProvider:" + MakeAvailableAction.class.getSimpleName(); |
| private final String mClientId; |
| // 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); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "MakeAvailableAction with a null word list!"); |
| return; |
| } |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| if (null != MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion)) { |
| 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); |
| // 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, |
| MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_AVAILABLE, |
| mWordList.mId, mWordList.mLocale, mWordList.mDescription, |
| null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename, |
| mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mChecksum, |
| 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); |
| } |
| } |
| |
| /** |
| * An action that marks a word list as pre-installed. |
| * |
| * This is almost the same as MakeAvailableAction, as it only inserts a line with parameters |
| * received from outside. |
| * Unlike MakeAvailableAction, the parameters are not received from a downloaded metadata file |
| * but from the client directly; it marks a word list as being "installed" and not "available". |
| * It also explicitly sets the filename to the empty string, so that we don't try to open |
| * it on our side. |
| */ |
| public static final class MarkPreInstalledAction implements Action { |
| static final String TAG = "DictionaryProvider:" |
| + MarkPreInstalledAction.class.getSimpleName(); |
| private final String mClientId; |
| // 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); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "MarkPreInstalledAction with a null word list!"); |
| return; |
| } |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| if (null != MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion)) { |
| 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); |
| // 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. |
| final ContentValues values = MetadataDbHelper.makeContentValues(0, |
| MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED, |
| mWordList.mId, mWordList.mLocale, mWordList.mDescription, |
| "", mWordList.mRemoteFilename, mWordList.mLastUpdate, |
| mWordList.mChecksum, 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); |
| } |
| } |
| |
| /** |
| * An action that updates information about a word list - description, locale etc |
| */ |
| public static final class UpdateDataAction implements Action { |
| static final String TAG = "DictionaryProvider:" + UpdateDataAction.class.getSimpleName(); |
| private final String mClientId; |
| final WordListMetadata mWordList; |
| public UpdateDataAction(final String clientId, final WordListMetadata wordlist) { |
| Utils.l("New UpdateData action for client ", clientId, " : ", wordlist); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "UpdateDataAction with a null word list!"); |
| return; |
| } |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| ContentValues oldValues = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| if (null == oldValues) { |
| 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); |
| final ContentValues values = MetadataDbHelper.makeContentValues( |
| oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN), |
| oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN), |
| oldValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN), |
| mWordList.mId, mWordList.mLocale, mWordList.mDescription, |
| oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN), |
| mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mChecksum, |
| mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion); |
| PrivateLog.log("Updating record for " + mWordList.mDescription |
| + " and locale " + mWordList.mLocale); |
| db.update(MetadataDbHelper.METADATA_TABLE_NAME, values, |
| MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " |
| + MetadataDbHelper.VERSION_COLUMN + " = ?", |
| new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); |
| } |
| } |
| |
| /** |
| * An action that deletes the metadata about a word list if possible. |
| * |
| * This is triggered when a specific word list disappeared from the server, or when a fresher |
| * word list is available and the old one was not installed. |
| * If the word list has not been installed, it's possible to delete its associated metadata. |
| * Otherwise, the settings are retained so that the user can still administrate it. |
| */ |
| public static final class ForgetAction implements Action { |
| static final String TAG = "DictionaryProvider:" + ForgetAction.class.getSimpleName(); |
| private final String mClientId; |
| // The word list to remove. May not be null. |
| final WordListMetadata mWordList; |
| final boolean mHasNewerVersion; |
| public ForgetAction(final String clientId, final WordListMetadata wordlist, |
| final boolean hasNewerVersion) { |
| Utils.l("New TryRemove action for client ", clientId, " : ", wordlist); |
| mClientId = clientId; |
| mWordList = wordlist; |
| mHasNewerVersion = hasNewerVersion; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "TryRemoveAction with a null word list!"); |
| return; |
| } |
| Utils.l("Trying to remove word list : " + mWordList); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| if (null == values) { |
| Log.e(TAG, "Trying to update the metadata of a non-existing wordlist. Cancelling."); |
| return; |
| } |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (mHasNewerVersion && MetadataDbHelper.STATUS_AVAILABLE != status) { |
| // If we have a newer version of this word list, we should be here ONLY if it was |
| // not installed - else we should be upgrading it. |
| Log.e(TAG, "Unexpected status for forgetting a word list info : " + status |
| + ", removing URL to prevent re-download"); |
| } |
| if (MetadataDbHelper.STATUS_INSTALLED == status |
| || MetadataDbHelper.STATUS_DISABLED == status |
| || MetadataDbHelper.STATUS_DELETING == status) { |
| // If it is installed or disabled, we need to mark it as deleted so that LatinIME |
| // will remove it next time it enquires for dictionaries. |
| // If it is deleting and we don't have a new version, then we have to wait until |
| // LatinIME actually has deleted it before we can remove its metadata. |
| // In both cases, remove the URI from the database since it is not supposed to |
| // be accessible any more. |
| values.put(MetadataDbHelper.REMOTE_FILENAME_COLUMN, ""); |
| values.put(MetadataDbHelper.STATUS_COLUMN, MetadataDbHelper.STATUS_DELETING); |
| db.update(MetadataDbHelper.METADATA_TABLE_NAME, values, |
| MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " |
| + MetadataDbHelper.VERSION_COLUMN + " = ?", |
| new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); |
| } else { |
| // If it's AVAILABLE or DOWNLOADING or even UNKNOWN, delete the entry. |
| db.delete(MetadataDbHelper.METADATA_TABLE_NAME, |
| MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " |
| + MetadataDbHelper.VERSION_COLUMN + " = ?", |
| new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); |
| } |
| } |
| } |
| |
| /** |
| * An action that sets the word list for deletion as soon as possible. |
| * |
| * This is triggered when the user requests deletion of a word list. This will mark it as |
| * deleted in the database, and fire an intent for Android Keyboard to take notice and |
| * reload its dictionaries right away if it is up. If it is not up now, then it will |
| * delete the actual file the next time it gets up. |
| * A file marked as deleted causes the content provider to supply a zero-sized file to |
| * Android Keyboard, which will overwrite any existing file and provide no words for this |
| * word list. This is not exactly a "deletion", since there is an actual file which takes up |
| * a few bytes on the disk, but this allows to override a default dictionary with an empty |
| * dictionary. This way, there is no need for the user to make a distinction between |
| * dictionaries installed by default and add-on dictionaries. |
| */ |
| public static final class StartDeleteAction implements Action { |
| static final String TAG = "DictionaryProvider:" + StartDeleteAction.class.getSimpleName(); |
| private final String mClientId; |
| // 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); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "StartDeleteAction with a null word list!"); |
| return; |
| } |
| Utils.l("Trying to delete word list : " + mWordList); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| if (null == values) { |
| Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling."); |
| return; |
| } |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (MetadataDbHelper.STATUS_DISABLED != status) { |
| Log.e(TAG, "Unexpected status for deleting a word list info : " + status); |
| } |
| MetadataDbHelper.markEntryAsDeleting(db, mWordList.mId, mWordList.mVersion); |
| } |
| } |
| |
| /** |
| * An action that validates a word list as deleted. |
| * |
| * This will restore the word list as available if it still is, or remove the entry if |
| * it is not any more. |
| */ |
| public static final class FinishDeleteAction implements Action { |
| static final String TAG = "DictionaryProvider:" + FinishDeleteAction.class.getSimpleName(); |
| private final String mClientId; |
| // 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); |
| mClientId = clientId; |
| mWordList = wordlist; |
| } |
| |
| @Override |
| public void execute(final Context context) { |
| if (null == mWordList) { // This should never happen |
| Log.e(TAG, "FinishDeleteAction with a null word list!"); |
| return; |
| } |
| Utils.l("Trying to delete word list : " + mWordList); |
| final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); |
| final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, |
| mWordList.mId, mWordList.mVersion); |
| if (null == values) { |
| Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling."); |
| return; |
| } |
| final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); |
| if (MetadataDbHelper.STATUS_DELETING != status) { |
| Log.e(TAG, "Unexpected status for finish-deleting a word list info : " + status); |
| } |
| final String remoteFilename = |
| values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN); |
| // If there isn't a remote filename any more, then we don't know where to get the file |
| // from any more, so we remove the entry entirely. As a matter of fact, if the file was |
| // marked DELETING but disappeared from the metadata on the server, it ended up |
| // this way. |
| if (TextUtils.isEmpty(remoteFilename)) { |
| db.delete(MetadataDbHelper.METADATA_TABLE_NAME, |
| MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND " |
| + MetadataDbHelper.VERSION_COLUMN + " = ?", |
| new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) }); |
| } else { |
| MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion); |
| } |
| } |
| } |
| |
| // An action batch consists of an ordered queue of Actions that can execute. |
| private final Queue<Action> mActions; |
| |
| public ActionBatch() { |
| mActions = new LinkedList<Action>(); |
| } |
| |
| public void add(final Action a) { |
| mActions.add(a); |
| } |
| |
| /** |
| * Append all the actions of another action batch. |
| * @param that the upgrade to merge into this one. |
| */ |
| public void append(final ActionBatch that) { |
| for (final Action a : that.mActions) { |
| add(a); |
| } |
| } |
| |
| /** |
| * Execute this batch. |
| * |
| * @param context the context for getting resources, databases, system services. |
| * @param reporter a Reporter to send errors to. |
| */ |
| public void execute(final Context context, final ProblemReporter reporter) { |
| Utils.l("Executing a batch of actions"); |
| Queue<Action> remainingActions = mActions; |
| while (!remainingActions.isEmpty()) { |
| final Action a = remainingActions.poll(); |
| try { |
| a.execute(context); |
| } catch (Exception e) { |
| if (null != reporter) |
| reporter.report(e); |
| } |
| } |
| } |
| } |