Loading java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +19 −9 Original line number Diff line number Diff line Loading @@ -76,19 +76,29 @@ public final class DictionaryService extends Service { * How often, in milliseconds, we want to update the metadata. This is a * floor value; actually, it may happen several hours later, or even more. */ private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4); private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4); /** * We are waked around midnight, local time. We want to wake between midnight and 6 am, * roughly. So use a random time between 0 and this delay. */ private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6); private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6); /** * How long we consider a "very long time". If no update took place in this time, * the content provider will trigger an update in the background. */ private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14); private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14); /** * After starting a download, how long we wait before considering it may be stuck. After this * period is elapsed, if the keyboard tries to download again, then we cancel and re-register * the request; if it's within this time, we just leave it be. * It's important to note that we do not re-submit the request merely because the time is up. * This is only to decide whether to cancel the old one and re-requesting when the keyboard * fires a new request for the same data. */ public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30); /** * An executor that serializes tasks given to it. Loading Loading @@ -188,16 +198,16 @@ public final class DictionaryService extends Service { */ private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) { // Of all clients, if the one that hasn't been updated for the longest // is still more recent than UPDATE_FREQUENCY, do nothing. if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return; // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing. if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return; PrivateLog.log("Date changed - registering alarm"); AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning. // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning. // It doesn't matter too much if this is very inexact. final long now = System.currentTimeMillis(); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS); final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); Loading @@ -223,11 +233,11 @@ public final class DictionaryService extends Service { /** * Refreshes data if it hasn't been refreshed in a very long time. * * This will check the last update time, and if it's been more than VERY_LONG_TIME, * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS, * update metadata now - and possibly take subsequent update actions. */ public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return; if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return; UpdateHandler.tryUpdate(context, false); } Loading java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.dictionarypack; /** * A simple container of download ID and download start date. */ public class DownloadIdAndStartDate { public final long mId; public final long mStartDate; public DownloadIdAndStartDate(final long id, final long startDate) { mId = id; mStartDate = startDate; } } java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +7 −6 Original line number Diff line number Diff line Loading @@ -433,18 +433,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper { * * @param context a context instance to open the database on * @param uri the URI to retrieve the metadata download ID of * @return the metadata download ID, or NOT_AN_ID if no download is in progress * @return the download id and start date, or null if the URL is not known */ public static long getMetadataDownloadIdForURI(final Context context, final String uri) { public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI( final Context context, final String uri) { SQLiteDatabase defaultDb = getDb(context, null); final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, new String[] { CLIENT_PENDINGID_COLUMN }, new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN }, CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri }, null, null, null, null); try { if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID; return cursor.getInt(0); // Only one column, return it if (!cursor.moveToFirst()) return null; return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1)); } finally { cursor.close(); } Loading Loading @@ -922,6 +922,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { final long downloadId) { final ContentValues values = new ContentValues(); values.put(CLIENT_PENDINGID_COLUMN, downloadId); values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis()); final SQLiteDatabase defaultDb = getDb(context, ""); final Cursor cursor = MetadataDbHelper.queryClientIds(context); if (null == cursor) return; Loading java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +24 −11 Original line number Diff line number Diff line Loading @@ -251,12 +251,16 @@ public final class UpdateHandler { res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI)); final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); cancelUpdateWithDownloadManager(context, metadataUri, manager); if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) { // We already have a recent download in progress. Don't register a new download. return; } final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(metadataRequest); DebugLogUtils.l("Metadata download requested with id", downloadId); // If there is already a download in progress, it's been there for a while and // If there is still a download in progress, it's been there for a while and // there is probably something wrong with download manager. It's best to just // overwrite the id and request it again. If the old one happens to finish // anyway, we don't know about its ID any more, so the downloadFinished Loading @@ -267,21 +271,29 @@ public final class UpdateHandler { } /** * Cancels downloading a file, if there is one for this URI. * Cancels downloading a file if there is one for this URI and it's too long. * * If we are not currently downloading the file at this URI, this is a no-op. * * @param context the context to open the database on * @param metadataUri the URI to cancel * @param manager an wrapped instance of DownloadManager * @param graceTime if there was a download started less than this many milliseconds, don't * cancel and return true * @return whether the download is still active */ private static void cancelUpdateWithDownloadManager(final Context context, final String metadataUri, final DownloadManagerWrapper manager) { private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context, final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) { synchronized (sSharedIdProtector) { final long metadataDownloadId = MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri); if (NOT_AN_ID == metadataDownloadId) return; manager.remove(metadataDownloadId); final DownloadIdAndStartDate metadataDownloadIdAndStartDate = MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri); if (null == metadataDownloadIdAndStartDate) return false; if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false; if (metadataDownloadIdAndStartDate.mStartDate + graceTime > System.currentTimeMillis()) { return true; } manager.remove(metadataDownloadIdAndStartDate.mId); writeMetadataDownloadId(context, metadataUri, NOT_AN_ID); } // Consider a cancellation as a failure. As such, inform listeners that the download Loading @@ -289,6 +301,7 @@ public final class UpdateHandler { for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { listener.downloadedMetadata(false); } return false; } /** Loading @@ -303,7 +316,7 @@ public final class UpdateHandler { public static void cancelUpdate(final Context context, final String clientId) { final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); cancelUpdateWithDownloadManager(context, metadataUri, manager); maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */); } /** Loading Loading @@ -387,7 +400,7 @@ public final class UpdateHandler { // If any of these is metadata, we should update the DB boolean hasMetadata = false; for (DownloadRecord record : downloadRecords) { if (null == record.mAttributes) { if (record.isMetadata()) { hasMetadata = true; break; } Loading Loading
java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +19 −9 Original line number Diff line number Diff line Loading @@ -76,19 +76,29 @@ public final class DictionaryService extends Service { * How often, in milliseconds, we want to update the metadata. This is a * floor value; actually, it may happen several hours later, or even more. */ private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4); private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4); /** * We are waked around midnight, local time. We want to wake between midnight and 6 am, * roughly. So use a random time between 0 and this delay. */ private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6); private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6); /** * How long we consider a "very long time". If no update took place in this time, * the content provider will trigger an update in the background. */ private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14); private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14); /** * After starting a download, how long we wait before considering it may be stuck. After this * period is elapsed, if the keyboard tries to download again, then we cancel and re-register * the request; if it's within this time, we just leave it be. * It's important to note that we do not re-submit the request merely because the time is up. * This is only to decide whether to cancel the old one and re-requesting when the keyboard * fires a new request for the same data. */ public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30); /** * An executor that serializes tasks given to it. Loading Loading @@ -188,16 +198,16 @@ public final class DictionaryService extends Service { */ private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) { // Of all clients, if the one that hasn't been updated for the longest // is still more recent than UPDATE_FREQUENCY, do nothing. if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return; // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing. if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return; PrivateLog.log("Date changed - registering alarm"); AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning. // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning. // It doesn't matter too much if this is very inexact. final long now = System.currentTimeMillis(); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS); final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); Loading @@ -223,11 +233,11 @@ public final class DictionaryService extends Service { /** * Refreshes data if it hasn't been refreshed in a very long time. * * This will check the last update time, and if it's been more than VERY_LONG_TIME, * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS, * update metadata now - and possibly take subsequent update actions. */ public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return; if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return; UpdateHandler.tryUpdate(context, false); } Loading
java/src/com/android/inputmethod/dictionarypack/DownloadIdAndStartDate.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.dictionarypack; /** * A simple container of download ID and download start date. */ public class DownloadIdAndStartDate { public final long mId; public final long mStartDate; public DownloadIdAndStartDate(final long id, final long startDate) { mId = id; mStartDate = startDate; } }
java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +7 −6 Original line number Diff line number Diff line Loading @@ -433,18 +433,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper { * * @param context a context instance to open the database on * @param uri the URI to retrieve the metadata download ID of * @return the metadata download ID, or NOT_AN_ID if no download is in progress * @return the download id and start date, or null if the URL is not known */ public static long getMetadataDownloadIdForURI(final Context context, final String uri) { public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI( final Context context, final String uri) { SQLiteDatabase defaultDb = getDb(context, null); final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, new String[] { CLIENT_PENDINGID_COLUMN }, new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN }, CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri }, null, null, null, null); try { if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID; return cursor.getInt(0); // Only one column, return it if (!cursor.moveToFirst()) return null; return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1)); } finally { cursor.close(); } Loading Loading @@ -922,6 +922,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { final long downloadId) { final ContentValues values = new ContentValues(); values.put(CLIENT_PENDINGID_COLUMN, downloadId); values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis()); final SQLiteDatabase defaultDb = getDb(context, ""); final Cursor cursor = MetadataDbHelper.queryClientIds(context); if (null == cursor) return; Loading
java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +24 −11 Original line number Diff line number Diff line Loading @@ -251,12 +251,16 @@ public final class UpdateHandler { res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI)); final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); cancelUpdateWithDownloadManager(context, metadataUri, manager); if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) { // We already have a recent download in progress. Don't register a new download. return; } final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(metadataRequest); DebugLogUtils.l("Metadata download requested with id", downloadId); // If there is already a download in progress, it's been there for a while and // If there is still a download in progress, it's been there for a while and // there is probably something wrong with download manager. It's best to just // overwrite the id and request it again. If the old one happens to finish // anyway, we don't know about its ID any more, so the downloadFinished Loading @@ -267,21 +271,29 @@ public final class UpdateHandler { } /** * Cancels downloading a file, if there is one for this URI. * Cancels downloading a file if there is one for this URI and it's too long. * * If we are not currently downloading the file at this URI, this is a no-op. * * @param context the context to open the database on * @param metadataUri the URI to cancel * @param manager an wrapped instance of DownloadManager * @param graceTime if there was a download started less than this many milliseconds, don't * cancel and return true * @return whether the download is still active */ private static void cancelUpdateWithDownloadManager(final Context context, final String metadataUri, final DownloadManagerWrapper manager) { private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context, final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) { synchronized (sSharedIdProtector) { final long metadataDownloadId = MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri); if (NOT_AN_ID == metadataDownloadId) return; manager.remove(metadataDownloadId); final DownloadIdAndStartDate metadataDownloadIdAndStartDate = MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri); if (null == metadataDownloadIdAndStartDate) return false; if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false; if (metadataDownloadIdAndStartDate.mStartDate + graceTime > System.currentTimeMillis()) { return true; } manager.remove(metadataDownloadIdAndStartDate.mId); writeMetadataDownloadId(context, metadataUri, NOT_AN_ID); } // Consider a cancellation as a failure. As such, inform listeners that the download Loading @@ -289,6 +301,7 @@ public final class UpdateHandler { for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { listener.downloadedMetadata(false); } return false; } /** Loading @@ -303,7 +316,7 @@ public final class UpdateHandler { public static void cancelUpdate(final Context context, final String clientId) { final DownloadManagerWrapper manager = new DownloadManagerWrapper(context); final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); cancelUpdateWithDownloadManager(context, metadataUri, manager); maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */); } /** Loading Loading @@ -387,7 +400,7 @@ public final class UpdateHandler { // If any of these is metadata, we should update the DB boolean hasMetadata = false; for (DownloadRecord record : downloadRecords) { if (null == record.mAttributes) { if (record.isMetadata()) { hasMetadata = true; break; } Loading