diff options
author | 2013-03-15 19:00:51 +0900 | |
---|---|---|
committer | 2013-03-19 15:40:14 +0900 | |
commit | 0cc0544a2995c7eb54a830ae54db60af89d4073d (patch) | |
tree | 095745e7050c4a8f557f8967217f332874c78f04 /java/src/com/android/inputmethod/dictionarypack/DictionaryService.java | |
parent | 5b048292540b6fbbd8929a3622262f352245d464 (diff) | |
download | latinime-0cc0544a2995c7eb54a830ae54db60af89d4073d.tar.gz latinime-0cc0544a2995c7eb54a830ae54db60af89d4073d.tar.xz latinime-0cc0544a2995c7eb54a830ae54db60af89d4073d.zip |
Merge the dictionary pack in Latin IME.
Bug: 8161354
Change-Id: I17c23f56dd3bc2f27726556bf2c5a9d5520bd172
Diffstat (limited to 'java/src/com/android/inputmethod/dictionarypack/DictionaryService.java')
-rw-r--r-- | java/src/com/android/inputmethod/dictionarypack/DictionaryService.java | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java new file mode 100644 index 000000000..5817eb498 --- /dev/null +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -0,0 +1,242 @@ +/** + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.dictionarypack; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.text.format.DateUtils; +import android.util.Log; +import android.widget.Toast; + +import com.android.inputmethod.latin.R; + +import java.util.Locale; +import java.util.Random; + +/** + * Service that handles background tasks for the dictionary provider. + * + * This service provides the context for the long-running operations done by the + * dictionary provider. Those include: + * - Checking for the last update date and scheduling the next update. This runs every + * day around midnight, upon reception of the DATE_CHANGED_INTENT_ACTION broadcast. + * Every four days, it schedules an update of the metadata with the alarm manager. + * - Issuing the order to update the metadata. This runs every four days, between 0 and + * 6, upon reception of the UPDATE_NOW_INTENT_ACTION broadcast sent by the alarm manager + * as a result of the above action. + * - Handling a download that just ended. These come in two flavors: + * - Metadata is finished downloading. We should check whether there are new dictionaries + * available, and download those that we need that have new versions. + * - A dictionary file finished downloading. We should put the file ready for a client IME + * to access, and mark the current state as such. + */ +public final class DictionaryService extends Service { + private static final String TAG = DictionaryService.class.getName(); + + /** + * The package name, to use in the intent actions. + */ + private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin"; + + /** + * The action of the intent to tell the dictionary provider to update now. + */ + private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW"; + + /** + * The action of the date changing, used to schedule a periodic freshness check + */ + private static final String DATE_CHANGED_INTENT_ACTION = + Intent.ACTION_DATE_CHANGED; + + /** + * The action of displaying a toast to warn the user an automatic download is starting. + */ + /* package */ static final String SHOW_DOWNLOAD_TOAST_INTENT_ACTION = + PACKAGE_NAME + ".SHOW_DOWNLOAD_TOAST_INTENT_ACTION"; + + /** + * A locale argument, as a String. + */ + /* package */ static final String LOCALE_INTENT_ARGUMENT = "locale"; + + /** + * How often, in milliseconds, we want to update the metadata. This is a + * floor value; actually, it may happen several hours later, or even more. + */ + private static final long UPDATE_FREQUENCY = 4 * DateUtils.DAY_IN_MILLIS; + + /** + * We are waked around midnight, local time. We want to wake between midnight and 6 am, + * roughly. So use a random time between 0 and this delay. + */ + private static final int MAX_ALARM_DELAY = 6 * ((int)AlarmManager.INTERVAL_HOUR); + + /** + * How long we consider a "very long time". If no update took place in this time, + * the content provider will trigger an update in the background. + */ + private static final long VERY_LONG_TIME = 14 * DateUtils.DAY_IN_MILLIS; + + /** + * The last seen start Id. This must be stored because we must only call stopSelfResult() with + * the last seen Id, or the service won't stop. + */ + private int mLastSeenStartId; + + /** + * The command count. We need this because we need to not call stopSelfResult() while we still + * have commands running. + */ + private int mCommandCount; + + @Override + public void onCreate() { + mLastSeenStartId = 0; + mCommandCount = 0; + } + + @Override + public void onDestroy() { + } + + @Override + public IBinder onBind(Intent intent) { + // This service cannot be bound + return null; + } + + /** + * Executes an explicit command. + * + * This is the entry point for arbitrary commands that are executed upon reception of certain + * events that should be executed on the context of this service. The supported commands are: + * - Check last update time and possibly schedule an update of the data for later. + * This is triggered every day, upon reception of the DATE_CHANGED_INTENT_ACTION broadcast. + * - Update data NOW. + * This is normally received upon trigger of the scheduled update. + * - Handle a finished download. + * This executes the actions that must be taken after a file (metadata or dictionary data + * has been downloaded (or failed to download). + */ + @Override + public synchronized int onStartCommand(final Intent intent, final int flags, + final int startId) { + final DictionaryService self = this; + mLastSeenStartId = startId; + mCommandCount += 1; + if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) { + // This is a UI action, it can't be run in another thread + showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString( + intent.getStringExtra(LOCALE_INTENT_ARGUMENT))); + } else { + // If it's a command that does not require UI, create a thread to do the work + // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands. + new Thread("updateOrFinishDownload") { + @Override + public void run() { + dispatchBroadcast(self, intent); + synchronized(self) { + if (--mCommandCount <= 0) { + if (!stopSelfResult(mLastSeenStartId)) { + Log.e(TAG, "Can't stop ourselves"); + } + } + } + } + }.start(); + } + return Service.START_REDELIVER_INTENT; + } + + private static void dispatchBroadcast(final Context context, final Intent intent) { + if (DATE_CHANGED_INTENT_ACTION.equals(intent.getAction())) { + // This happens when the date of the device changes. This normally happens + // at midnight local time, but it may happen if the user changes the date + // by hand or something similar happens. + checkTimeAndMaybeSetupUpdateAlarm(context); + } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { + // Intent to trigger an update now. + UpdateHandler.update(context, false); + } else { + UpdateHandler.downloadFinished(context, intent); + } + } + + /** + * Setups an alarm to check for updates if an update is due. + */ + 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; + + PrivateLog.log("Date changed - registering alarm", context); + AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + + // Best effort to wake between midnight and MAX_ALARM_DELAY 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 Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, + updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); + + // We set the alarm in the type that doesn't forcefully wake the device + // from sleep, but fires the next time the device actually wakes for any + // other reason. + if (null != alarmManager) alarmManager.set(AlarmManager.RTC, alarmTime, pendingIntent); + } + + /** + * Utility method to decide whether the last update is older than a certain time. + * + * @return true if at least `time' milliseconds have elapsed since last update, false otherwise. + */ + private static boolean isLastUpdateAtLeastThisOld(final Context context, final long time) { + final long now = System.currentTimeMillis(); + final long lastUpdate = MetadataDbHelper.getOldestUpdateTime(context); + PrivateLog.log("Last update was " + lastUpdate, context); + return lastUpdate + time < now; + } + + /** + * 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, + * update metadata now - and possibly take subsequent update actions. + */ + public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) { + if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return; + UpdateHandler.update(context, false); + } + + /** + * Shows a toast informing the user that an automatic dictionary download is starting. + */ + private static void showStartDownloadingToast(final Context context, final Locale locale) { + final String toastText = String.format( + context.getString(R.string.toast_downloading_suggestions), + locale.getDisplayName()); + Toast.makeText(context, toastText, Toast.LENGTH_LONG).show(); + } +} |