10cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/*
20cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Copyright (C) 2011 The Android Open Source Project
30cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
40cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); you may not
50cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * use this file except in compliance with the License. You may obtain a copy of
60cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the License at
70cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
80cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * http://www.apache.org/licenses/LICENSE-2.0
90cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Unless required by applicable law or agreed to in writing, software
110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * License for the specific language governing permissions and limitations under
140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the License.
150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */
160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardpackage com.android.inputmethod.dictionarypack;
180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.DownloadManager.Request;
200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.ContentValues;
210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.Context;
220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.res.Resources;
230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.sqlite.SQLiteDatabase;
240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.net.Uri;
250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.text.TextUtils;
260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.util.Log;
270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.compat.DownloadManagerCompatUtils;
290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.latin.R;
304be6198cb73cc24e10834153c4e049644ed187e3Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.ApplicationUtils;
3103118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasaimport com.android.inputmethod.latin.utils.DebugLogUtils;
320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.LinkedList;
340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Queue;
350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/**
370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Object representing an upgrade from one state to another.
380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard *
390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This implementation basically encapsulates a list of Runnable objects. In the future
400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * it may manage dependencies between them. Concretely, it does not use Runnable because the
410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * actions need an argument.
420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */
430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/*
440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
450cc0544a2995c7eb54a830ae54db60af89d4073dJean ChalardThe state of a word list follows the following scheme.
460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |                                   ^
480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  MakeAvailable                            |
490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |        .------------Forget--------'
500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       V        |
510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard STATUS_AVAILABLE  <-------------------------.
520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |                                     |
530cc0544a2995c7eb54a830ae54db60af89d4073dJean ChalardStartDownloadAction                  FinishDeleteAction
540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |                                     |
550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       V                                     |
560cc0544a2995c7eb54a830ae54db60af89d4073dJean ChalardSTATUS_DOWNLOADING      EnableAction-- STATUS_DELETING
570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |                     |               ^
580cc0544a2995c7eb54a830ae54db60af89d4073dJean ChalardInstallAfterDownloadAction   |               |
590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |     .---------------'        StartDeleteAction
600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       |     |                               |
610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard       V     V                               |
620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard STATUS_INSTALLED  <--EnableAction--   STATUS_DISABLED
630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    --DisableAction-->
640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  It may also be possible that DisableAction or StartDeleteAction or
660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  DownloadAction run when the file is still downloading.  This cancels
670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  the download and returns to STATUS_AVAILABLE.
680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  Also, an UpdateDataAction may apply in any state. It does not affect
690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  the state in any way (nor type, local filename, id or version) but
700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  may update other attributes like description or remote filename.
710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  Forget is an DB maintenance action that removes the entry if it is not installed or disabled.
730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  This happens when the word list information disappeared from the server, or when a new version
740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard  is available and we should forget about the old one.
750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard*/
760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardpublic final class ActionBatch {
770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * A piece of update.
790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Action is basically like a Runnable that takes an argument.
810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public interface Action {
830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        /**
840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard         * Execute this action NOW.
850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard         * @param context the context to get system services, resources, databases
860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard         */
870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context);
880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that starts downloading an available word list.
920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class StartDownloadAction implements Action {
940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + StartDownloadAction.class.getSimpleName();
950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The data to download. May not be null.
980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final boolean mForceStartNow;
1000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public StartDownloadAction(final String clientId,
1010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final WordListMetadata wordList, final boolean forceStartNow) {
10203118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New download action for client ", clientId, " : ", wordList);
1030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
1040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordList;
1050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mForceStartNow = forceStartNow;
1060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
1090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
1100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
1110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "UpdateAction with a null parameter!");
1120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
1130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
11403118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Downloading word list");
1150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
1160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
1170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
1180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
119a149731a6764f259b7d15e05a2f557a3bdd23aabJean Chalard            final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
1200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
1210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // The word list is still downloading. Cancel the download and revert the
1220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // word list status to "available".
123a149731a6764f259b7d15e05a2f557a3bdd23aabJean Chalard                 manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
1240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
1250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else if (MetadataDbHelper.STATUS_AVAILABLE != status) {
1260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // Should never happen
1270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status
1280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        + " for an upgrade action. Fall back to download.");
1290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
1300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // Download it.
13103118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
1320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // This is an upgraded word list: we should download it.
13443590149a5c2073a9fc8e3ed6afbf21fb017193eJean Chalard            // Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
13543590149a5c2073a9fc8e3ed6afbf21fb017193eJean Chalard            // DownloadManager also stupidly cuts the extension to replace with its own that it
13643590149a5c2073a9fc8e3ed6afbf21fb017193eJean Chalard            // gets from the content-type. We need to circumvent this.
13743590149a5c2073a9fc8e3ed6afbf21fb017193eJean Chalard            final String disambiguator = "#" + System.currentTimeMillis()
1384be6198cb73cc24e10834153c4e049644ed187e3Tadashi G. Takaoka                    + ApplicationUtils.getVersionName(context) + ".dict";
13943590149a5c2073a9fc8e3ed6afbf21fb017193eJean Chalard            final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
1400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final Request request = new Request(uri);
1410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final Resources res = context.getResources();
1430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (!mForceStartNow) {
1440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                if (DownloadManagerCompatUtils.hasSetAllowedOverMetered()) {
1450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    final boolean allowOverMetered;
1460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    switch (UpdateHandler.getDownloadOverMeteredSetting(context)) {
1470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    case UpdateHandler.DOWNLOAD_OVER_METERED_DISALLOWED:
1480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // User said no: don't allow.
1490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        allowOverMetered = false;
1500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        break;
1510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    case UpdateHandler.DOWNLOAD_OVER_METERED_ALLOWED:
1520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // User said yes: allow.
1530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        allowOverMetered = true;
1540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        break;
1550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    default: // UpdateHandler.DOWNLOAD_OVER_METERED_SETTING_UNKNOWN
1560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        // Don't know: use the default value from configuration.
1570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        allowOverMetered = res.getBoolean(R.bool.allow_over_metered);
1580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    }
1590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    DownloadManagerCompatUtils.setAllowedOverMetered(request, allowOverMetered);
1600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                } else {
1610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
1620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                }
1630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                request.setAllowedOverRoaming(res.getBoolean(R.bool.allow_over_roaming));
1640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } // if mForceStartNow, then allow all network types and roaming, which is the default.
1650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            request.setTitle(mWordList.mDescription);
1660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            request.setNotificationVisibility(
1670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    res.getBoolean(R.bool.display_notification_for_auto_update)
1680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            ? Request.VISIBILITY_VISIBLE : Request.VISIBILITY_HIDDEN);
1690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            request.setVisibleInDownloadsUi(
1700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    res.getBoolean(R.bool.dict_downloads_visible_in_download_UI));
1710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
1730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
17403118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
175f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard            PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
1760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
1780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
1800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that updates the database to reflect the status of a newly installed word list.
1810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
1820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class InstallAfterDownloadAction implements Action {
1830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:"
1840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                + InstallAfterDownloadAction.class.getSimpleName();
1850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
1860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The state to upgrade from. May not be null.
1870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final ContentValues mWordListValues;
1880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public InstallAfterDownloadAction(final String clientId,
1900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final ContentValues wordListValues) {
19103118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ",
192e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasa                    wordListValues);
1930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
1940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordListValues = wordListValues;
1950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
1960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
1970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
1980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
1990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordListValues) {
2000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "InstallAfterDownloadAction with a null parameter!");
2010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
2020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
2030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = mWordListValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
2040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DOWNLOADING != status) {
2050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final String id = mWordListValues.getAsString(MetadataDbHelper.WORDLISTID_COLUMN);
2060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected state of the word list '" + id + "' : " + status
2070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        + " for an InstallAfterDownload action. Bailing out.");
2080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
2090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
21003118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Setting word list as installed");
2110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
2120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues);
2130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that enables an existing word list.
2180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class EnableAction implements Action {
2200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + EnableAction.class.getSimpleName();
2210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
2220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The state to upgrade from. May not be null.
2230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
2240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public EnableAction(final String clientId, final WordListMetadata wordList) {
22603118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList);
2270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
2280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordList;
2290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
2320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
2330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) {
2340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "EnableAction with a null parameter!");
2350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
2360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
23703118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Enabling word list");
2380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
2390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
2400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
2410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
2420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DISABLED != status
2430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    && MetadataDbHelper.STATUS_DELETING != status) {
2440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + " : " + status
2450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                      + " for an enable action. Cancelling");
2460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
2470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
2480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            MetadataDbHelper.markEntryAsEnabled(db, mWordList.mId, mWordList.mVersion);
2490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that disables a word list.
2540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class DisableAction implements Action {
2560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + DisableAction.class.getSimpleName();
2570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
2580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to disable. May not be null.
2590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
2600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public DisableAction(final String clientId, final WordListMetadata wordlist) {
26103118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist);
2620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
2630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
2640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
2670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
2680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
2690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "DisableAction with a null word list!");
2700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
2710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
27203118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Disabling word list : " + mWordList);
2730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
2740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
2750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
2760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
2770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_INSTALLED == status) {
2780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // Disabling an installed word list
2790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.markEntryAsDisabled(db, mWordList.mId, mWordList.mVersion);
2800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else {
2810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                if (MetadataDbHelper.STATUS_DOWNLOADING != status) {
2820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : "
2830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            + status + " for a disable action. Fall back to marking as available.");
2840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                }
2850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // The word list is still downloading. Cancel the download and revert the
2860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // word list status to "available".
287a149731a6764f259b7d15e05a2f557a3bdd23aabJean Chalard                final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
288a149731a6764f259b7d15e05a2f557a3bdd23aabJean Chalard                manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
2890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
2900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
2910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
2920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
2930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
2940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
2950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that makes a word list available.
2960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
2970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class MakeAvailableAction implements Action {
2980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + MakeAvailableAction.class.getSimpleName();
2990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
3000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to make available. May not be null.
3010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
3020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) {
30303118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist);
3040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
3050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
3060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
3090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
3100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
3110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "MakeAvailableAction with a null word list!");
3120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
3130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
3140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
3150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null != MetadataDbHelper.getContentValuesByWordListId(db,
3160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion)) {
3170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
3180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        + " for a makeavailable action. Marking as available anyway.");
3190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
32003118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Making word list available : " + mWordList);
3210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // If mLocalFilename is null, then it's a remote file that hasn't been downloaded
3220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // yet, so we set the local filename to the empty string.
3230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.makeContentValues(0,
3240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_AVAILABLE,
3250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mLocale, mWordList.mDescription,
3260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename,
327e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
328e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
329e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mFormatVersion);
3300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            PrivateLog.log("Insert 'available' record for " + mWordList.mDescription
331f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                    + " and locale " + mWordList.mLocale);
3320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
3330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
3350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
3370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that marks a word list as pre-installed.
3380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
3390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This is almost the same as MakeAvailableAction, as it only inserts a line with parameters
3400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * received from outside.
3410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Unlike MakeAvailableAction, the parameters are not received from a downloaded metadata file
3420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * but from the client directly; it marks a word list as being "installed" and not "available".
3430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * It also explicitly sets the filename to the empty string, so that we don't try to open
3440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * it on our side.
3450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
3460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class MarkPreInstalledAction implements Action {
3470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:"
3480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                + MarkPreInstalledAction.class.getSimpleName();
3490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
3500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to mark pre-installed. May not be null.
3510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
3520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) {
35303118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
3540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
3550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
3560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
3590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
3600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
3610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "MarkPreInstalledAction with a null word list!");
3620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
3630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
3640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
3650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null != MetadataDbHelper.getContentValuesByWordListId(db,
3660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion)) {
3670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
3680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        + " for a markpreinstalled action. Marking as preinstalled anyway.");
3690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
37003118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Marking word list preinstalled : " + mWordList);
3710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // This word list is pre-installed : we don't have its file. We should reset
3720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // the local file name to the empty string so that we don't try to open it
3730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // accidentally. The remote filename may be set by the application if it so wishes.
3740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.makeContentValues(0,
3750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
3760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mLocale, mWordList.mDescription,
377e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    "", mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
3780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
3790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mFormatVersion);
3800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
381f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                    + " and locale " + mWordList.mLocale);
3820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
3830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
3850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
3870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that updates information about a word list - description, locale etc
3880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
3890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class UpdateDataAction implements Action {
3900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + UpdateDataAction.class.getSimpleName();
3910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
3920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
3930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public UpdateDataAction(final String clientId, final WordListMetadata wordlist) {
39403118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist);
3950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
3960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
3970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
3980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
3990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
4000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
4010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
4020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "UpdateDataAction with a null word list!");
4030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
4040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
4060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            ContentValues oldValues = MetadataDbHelper.getContentValuesByWordListId(db,
4070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
4080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == oldValues) {
4090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out.");
4100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
4110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
41203118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Updating data about a word list : " + mWordList);
4130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.makeContentValues(
4140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN),
4150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN),
4160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    oldValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN),
4170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mLocale, mWordList.mDescription,
4180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN),
419e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
420e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mChecksum, mWordList.mFileSize, mWordList.mVersion,
421e077c014616f7b4e28dbbfab622ba36c8e922268Jean Chalard                    mWordList.mFormatVersion);
4220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            PrivateLog.log("Updating record for " + mWordList.mDescription
423f8014eea341040f8d155e071e4e0c915a7ebd61dJean Chalard                    + " and locale " + mWordList.mLocale);
4240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
4250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
4260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                            + MetadataDbHelper.VERSION_COLUMN + " = ?",
4270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
4280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
4300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
4320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that deletes the metadata about a word list if possible.
4330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
4340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This is triggered when a specific word list disappeared from the server, or when a fresher
4350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * word list is available and the old one was not installed.
4360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * If the word list has not been installed, it's possible to delete its associated metadata.
4370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Otherwise, the settings are retained so that the user can still administrate it.
4380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
4390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class ForgetAction implements Action {
4400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + ForgetAction.class.getSimpleName();
4410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
4420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to remove. May not be null.
4430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
4440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final boolean mHasNewerVersion;
4450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public ForgetAction(final String clientId, final WordListMetadata wordlist,
4460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                final boolean hasNewerVersion) {
44703118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist);
4480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
4490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
4500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mHasNewerVersion = hasNewerVersion;
4510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
4540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
4550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
4560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "TryRemoveAction with a null word list!");
4570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
4580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
45903118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Trying to remove word list : " + mWordList);
4600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
4610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
4620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
4630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == values) {
4640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Trying to update the metadata of a non-existing wordlist. Cancelling.");
4650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
4660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
4680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (mHasNewerVersion && MetadataDbHelper.STATUS_AVAILABLE != status) {
4690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // If we have a newer version of this word list, we should be here ONLY if it was
4700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // not installed - else we should be upgrading it.
4710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected status for forgetting a word list info : " + status
4720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        + ", removing URL to prevent re-download");
4730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_INSTALLED == status
4750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    || MetadataDbHelper.STATUS_DISABLED == status
4760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    || MetadataDbHelper.STATUS_DELETING == status) {
477e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                // If it is installed or disabled, we need to mark it as deleted so that LatinIME
478e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                // will remove it next time it enquires for dictionaries.
4790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // If it is deleting and we don't have a new version, then we have to wait until
480e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                // LatinIME actually has deleted it before we can remove its metadata.
481e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                // In both cases, remove the URI from the database since it is not supposed to
482e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                // be accessible any more.
4830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                values.put(MetadataDbHelper.REMOTE_FILENAME_COLUMN, "");
484e8ed5d88763ce495ba36e7f7b8b334d75f211a2aJean Chalard                values.put(MetadataDbHelper.STATUS_COLUMN, MetadataDbHelper.STATUS_DELETING);
4850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
4860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
4870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                + MetadataDbHelper.VERSION_COLUMN + " = ?",
4880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
4890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else {
4900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                // If it's AVAILABLE or DOWNLOADING or even UNKNOWN, delete the entry.
4910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                db.delete(MetadataDbHelper.METADATA_TABLE_NAME,
4920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
4930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                + MetadataDbHelper.VERSION_COLUMN + " = ?",
4940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
4950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
4960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
4970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
4980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
4990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
5000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that sets the word list for deletion as soon as possible.
5010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
5020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This is triggered when the user requests deletion of a word list. This will mark it as
5030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * deleted in the database, and fire an intent for Android Keyboard to take notice and
5040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * reload its dictionaries right away if it is up. If it is not up now, then it will
5050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * delete the actual file the next time it gets up.
5060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * A file marked as deleted causes the content provider to supply a zero-sized file to
5070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Android Keyboard, which will overwrite any existing file and provide no words for this
5080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * word list. This is not exactly a "deletion", since there is an actual file which takes up
5090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * a few bytes on the disk, but this allows to override a default dictionary with an empty
5100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * dictionary. This way, there is no need for the user to make a distinction between
5110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * dictionaries installed by default and add-on dictionaries.
5120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
5130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class StartDeleteAction implements Action {
5140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + StartDeleteAction.class.getSimpleName();
5150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
5160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to delete. May not be null.
5170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
5180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public StartDeleteAction(final String clientId, final WordListMetadata wordlist) {
51903118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist);
5200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
5210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
5220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
5230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
5240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
5250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
5260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
5270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "StartDeleteAction with a null word list!");
5280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
5290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
53003118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Trying to delete word list : " + mWordList);
5310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
5320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
5330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
5340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == values) {
5350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling.");
5360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
5370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
5380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
5390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DISABLED != status) {
5400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected status for deleting a word list info : " + status);
5410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
5420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            MetadataDbHelper.markEntryAsDeleting(db, mWordList.mId, mWordList.mVersion);
5430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
5440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
5450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
5460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
5470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * An action that validates a word list as deleted.
5480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
5490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * This will restore the word list as available if it still is, or remove the entry if
5500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * it is not any more.
5510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
5520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public static final class FinishDeleteAction implements Action {
5530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        static final String TAG = "DictionaryProvider:" + FinishDeleteAction.class.getSimpleName();
5540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        private final String mClientId;
5550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        // The word list to delete. May not be null.
5560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        final WordListMetadata mWordList;
5570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) {
55803118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist);
5590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mClientId = clientId;
5600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            mWordList = wordlist;
5610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
5620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
5630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        @Override
5640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        public void execute(final Context context) {
5650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == mWordList) { // This should never happen
5660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "FinishDeleteAction with a null word list!");
5670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
5680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
56903118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa            DebugLogUtils.l("Trying to delete word list : " + mWordList);
5700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
5710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
5720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    mWordList.mId, mWordList.mVersion);
5730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (null == values) {
5740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling.");
5750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                return;
5760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
5770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
5780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (MetadataDbHelper.STATUS_DELETING != status) {
5790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                Log.e(TAG, "Unexpected status for finish-deleting a word list info : " + status);
5800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
5810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final String remoteFilename =
5820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN);
5830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // If there isn't a remote filename any more, then we don't know where to get the file
5840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // from any more, so we remove the entry entirely. As a matter of fact, if the file was
5850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // marked DELETING but disappeared from the metadata on the server, it ended up
5860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            // this way.
5870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            if (TextUtils.isEmpty(remoteFilename)) {
5880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                db.delete(MetadataDbHelper.METADATA_TABLE_NAME,
5890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
5900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                                + MetadataDbHelper.VERSION_COLUMN + " = ?",
5910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                        new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
5920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } else {
5930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
5940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
5950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
5960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
5970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
5980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    // An action batch consists of an ordered queue of Actions that can execute.
5990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    private final Queue<Action> mActions;
6000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
6010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public ActionBatch() {
602a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        mActions = new LinkedList<>();
6030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
6040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
6050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public void add(final Action a) {
6060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        mActions.add(a);
6070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
6080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
6090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
6100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Append all the actions of another action batch.
6110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param that the upgrade to merge into this one.
6120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
6130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public void append(final ActionBatch that) {
6140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        for (final Action a : that.mActions) {
6150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            add(a);
6160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
6170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
6180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard
6190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    /**
6200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * Execute this batch.
6210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     *
6220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param context the context for getting resources, databases, system services.
6230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     * @param reporter a Reporter to send errors to.
6240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard     */
6250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    public void execute(final Context context, final ProblemReporter reporter) {
62603118a276014cd44d44d0d46f4f39622765e8e0cKen Wakasa        DebugLogUtils.l("Executing a batch of actions");
6270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        Queue<Action> remainingActions = mActions;
6280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        while (!remainingActions.isEmpty()) {
6290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            final Action a = remainingActions.poll();
6300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            try {
6310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                a.execute(context);
6320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            } catch (Exception e) {
6330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                if (null != reporter)
6340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard                    reporter.report(e);
6350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard            }
6360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard        }
6370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard    }
6380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard}
639