UpdateHandler.java revision 0cc0544a2995c7eb54a830ae54db60af89d4073d
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; 200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.DownloadManager.Query; 210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.DownloadManager.Request; 220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.Notification; 230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.NotificationManager; 240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.app.PendingIntent; 250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.ContentValues; 260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.Context; 270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.Intent; 280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.SharedPreferences; 290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.content.res.Resources; 300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.Cursor; 310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.database.sqlite.SQLiteDatabase; 320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.net.ConnectivityManager; 330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.net.Uri; 340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.os.ParcelFileDescriptor; 350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.text.TextUtils; 360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport android.util.Log; 370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.compat.ConnectivityManagerCompatUtils; 390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.compat.DownloadManagerCompatUtils; 400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport com.android.inputmethod.latin.R; 410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.File; 430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.FileInputStream; 440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.FileNotFoundException; 450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.FileOutputStream; 460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.IOException; 470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.InputStream; 480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.InputStreamReader; 490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.io.OutputStream; 500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.nio.channels.FileChannel; 510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.ArrayList; 520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Collections; 530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.LinkedList; 540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.List; 550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Locale; 560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.Set; 570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardimport java.util.TreeSet; 580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard/** 600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Handler for the update process. 610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This class is in charge of coordinating the update process for the various dictionaries 630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * stored in the dictionary pack. 640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalardpublic final class UpdateHandler { 660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard static final String TAG = "DictionaryProvider:" + UpdateHandler.class.getSimpleName(); 670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static final boolean DEBUG = DictionaryProvider.DEBUG; 680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Used to prevent trying to read the id of the downloaded file before it is written 700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard static final Object sSharedIdProtector = new Object(); 710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Value used to mean this is not a real DownloadManager downloaded file id 730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column 740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // in SQLite, so it should never return anything < 0. 750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final int NOT_AN_ID = -1; 760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION = 2; 770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long. 790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static final int FILE_COPY_BUFFER_SIZE = 8192; 800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Table fixed values for metadata / downloads 820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final static String METADATA_NAME = "metadata"; 830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final static int METADATA_TYPE = 0; 840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final static int WORDLIST_TYPE = 1; 850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Suffix for generated dictionary files 870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static final String DICT_FILE_SUFFIX = ".dict"; 880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Name of the category for the main dictionary 890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final String MAIN_DICTIONARY_CATEGORY = "main"; 900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * The action of the intent for publishing that new dictionary data is available. 930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: make this different across different packages. A suggested course of action is 950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // to use the package name inside this string. 960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final String NEW_DICTIONARY_INTENT_ACTION = 970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard "com.android.inputmethod.dictionarypack.newdict"; 980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // The id for the "dictionary available" notification. 1000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard static final int DICT_AVAILABLE_NOTIFICATION_ID = 1; 1010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * An interface for UIs or services that want to know when something happened. 1040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 1050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This is chiefly used by the dictionary manager UI. 1060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public interface UpdateEventListener { 1080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public void downloadedMetadata(boolean succeeded); 1090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public void wordListDownloadFinished(String wordListId, boolean succeeded); 1100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public void updateCycleCompleted(); 1110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * The list of currently registered listeners. 1150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static List<UpdateEventListener> sUpdateEventListeners 1170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard = Collections.synchronizedList(new LinkedList<UpdateEventListener>()); 1180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Register a new listener to be notified of updates. 1210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 1220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Don't forget to call unregisterUpdateEventListener when done with it, or 1230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * it will leak the register. 1240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void registerUpdateEventListener(final UpdateEventListener listener) { 1260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard sUpdateEventListeners.add(listener); 1270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Unregister a previously registered listener. 1310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void unregisterUpdateEventListener(final UpdateEventListener listener) { 1330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard sUpdateEventListeners.remove(listener); 1340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static final String DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY = "downloadOverMetered"; 1370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Write the DownloadManager ID of the currently downloading metadata to permanent storage. 1400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 1410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context to open shared prefs 1420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param uri the uri of the metadata 1430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param downloadId the id returned by DownloadManager 1440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void writeMetadataDownloadId(final Context context, final String uri, 1460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long downloadId) { 1470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.registerMetadataDownloadId(context, uri, downloadId); 1480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final int DOWNLOAD_OVER_METERED_SETTING_UNKNOWN = 0; 1510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final int DOWNLOAD_OVER_METERED_ALLOWED = 1; 1520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static final int DOWNLOAD_OVER_METERED_DISALLOWED = 2; 1530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Sets the setting that tells us whether we may download over a metered connection. 1560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void setDownloadOverMeteredSetting(final Context context, 1580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean shouldDownloadOverMetered) { 1590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); 1600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SharedPreferences.Editor editor = prefs.edit(); 1610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard editor.putInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY, shouldDownloadOverMetered 1620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard ? DOWNLOAD_OVER_METERED_ALLOWED : DOWNLOAD_OVER_METERED_DISALLOWED); 1630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard editor.apply(); 1640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Gets the setting that tells us whether we may download over a metered connection. 1680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 1690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This returns one of the constants above. 1700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static int getDownloadOverMeteredSetting(final Context context) { 1720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context); 1730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int setting = prefs.getInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY, 1740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard DOWNLOAD_OVER_METERED_SETTING_UNKNOWN); 1750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return setting; 1760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 1770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 1780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 1790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Download latest metadata from the server through DownloadManager for all known clients 1800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context The context for retrieving resources 1810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param updateNow Whether we should update NOW, or respect bandwidth policies 1820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 1830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void update(final Context context, final boolean updateNow) { 1840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: loop through all clients instead of only doing the default one. 1850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final TreeSet<String> uris = new TreeSet<String>(); 1860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Cursor cursor = MetadataDbHelper.queryClientIds(context); 1870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == cursor) return; 1880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 1890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!cursor.moveToFirst()) return; 1900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard do { 1910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId = cursor.getString(0); 1920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (TextUtils.isEmpty(clientId)) continue; // This probably can't happen 1930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String metadataUri = 1940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.getMetadataUriAsString(context, clientId); 1950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Update for clientId " + Utils.s(clientId), context); 1960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Update for clientId", clientId, " which uses URI ", metadataUri); 1970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard uris.add(metadataUri); 1980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } while (cursor.moveToNext()); 1990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 2000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard cursor.close(); 2010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (final String metadataUri : uris) { 2030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!TextUtils.isEmpty(metadataUri)) { 2040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If the metadata URI is empty, that means we should never update it at all. 2050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // It should not be possible to come here with a null metadata URI, because 2060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // it should have been rejected at the time of client registration; if there 2070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // is a bug and it happens anyway, doing nothing is the right thing to do. 2080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}. 2090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard updateClientsWithMetadataUri(context, updateNow, metadataUri); 2100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 2150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Download latest metadata from the server through DownloadManager for all relevant clients 2160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 2170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context The context for retrieving resources 2180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param updateNow Whether we should update NOW, or respect bandwidth policies 2190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param metadataUri The client to update 2200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 2210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void updateClientsWithMetadataUri(final Context context, 2220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean updateNow, final String metadataUri) { 2230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Update for metadata URI " + Utils.s(metadataUri), context); 2240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Request metadataRequest = new Request(Uri.parse(metadataUri)); 2250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Request =", metadataRequest); 2260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Resources res = context.getResources(); 2280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // By default, download over roaming is allowed and all network types are allowed too. 2290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!updateNow) { 2300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean allowedOverMetered = res.getBoolean(R.bool.allow_over_metered); 2310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If we don't have to update NOW, then only do it over non-metered connections. 2320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (DownloadManagerCompatUtils.hasSetAllowedOverMetered()) { 2330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard DownloadManagerCompatUtils.setAllowedOverMetered(metadataRequest, 2340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard allowedOverMetered); 2350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (!allowedOverMetered) { 2360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard metadataRequest.setAllowedNetworkTypes(Request.NETWORK_WIFI); 2370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard metadataRequest.setAllowedOverRoaming(res.getBoolean(R.bool.allow_over_roaming)); 2390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean notificationVisible = updateNow 2410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard ? res.getBoolean(R.bool.display_notification_for_user_requested_update) 2420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard : res.getBoolean(R.bool.display_notification_for_auto_update); 2430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard metadataRequest.setTitle(res.getString(R.string.download_description)); 2450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard metadataRequest.setNotificationVisibility(notificationVisible 2460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard ? Request.VISIBILITY_VISIBLE : Request.VISIBILITY_HIDDEN); 2470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard metadataRequest.setVisibleInDownloadsUi( 2480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI)); 2490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final DownloadManager manager = 2510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 2520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == manager) { 2530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Download manager is not installed or disabled. 2540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: fall back to self-managed download? 2550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; 2560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard cancelUpdateWithDownloadManager(context, metadataUri, manager); 2580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long downloadId; 2590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard synchronized (sSharedIdProtector) { 2600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadId = manager.enqueue(metadataRequest); 2610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Metadata download requested with id", downloadId); 2620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If there is already a download in progress, it's been there for a while and 2630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // there is probably something wrong with download manager. It's best to just 2640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // overwrite the id and request it again. If the old one happens to finish 2650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // anyway, we don't know about its ID any more, so the downloadFinished 2660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // method will ignore it. 2670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard writeMetadataDownloadId(context, metadataUri, downloadId); 2680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Requested download with id " + downloadId, context); 2700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 2730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Cancels a pending update, if there is one. 2740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 2750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * If none, this is a no-op. 2760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 2770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to open the database on 2780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client 2790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param manager an instance of DownloadManager 2800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 2810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void cancelUpdateWithDownloadManager(final Context context, 2820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId, final DownloadManager manager) { 2830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard synchronized (sSharedIdProtector) { 2840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long metadataDownloadId = 2850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.getMetadataDownloadIdForClient(context, clientId); 2860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (NOT_AN_ID == metadataDownloadId) return; 2870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard manager.remove(metadataDownloadId); 2880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard writeMetadataDownloadId(context, 2890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.getMetadataUriAsString(context, clientId), NOT_AN_ID); 2900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Consider a cancellation as a failure. As such, inform listeners that the download 2920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // has failed. 2930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { 2940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard listener.downloadedMetadata(false); 2950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 2970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 2980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 2990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Cancels a pending update, if there is one. 3000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 3010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * If there is none, this is a no-op. This is a helper method that gets the 3020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * download manager service. 3030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 3040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context, to get an instance of DownloadManager 3050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the ID of the client we want to cancel the update of 3060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 3070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void cancelUpdate(final Context context, final String clientId) { 3080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final DownloadManager manager = 3090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 3100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null != manager) cancelUpdateWithDownloadManager(context, clientId, manager); 3110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 3130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 3140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Registers a download request and flags it as downloading in the metadata table. 3150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 3160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This is a helper method that exists to avoid race conditions where DownloadManager might 3170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * finish downloading the file before the data is committed to the database. 3180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * It registers the request with the DownloadManager service and also updates the metadata 3190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * database directly within a synchronized section. 3200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This method has no intelligence about the data it commits to the database aside from the 3210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * download request id, which is not known before submitting the request to the download 3220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * manager. Hence, it only updates the relevant line. 3230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 3240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param manager the download manager service to register the request with. 3250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param request the request to register. 3260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param db the metadata database. 3270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param id the id of the word list. 3280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list. 3290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @return the download id returned by the download manager. 3300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 3310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static long registerDownloadRequest(final DownloadManager manager, final Request request, 3320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SQLiteDatabase db, final String id, final int version) { 3330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version); 3340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long downloadId; 3350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard synchronized (sSharedIdProtector) { 3360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadId = manager.enqueue(request); 3370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Download requested with id", downloadId); 3380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId); 3390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return downloadId; 3410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 3430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 3440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Retrieve information about a specific download from DownloadManager. 3450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 3460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static CompletedDownloadInfo getCompletedDownloadInfo(final DownloadManager manager, 3470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long downloadId) { 3480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Query query = new Query().setFilterById(downloadId); 3490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Cursor cursor = manager.query(query); 3500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 3510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == cursor) { 3520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return new CompletedDownloadInfo(null, downloadId, DownloadManager.STATUS_FAILED); 3530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 3550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String uri; 3560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int status; 3570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (cursor.moveToNext()) { 3580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int columnStatus = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); 3590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int columnError = cursor.getColumnIndex(DownloadManager.COLUMN_REASON); 3600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int columnUri = cursor.getColumnIndex(DownloadManager.COLUMN_URI); 3610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int error = cursor.getInt(columnError); 3620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard status = cursor.getInt(columnStatus); 3630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard uri = cursor.getString(columnUri); 3640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (DownloadManager.STATUS_SUCCESSFUL != status) { 3650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Permanent failure of download " + downloadId 3660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + " with error code: " + error); 3670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 3690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard uri = null; 3700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard status = DownloadManager.STATUS_FAILED; 3710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return new CompletedDownloadInfo(uri, downloadId, status); 3730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 3740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard cursor.close(); 3750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 3780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static ArrayList<DownloadRecord> getDownloadRecordsForCompletedDownloadInfo( 3790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Context context, final CompletedDownloadInfo downloadInfo) { 3800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Get and check the ID of the file we are waiting for, compare them to downloaded ones 3810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard synchronized(sSharedIdProtector) { 3820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ArrayList<DownloadRecord> downloadRecords = 3830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.getDownloadRecordsForDownloadId(context, 3840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadInfo.mDownloadId); 3850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If any of these is metadata, we should update the DB 3860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard boolean hasMetadata = false; 3870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (DownloadRecord record : downloadRecords) { 3880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == record.mAttributes) { 3890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard hasMetadata = true; 3900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard break; 3910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (hasMetadata) { 3940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard writeMetadataDownloadId(context, downloadInfo.mUri, NOT_AN_ID); 3950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.saveLastUpdateTimeOfUri(context, downloadInfo.mUri); 3960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return downloadRecords; 3980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 3990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 4020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Take appropriate action after a download finished, in success or in error. 4030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 4040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This is called by the system upon broadcast from the DownloadManager that a file 4050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * has been downloaded successfully. 4060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * After a simple check that this is actually the file we are waiting for, this 4070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * method basically coordinates the parsing and comparison of metadata, and fires 4080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the computation of the list of actions that should be taken then executes them. 4090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 4100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context The context for this action. 4110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param intent The intent from the DownloadManager containing details about the download. 4120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 4130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /* package */ static void downloadFinished(final Context context, final Intent intent) { 4140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Get and check the ID of the file that was downloaded 4150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID); 4160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Download finished with id " + fileId, context); 4170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("DownloadFinished with id", fileId); 4180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore 4190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final DownloadManager manager = 4210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 4220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final CompletedDownloadInfo downloadInfo = getCompletedDownloadInfo(manager, fileId); 4230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ArrayList<DownloadRecord> recordList = 4250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo); 4260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == recordList) return; // It was someone else's download. 4270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Received result for download ", fileId); 4280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: handle gracefully a null pointer here. This is practically impossible because 4300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // we come here only when DownloadManager explicitly called us when it ended a 4310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // download, so we are pretty sure it's alive. It's theoretically possible that it's 4320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // disabled right inbetween the firing of the intent and the control reaching here. 4330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (final DownloadRecord record : recordList) { 4350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // downloadSuccessful is not final because we may still have exceptions from now on 4360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard boolean downloadSuccessful = false; 4370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 4380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (downloadInfo.wasSuccessful()) { 4390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadSuccessful = handleDownloadedFile(context, record, manager, fileId); 4400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 4420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (record.isMetadata()) { 4430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard publishUpdateMetadataCompleted(context, downloadSuccessful); 4440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 4450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId); 4460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard publishUpdateWordListCompleted(context, downloadSuccessful, fileId, 4470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard db, record.mAttributes, record.mClientId); 4480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Now that we're done using it, we can remove this download from DLManager 4520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard manager.remove(fileId); 4530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void publishUpdateMetadataCompleted(final Context context, 4560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean downloadSuccessful) { 4570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // We need to warn all listeners of what happened. But some listeners may want to 4580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // remove themselves or re-register something in response. Hence we should take a 4590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // snapshot of the listener list and warn them all. This also prevents any 4600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // concurrent modification problem of the static list. 4610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { 4620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard listener.downloadedMetadata(downloadSuccessful); 4630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard publishUpdateCycleCompletedEvent(context); 4650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void publishUpdateWordListCompleted(final Context context, 4680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final boolean downloadSuccessful, final long fileId, 4690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SQLiteDatabase db, final ContentValues downloadedFileRecord, 4700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId) { 4710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard synchronized(sSharedIdProtector) { 4720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (downloadSuccessful) { 4730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 4740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.InstallAfterDownloadAction(clientId, 4750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadedFileRecord)); 4760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 4770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 4780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.deleteDownloadingEntry(db, fileId); 4790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // See comment above about #linkedCopyOfLists 4820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { 4830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard listener.wordListDownloadFinished(downloadedFileRecord.getAsString( 4840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.WORDLISTID_COLUMN), downloadSuccessful); 4850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard publishUpdateCycleCompletedEvent(context); 4870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void publishUpdateCycleCompletedEvent(final Context context) { 4900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Even if this is not successful, we have to publish the new state. 4910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Publishing update cycle completed event", context); 4920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Publishing update cycle completed event"); 4930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { 4940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard listener.updateCycleCompleted(); 4950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard signalNewDictionaryState(context); 4970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 4980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 4990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static boolean handleDownloadedFile(final Context context, 5000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final DownloadRecord downloadRecord, final DownloadManager manager, 5010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final long fileId) { 5020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 5030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // {@link handleWordList(Context,InputStream,ContentValues)}. 5040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Handle the downloaded file according to its type 5050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (downloadRecord.isMetadata()) { 5060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Data D/L'd is metadata for", downloadRecord.mClientId); 5070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // #handleMetadata() closes its InputStream argument 5080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream( 5090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard manager.openDownloadedFile(fileId)), downloadRecord.mClientId); 5100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 5110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Data D/L'd is a word list"); 5120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int wordListStatus = downloadRecord.mAttributes.getAsInteger( 5130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.STATUS_COLUMN); 5140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) { 5150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // #handleWordList() closes its InputStream argument 5160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard handleWordList(context, new ParcelFileDescriptor.AutoCloseInputStream( 5170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard manager.openDownloadedFile(fileId)), downloadRecord); 5180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 5190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Spurious download ended. Maybe a cancelled download?"); 5200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return true; 5230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } catch (FileNotFoundException e) { 5240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "A file was downloaded but it can't be opened", e); 5250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } catch (IOException e) { 5260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Can't read the file... disk damage? 5270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Can't read a file", e); 5280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: Check with UX how we should warn the user. 5290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } catch (IllegalStateException e) { 5300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // The format of the downloaded file is incorrect. We should maybe report upstream? 5310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Incorrect data received", e); 5320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } catch (BadFormatException e) { 5330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // The format of the downloaded file is incorrect. We should maybe report upstream? 5340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Incorrect data received", e); 5350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return false; 5370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 5400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Returns a copy of the specified list, with all elements copied. 5410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 5420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This returns a linked list. 5430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 5440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static <T> List<T> linkedCopyOfList(final List<T> src) { 5450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Instantiation of a parameterized type is not possible in Java, so it's not possible to 5460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // return the same type of list that was passed - probably the same reason why Collections 5470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // does not do it. So we need to decide statically which concrete type to return. 5480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return new LinkedList<T>(src); 5490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 5520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Warn Android Keyboard that the state of dictionaries changed and it should refresh its data. 5530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 5540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void signalNewDictionaryState(final Context context) { 5550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Intent newDictBroadcast = new Intent(NEW_DICTIONARY_INTENT_ACTION); 5560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard context.sendBroadcast(newDictBroadcast); 5570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 5600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Parse metadata and take appropriate action (that is, upgrade dictionaries). 5610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to read settings. 5620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param stream an input stream pointing to the downloaded data. May not be null. 5630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Will be closed upon finishing. 5640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the ID of the client to update 5650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws BadFormatException if the metadata is not in a known format. 5660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws IOException if the downloaded file can't be read from the disk 5670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 5680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void handleMetadata(final Context context, final InputStream stream, 5690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId) throws IOException, BadFormatException { 5700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Entering handleMetadata"); 5710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> newMetadata; 5720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final InputStreamReader reader = new InputStreamReader(stream); 5730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 5740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // According to the doc InputStreamReader buffers, so no need to add a buffering layer 5750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard newMetadata = MetadataHandler.readMetadata(reader); 5760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 5770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard reader.close(); 5780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Downloaded metadata :", newMetadata); 5810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Downloaded metadata\n" + newMetadata, context); 5820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata); 5840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: Check with UX how we should report to the user 5850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: add an action to close the database 5860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 5870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 5880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 5890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 5900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Handle a word list: put it in its right place, and update the passed content values. 5910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context for opening files. 5920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param inputStream an input stream pointing to the downloaded data. May not be null. 5930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Will be closed upon finishing. 5940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param downloadRecord the content values to fill the file name in. 5950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws IOException if files can't be read or written. 5960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws BadFormatException if the md5 checksum doesn't match the metadata. 5970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 5980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void handleWordList(final Context context, 5990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final InputStream inputStream, final DownloadRecord downloadRecord) 6000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard throws IOException, BadFormatException { 6010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // DownloadManager does not have the ability to put the file directly where we want 6030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // it, so we had it download to a temporary place. Now we move it. It will be deleted 6040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // automatically by DownloadManager. 6050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( 6060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId); 6070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PrivateLog.log("Downloaded a new word list with description : " 6080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN) 6090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + " for " + downloadRecord.mClientId, context); 6100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String locale = 6120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadRecord.mAttributes.getAsString(MetadataDbHelper.LOCALE_COLUMN); 6130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String destinationFile = getTempFileName(context, locale); 6140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard downloadRecord.mAttributes.put(MetadataDbHelper.LOCAL_FILENAME_COLUMN, destinationFile); 6150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard FileOutputStream outputStream = null; 6170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 6180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard outputStream = context.openFileOutput(destinationFile, Context.MODE_PRIVATE); 6190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard copyFile(inputStream, outputStream); 6200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 6210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard inputStream.close(); 6220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (outputStream != null) { 6230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard outputStream.close(); 6240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: Consolidate this MD5 calculation with file copying above. 6280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // We need to reopen the file because the inputstream bytes have been consumed, and there 6290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // is nothing in InputStream to reopen or rewind the stream 6300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard FileInputStream copiedFile = null; 6310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String md5sum; 6320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 6330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard copiedFile = context.openFileInput(destinationFile); 6340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard md5sum = MD5Calculator.checksum(copiedFile); 6350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } finally { 6360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (copiedFile != null) { 6370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard copiedFile.close(); 6380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (TextUtils.isEmpty(md5sum)) { 6410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; // We can't compute the checksum anyway, so return and hope for the best 6420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!md5sum.equals(downloadRecord.mAttributes.getAsString( 6440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.CHECKSUM_COLUMN))) { 6450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard context.deleteFile(destinationFile); 6460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard throw new BadFormatException("MD5 checksum check failed : \"" + md5sum + "\" <> \"" 6470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + downloadRecord.mAttributes.getAsString(MetadataDbHelper.CHECKSUM_COLUMN) 6480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + "\""); 6490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 6530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Copies in to out using FileChannels. 6540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 6550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This tries to use channels for fast copying. If it doesn't work, fall back to 6560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * copyFileFallBack below. 6570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 6580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param in the stream to copy from. 6590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param out the stream to copy to. 6600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws IOException if both the normal and fallback methods raise exceptions. 6610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 6620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void copyFile(final InputStream in, final OutputStream out) 6630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard throws IOException { 6640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Copying files"); 6650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) { 6660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Not the right types"); 6670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard copyFileFallback(in, out); 6680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 6690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard try { 6700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final FileChannel sourceChannel = ((FileInputStream) in).getChannel(); 6710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final FileChannel destinationChannel = ((FileOutputStream) out).getChannel(); 6720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel); 6730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } catch (IOException e) { 6740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Can't work with channels, or something went wrong. Copy by hand. 6750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Won't work"); 6760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard copyFileFallback(in, out); 6770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 6820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Copies in to out with read/write methods, not FileChannels. 6830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 6840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param in the stream to copy from. 6850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param out the stream to copy to. 6860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws IOException if a read or a write fails. 6870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 6880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void copyFileFallback(final InputStream in, final OutputStream out) 6890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard throws IOException { 6900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Falling back to slow copy"); 6910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE]; 6920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer)) 6930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard out.write(buffer, 0, readBytes); 6940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 6950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 6960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 6970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Creates and returns a new file to store a dictionary 6980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to use to open the file. 6990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param locale the locale for this dictionary, to make the file name more readable. 7000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @return the file name, or throw an exception. 7010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @throws IOException if the file cannot be created. 7020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 7030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static String getTempFileName(final Context context, final String locale) 7040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard throws IOException { 7050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Entering openTempFileOutput"); 7060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final File dir = context.getFilesDir(); 7070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir); 7080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("File name is", f.getName()); 7090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return f.getName(); 7100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 7110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 7120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 7130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Compare metadata (collections of word lists). 7140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 7150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This method takes whole metadata sets directly and compares them, matching the wordlists in 7160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * each of them on the id. It creates an ActionBatch object that can be .execute()'d to perform 7170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the actual upgrade from `from' to `to'. 7180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 7190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to open databases on. 7200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 7210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param from the dictionary descriptor (as a list of wordlists) to upgrade from. 7220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param to the dictionary descriptor (as a list of wordlists) to upgrade to. 7230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @return an ordered list of runnables to be called to upgrade. 7240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 7250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static ActionBatch compareMetadataForUpgrade(final Context context, 7260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) { 7270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 7280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Upgrade existing word lists 7290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Comparing dictionaries"); 7300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Set<String> wordListIds = new TreeSet<String>(); 7310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: Can these be null? 7320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == from) from = new ArrayList<WordListMetadata>(); 7330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == to) to = new ArrayList<WordListMetadata>(); 7340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (WordListMetadata wlData : from) wordListIds.add(wlData.mId); 7350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (WordListMetadata wlData : to) wordListIds.add(wlData.mId); 7360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard for (String id : wordListIds) { 7370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata currentInfo = MetadataHandler.findWordListById(from, id); 7380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata metadataInfo = MetadataHandler.findWordListById(to, id); 7390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: Remove the following unnecessary check, since we are now doing the filtering 7400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // inside findWordListById. 7410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata newInfo = null == metadataInfo 7420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION 7430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard ? null : metadataInfo; 7440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Utils.l("Considering updating ", id, "currentInfo =", currentInfo); 7450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 7460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == currentInfo && null == newInfo) { 7470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // This may happen if a new word list appeared that we can't handle. 7480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == metadataInfo) { 7490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // What happened? Bug in Set<>? 7500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Got an id for a wordlist that is neither in from nor in to"); 7510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 7520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // We may come here if there is a new word list that we can't handle. 7530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format" 7540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + " version " + metadataInfo.mFormatVersion + " and the maximum version" 7550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + "we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION); 7560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 7570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard continue; 7580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (null == currentInfo) { 7590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // This is the case where a new list that we did not know of popped on the server. 7600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Make it available. 7610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo)); 7620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (null == newInfo) { 7630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // This is the case where an old list we had is not in the server data any more. 7640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Pass false to ForgetAction: this may be installed and we still want to apply 7650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // a forget-like action (remove the URL) if it is, so we want to turn off the 7660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // status == AVAILABLE check. If it's DELETING, this is the right thing to do, 7670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // as we want to leave the record as long as Android Keyboard has not deleted it ; 7680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // the record will be removed when the file is actually deleted. 7690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, false)); 7700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 7710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); 7720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (newInfo.mVersion == currentInfo.mVersion) { 7730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If it's the same id/version, we update the DB with the new values. 7740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // It doesn't matter too much if they didn't change. 7750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo)); 7760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (newInfo.mVersion > currentInfo.mVersion) { 7770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If it's a new version, it's a different entry in the database. Make it 7780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // available, and if it's installed, also start the download. 7790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, 7800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard currentInfo.mId, currentInfo.mVersion); 7810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN); 7820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo)); 7830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (status == MetadataDbHelper.STATUS_INSTALLED 7840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard || status == MetadataDbHelper.STATUS_DISABLED) { 7850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo, false)); 7860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 7870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Pass true to ForgetAction: this is indeed an update to a non-installed 7880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // word list, so activate status == AVAILABLE check 7890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // In case the status is DELETING, this is the right thing to do. It will 7900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // leave the entry as DELETING and remove its URL so that Android Keyboard 7910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // can delete it the next time it starts up. 7920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, true)); 7930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 7940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (DEBUG) { 7950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.i(TAG, "Not updating word list " + id 7960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + " : current list timestamp is " + currentInfo.mLastUpdate 7970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard + " ; new list timestamp is " + newInfo.mLastUpdate); 7980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 7990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return actions; 8020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 8040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 8050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Computes an upgrade from the current state of the dictionaries to some desired state. 8060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context for reading settings and files. 8070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 8080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param newMetadata the state we want to upgrade to. 8090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @return the upgrade from the current state to the desired state, ready to be executed. 8100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 8110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static ActionBatch computeUpgradeTo(final Context context, final String clientId, 8120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> newMetadata) { 8130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> currentMetadata = 8140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.getCurrentMetadata(context, clientId); 8150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return compareMetadataForUpgrade(context, clientId, currentMetadata, newMetadata); 8160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 8180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 8190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Shows the notification that informs the user a dictionary is available. 8200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 8210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * When this notification is clicked, the dialog for downloading the dictionary 8220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * over a metered connection is shown. 8230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 8240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard private static void showDictionaryAvailableNotification(final Context context, 8250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String clientId, final ContentValues installCandidate) { 8260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN); 8270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Intent intent = new Intent(); 8280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.setClass(context, DownloadOverMeteredDialog.class); 8290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.putExtra(DownloadOverMeteredDialog.CLIENT_ID_KEY, clientId); 8300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.putExtra(DownloadOverMeteredDialog.WORDLIST_TO_DOWNLOAD_KEY, 8310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard installCandidate.getAsString(MetadataDbHelper.WORDLISTID_COLUMN)); 8320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.putExtra(DownloadOverMeteredDialog.SIZE_KEY, 8330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard installCandidate.getAsInteger(MetadataDbHelper.FILESIZE_COLUMN)); 8340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.putExtra(DownloadOverMeteredDialog.LOCALE_KEY, localeString); 8350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 8360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final PendingIntent notificationIntent = PendingIntent.getActivity(context, 8370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 0 /* requestCode */, intent, 8380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); 8390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final NotificationManager notificationManager = 8400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 8410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // None of those are expected to happen, but just in case... 8420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == notificationIntent || null == notificationManager) return; 8430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 8440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Locale locale = LocaleUtils.constructLocaleFromString(localeString); 8450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String language = (null == locale ? "" : locale.getDisplayLanguage()); 8460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String titleFormat = context.getString(R.string.dict_available_notification_title); 8470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String notificationTitle = String.format(titleFormat, language); 8480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Notification notification = new Notification.Builder(context) 8490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setAutoCancel(true) 8500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setContentIntent(notificationIntent) 8510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setContentTitle(notificationTitle) 8520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setContentText(context.getString(R.string.dict_available_notification_description)) 8530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setTicker(notificationTitle) 8540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setOngoing(false) 8550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setOnlyAlertOnce(true) 8560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .setSmallIcon(R.drawable.ic_notify_dictionary) 8570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard .getNotification(); 8580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard notificationManager.notify(DICT_AVAILABLE_NOTIFICATION_ID, notification); 8590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 8610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 8620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Installs a word list if it has never been requested. 8630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 8640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This is called when a word list is requested, and is available but not installed. It checks 8650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * the conditions for auto-installation: if the dictionary is a main dictionary for this 8660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * language, and it has never been opted out through the dictionary interface, then we start 8670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * installing it. For the user who enables a language and uses it for the first time, the 8680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * dictionary should magically start being used a short time after they start typing. 8690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * The mayPrompt argument indicates whether we should prompt the user for a decision to 8700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * download or not, in case we decide we are in the case where we should download - this 8710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * roughly happens when the current connectivity is 3G. See 8720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * DictionaryProvider#getDictionaryWordListsForContentUri for details. 8730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 8740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // As opposed to many other methods, this method does not need the version of the word 8750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // list because it may only install the latest version we know about for this specific 8760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // word list ID / client ID combination. 8770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void installIfNeverRequested(final Context context, final String clientId, 8780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final boolean mayPrompt) { 8790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String[] idArray = wordlistId.split(DictionaryProvider.ID_CATEGORY_SEPARATOR); 8800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If we have a new-format dictionary id (category:manual_id), then use the 8810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // specified category. Otherwise, it is a main dictionary, so force the 8820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // MAIN category upon it. 8830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String category = 2 == idArray.length ? idArray[0] : MAIN_DICTIONARY_CATEGORY; 8840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (!MAIN_DICTIONARY_CATEGORY.equals(category)) { 8850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Not a main dictionary. We only auto-install main dictionaries, so we can return now. 8860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; 8870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (CommonPreferences.getCommonPreferences(context).contains(wordlistId)) { 8890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If some kind of settings has been done in the past for this specific id, then 8900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // this is not a candidate for auto-install. Because it already is either true, 8910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // in which case it may be installed or downloading or whatever, and we don't 8920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // need to care about it because it's already handled or being handled, or it's false 8930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // in which case it means the user explicitely turned it off and don't want to have 8940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // it installed. So we quit right away. 8950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; 8960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 8970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 8980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); 8990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ContentValues installCandidate = 9000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId); 9010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (MetadataDbHelper.STATUS_AVAILABLE 9020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard != installCandidate.getAsInteger(MetadataDbHelper.STATUS_COLUMN)) { 9030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // If it's not "AVAILABLE", we want to stop now. Because candidates for auto-install 9040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // are lists that we know are available, but we also know have never been installed. 9050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // It does obviously not concern already installed lists, or downloading lists, 9060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // or those that have been disabled, flagged as deleting... So anything else than 9070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // AVAILABLE means we don't auto-install. 9080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; 9090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 9110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (mayPrompt 9120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard && DOWNLOAD_OVER_METERED_SETTING_UNKNOWN 9130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard == getDownloadOverMeteredSetting(context)) { 9140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ConnectivityManager cm = 9150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 9160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (ConnectivityManagerCompatUtils.isActiveNetworkMetered(cm)) { 9170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard showDictionaryAvailableNotification(context, clientId, installCandidate); 9180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard return; 9190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 9220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // We decided against prompting the user for a decision. This may be because we were 9230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // explicitly asked not to, or because we are currently on wi-fi anyway, or because we 9240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // already know the answer to the question. We'll enqueue a request ; StartDownloadAction 9250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // knows to use the correct type of network according to the current settings. 9260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 9270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // Also note that once it's auto-installed, a word list will be marked as INSTALLED. It will 9280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // thus receive automatic updates if there are any, which is what we want. If the user does 9290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // not want this word list, they will have to go to the settings and change them, which will 9300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // change the shared preferences. So there is no way for a word list that has been 9310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // auto-installed once to get auto-installed again, and that's what we want. 9320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 9330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.StartDownloadAction(clientId, 9340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard WordListMetadata.createFromContentValues(installCandidate), false)); 9350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN); 9360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // We are in a content provider: we can't do any UI at all. We have to defer the displaying 9370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // itself to the service. Also, we only display this when the user does not have a 9380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // dictionary for this language already: we know that from the mayPrompt argument. 9390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (mayPrompt) { 9400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final Intent intent = new Intent(); 9410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.setClass(context, DictionaryService.class); 9420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.setAction(DictionaryService.SHOW_DOWNLOAD_TOAST_INTENT_ACTION); 9430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard intent.putExtra(DictionaryService.LOCALE_INTENT_ARGUMENT, localeString); 9440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard context.startService(intent); 9450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 9470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 9490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 9500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Marks the word list with the passed id as used. 9510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 9520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This will download/install the list as required. The action will see that the destination 9530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * word list is a valid list, and take appropriate action - in this case, mark it as used. 9540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @see ActionBatch.Action#execute 9550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 9560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context for using action batches. 9570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 9580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param wordlistId the id of the word list to mark as installed. 9590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list to mark as installed. 9600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param status the current status of the word list. 9610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param allowDownloadOnMeteredData whether to download even on metered data connection 9620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 9630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // The version argument is not used yet, because we don't need it to retrieve the information 9640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // we need. However, the pair (id, version) being the primary key to a word list in the database 9650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // it feels better for consistency to pass it, and some methods retrieving information about a 9660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // word list need it so we may need it in the future. 9670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void markAsUsed(final Context context, final String clientId, 9680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final int version, 9690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final int status, final boolean allowDownloadOnMeteredData) { 9700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> currentMetadata = 9710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.getCurrentMetadata(context, clientId); 9720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard WordListMetadata wordList = MetadataHandler.findWordListById(currentMetadata, wordlistId); 9730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == wordList) return; 9740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 9750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (MetadataDbHelper.STATUS_DISABLED == status 9760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard || MetadataDbHelper.STATUS_DELETING == status) { 9770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.EnableAction(clientId, wordList)); 9780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else if (MetadataDbHelper.STATUS_AVAILABLE == status) { 9790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.StartDownloadAction(clientId, wordList, 9800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard allowDownloadOnMeteredData)); 9810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } else { 9820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status); 9830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 9850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard signalNewDictionaryState(context); 9860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 9870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 9880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 9890cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Marks the word list with the passed id as unused. 9900cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 9910cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This leaves the file on the disk for ulterior use. The action will see that the destination 9920cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * word list is null, and take appropriate action - in this case, mark it as unused. 9930cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @see ActionBatch.Action#execute 9940cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 9950cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context for using action batches. 9960cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 9970cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param wordlistId the id of the word list to mark as installed. 9980cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list to mark as installed. 9990cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param status the current status of the word list. 10000cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 10010cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // The version and status arguments are not used yet, but this method matches its interface to 10020cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // markAsUsed for consistency. 10030cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void markAsUnused(final Context context, final String clientId, 10040cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final int version, final int status) { 10050cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> currentMetadata = 10060cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.getCurrentMetadata(context, clientId); 10070cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata wordList = 10080cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.findWordListById(currentMetadata, wordlistId); 10090cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == wordList) return; 10100cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 10110cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.DisableAction(clientId, wordList)); 10120cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 10130cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard signalNewDictionaryState(context); 10140cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 10150cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 10160cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 10170cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Marks the word list with the passed id as deleting. 10180cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10190cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This basically means that on the next chance there is (right away if Android Keyboard 10200cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * happens to be up, or the next time it gets up otherwise) the dictionary pack will 10210cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * supply an empty dictionary to it that will replace whatever dictionary is installed. 10220cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This allows to release the space taken by a dictionary (except for the few bytes the 10230cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * empty dictionary takes up), and override a built-in default dictionary so that we 10240cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * can fake delete a built-in dictionary. 10250cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10260cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to open the database on. 10270cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 10280cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param wordlistId the id of the word list to mark as deleted. 10290cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list to mark as deleted. 10300cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param status the current status of the word list. 10310cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 10320cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void markAsDeleting(final Context context, final String clientId, 10330cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final int version, final int status) { 10340cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> currentMetadata = 10350cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.getCurrentMetadata(context, clientId); 10360cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata wordList = 10370cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.findWordListById(currentMetadata, wordlistId); 10380cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == wordList) return; 10390cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 10400cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.DisableAction(clientId, wordList)); 10410cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.StartDeleteAction(clientId, wordList)); 10420cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 10430cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard signalNewDictionaryState(context); 10440cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 10450cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 10460cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 10470cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Marks the word list with the passed id as actually deleted. 10480cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10490cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This reverts to available status or deletes the row as appropriate. 10500cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10510cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to open the database on. 10520cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 10530cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param wordlistId the id of the word list to mark as deleted. 10540cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list to mark as deleted. 10550cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param status the current status of the word list. 10560cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 10570cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void markAsDeleted(final Context context, final String clientId, 10580cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final int version, final int status) { 10590cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final List<WordListMetadata> currentMetadata = 10600cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.getCurrentMetadata(context, clientId); 10610cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final WordListMetadata wordList = 10620cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataHandler.findWordListById(currentMetadata, wordlistId); 10630cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard if (null == wordList) return; 10640cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final ActionBatch actions = new ActionBatch(); 10650cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.add(new ActionBatch.FinishDeleteAction(clientId, wordList)); 10660cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard actions.execute(context, new LogProblemReporter(TAG)); 10670cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard signalNewDictionaryState(context); 10680cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 10690cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard 10700cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard /** 10710cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * Marks the word list with the passed id as broken. 10720cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10730cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * This effectively deletes the entry from the metadata. It doesn't prevent the same 10740cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * word list to be downloaded again at a later time if the same or a new version is 10750cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * available the next time we download the metadata. 10760cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * 10770cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param context the context to open the database on. 10780cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param clientId the id of the client. 10790cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param wordlistId the id of the word list to mark as broken. 10800cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard * @param version the version of the word list to mark as deleted. 10810cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard */ 10820cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard public static void markAsBroken(final Context context, final String clientId, 10830cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard final String wordlistId, final int version) { 10840cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard // TODO: do this on another thread to avoid blocking the UI. 10850cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId), 10860cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard wordlistId, version); 10870cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard } 10880cc0544a2995c7eb54a830ae54db60af89d4073dJean Chalard} 1089