aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
diff options
context:
space:
mode:
authorAmin Bandali <bandali@kelar.org>2024-12-16 21:45:41 -0500
committerAmin Bandali <bandali@kelar.org>2025-01-11 14:17:35 -0500
commite9a0e66716dab4dd3184d009d8920de1961efdfa (patch)
tree02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
parentfb3b9360d70596d7e921de8bf7d3ca99564a077e (diff)
downloadlatinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/com/android/inputmethod/dictionarypack/DictionaryService.java')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java280
1 files changed, 0 insertions, 280 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
deleted file mode 100644
index 5ab55bc44..000000000
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/**
- * 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.os.IBinder;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.common.LocaleUtils;
-
-import java.util.Locale;
-import java.util.Random;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nonnull;
-
-/**
- * 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.getSimpleName();
-
- /**
- * The package name, to use in the intent actions.
- */
- private static final String PACKAGE_NAME = "com.android.inputmethod.latin";
-
- /**
- * 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_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_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_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.
- */
- private ThreadPoolExecutor mExecutor;
- private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;
-
- @Override
- public void onCreate() {
- // By default, a thread pool executor does not timeout its core threads, so it will
- // never kill them when there isn't any work to do any more. That would mean the service
- // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
- // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
- // the process to be reclaimed by the system any time after that if it's not doing
- // anything else.
- // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
- // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
- // so we can't use that.
- mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
- WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
- TimeUnit.SECONDS /* unit for keepAliveTime */,
- new LinkedBlockingQueue<Runnable>() /* workQueue */);
- mExecutor.allowCoreThreadTimeOut(true);
- }
-
- @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).
- * The commands that can be spun an another thread will be executed serially, in order, on
- * a worker thread that is created on demand and terminates after a short while if there isn't
- * any work left to do.
- */
- @Override
- public synchronized int onStartCommand(final Intent intent, final int flags,
- final int startId) {
- final DictionaryService self = this;
- if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
- final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT);
- if (localeString == null) {
- Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped");
- } else {
- // This is a UI action, it can't be run in another thread
- showStartDownloadingToast(
- this, LocaleUtils.constructLocaleFromString(localeString));
- }
- } else {
- // If it's a command that does not require UI, arrange for the work to be done on a
- // separate thread, so that we can return right away. The executor will spawn a thread
- // if necessary, or reuse a thread that has become idle as appropriate.
- // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another
- // thread.
- mExecutor.submit(new Runnable() {
- @Override
- public void run() {
- dispatchBroadcast(self, intent);
- // Since calls to onStartCommand are serialized, the submissions to the executor
- // are serialized. That means we are guaranteed to call the stopSelfResult()
- // in the same order that we got them, so we don't need to take care of the
- // order.
- stopSelfResult(startId);
- }
- });
- }
- return Service.START_REDELIVER_INTENT;
- }
-
- static void dispatchBroadcast(final Context context, final Intent intent) {
- final String action = intent.getAction();
- if (DATE_CHANGED_INTENT_ACTION.equals(action)) {
- // 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 (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(action)) {
- // Intent to trigger an update now.
- UpdateHandler.tryUpdate(context);
- } else if (DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION.equals(action)) {
- // Initialize the client Db.
- final String mClientId = context.getString(R.string.dictionary_pack_client_id);
- BinaryDictionaryFileDumper.initializeClientRecordHelper(context, mClientId);
-
- // Updates the metadata and the download the dictionaries.
- UpdateHandler.tryUpdate(context);
- } 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_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_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_MILLIS);
- final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
- // Set the package name to ensure the PendingIntent is only delivered to trusted components
- updateIntent.setPackage(context.getPackageName());
- int pendingIntentFlags = PendingIntent.FLAG_CANCEL_CURRENT;
- if (android.os.Build.VERSION.SDK_INT >= 23) {
- pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
- }
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
- updateIntent, pendingIntentFlags);
-
- // 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);
- 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_MILLIS,
- * update metadata now - and possibly take subsequent update actions.
- */
- public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
- if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
- UpdateHandler.tryUpdate(context);
- }
-
- /**
- * Shows a toast informing the user that an automatic dictionary download is starting.
- */
- private static void showStartDownloadingToast(final Context context,
- @Nonnull 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();
- }
-}