diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2ac76d35..fbedfe47 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,17 +18,12 @@
coreApp="true"
package="org.dslul.openboard.inputmethod.latin">
-
-
-
-
-
-
-
-
-
-
-
@@ -162,22 +145,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
dictFiles =
getDictionaryWordListsForLocale(clientId, locale);
// TODO: pass clientId to the following function
- DictionaryService.updateNowIfNotUpdatedInAVeryLongTime(getContext());
if (null != dictFiles && dictFiles.size() > 0) {
PrivateLog.log("Returned " + dictFiles.size() + " files");
return new ResourcePathCursor(dictFiles);
@@ -364,18 +363,8 @@ public final class DictionaryProvider extends ContentProvider {
final String[] wordListIdArray =
TextUtils.split(wordListId, ID_CATEGORY_SEPARATOR);
final String wordListCategory;
- if (2 == wordListIdArray.length) {
- // This is at the category:manual_id format.
- wordListCategory = wordListIdArray[0];
- // We don't need to read wordListIdArray[1] here, because it's irrelevant to
- // word list selection - it's just a name we use to identify which data file
- // is a newer version of which word list. We do however return the full id
- // string for each selected word list, so in this sense we are 'using' it.
- } else {
- // This does not contain a colon, like the old format does. Old-format IDs
- // always point to main dictionaries, so we force the main category upon it.
- wordListCategory = UpdateHandler.MAIN_DICTIONARY_CATEGORY;
- }
+ // This is at the category:manual_id format.
+ wordListCategory = wordListIdArray[0];
final String wordListLocale = results.getString(localeIndex);
final String wordListLocalFilename = results.getString(localFileNameIndex);
final String wordListRawChecksum = results.getString(rawChecksumIndex);
@@ -403,10 +392,6 @@ public final class DictionaryProvider extends ContentProvider {
if (!f.isFile()) {
continue;
}
- } else if (MetadataDbHelper.STATUS_AVAILABLE == wordListStatus) {
- // The locale is the id for the main dictionary.
- UpdateHandler.installIfNeverRequested(context, clientId, wordListId);
- continue;
}
final WordListInfo currentBestMatch = dicts.get(wordListCategory);
if (null == currentBestMatch
@@ -457,26 +442,6 @@ public final class DictionaryProvider extends ContentProvider {
}
final int status = wordList.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
final int version = wordList.getAsInteger(MetadataDbHelper.VERSION_COLUMN);
- if (MetadataDbHelper.STATUS_DELETING == status) {
- UpdateHandler.markAsDeleted(getContext(), clientId, wordlistId, version, status);
- return 1;
- }
- if (MetadataDbHelper.STATUS_INSTALLED == status) {
- final String result = uri.getQueryParameter(QUERY_PARAMETER_DELETE_RESULT);
- if (QUERY_PARAMETER_FAILURE.equals(result)) {
- if (DEBUG) {
- Log.d(TAG,
- "Dictionary is broken, attempting to retry download & installation.");
- }
- UpdateHandler.markAsBrokenOrRetrying(getContext(), clientId, wordlistId, version);
- }
- final String localFilename =
- wordList.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN);
- final File f = getContext().getFileStreamPath(localFilename);
- // f.delete() returns true if the file was successfully deleted, false otherwise
- return f.delete() ? 1 : 0;
- }
- Log.e(TAG, "Attempt to delete a file whose status is " + status);
return 0;
}
@@ -512,11 +477,6 @@ public final class DictionaryProvider extends ContentProvider {
} catch (final BadFormatException e) {
Log.w(TAG, "Not enough information to insert this dictionary " + values, e);
}
- // We just received new information about the list of dictionary for this client.
- // For all intents and purposes, this is new metadata, so we should publish it
- // so that any listeners (like the Settings interface for example) can update
- // themselves.
- UpdateHandler.publishUpdateMetadataCompleted(getContext(), true);
break;
case DICTIONARY_V1_WHOLE_LIST:
case DICTIONARY_V1_DICT_INFO:
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionaryService.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionaryService.java
deleted file mode 100644
index 55e4484c..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionaryService.java
+++ /dev/null
@@ -1,274 +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 org.dslul.openboard.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 org.dslul.openboard.inputmethod.latin.BinaryDictionaryFileDumper;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.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 = "org.dslul.openboard.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() /* 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);
- 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);
- 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();
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionarySettingsFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 18d4f2ee..7ed924fb 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -53,8 +53,7 @@ import java.util.TreeMap;
/**
* Preference screen.
*/
-public final class DictionarySettingsFragment extends PreferenceFragment
- implements UpdateHandler.UpdateEventListener {
+public final class DictionarySettingsFragment extends PreferenceFragment {
private static final String TAG = DictionarySettingsFragment.class.getSimpleName();
static final private String DICT_LIST_ID = "list";
@@ -72,13 +71,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
// never null
private TreeMap mCurrentPreferenceMap = new TreeMap<>();
- private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- refreshNetworkState();
- }
- };
-
/**
* Empty constructor for fragment generation.
*/
@@ -105,41 +97,12 @@ public final class DictionarySettingsFragment extends PreferenceFragment
setHasOptionsMenu(true);
}
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- new AsyncTask() {
- @Override
- protected String doInBackground(Void... params) {
- return MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
- }
-
- @Override
- protected void onPostExecute(String metadataUri) {
- // We only add the "Refresh" button if we have a non-empty URL to refresh from. If
- // the URL is empty, of course we can't refresh so it makes no sense to display
- // this.
- if (!TextUtils.isEmpty(metadataUri)) {
- if (mUpdateNowMenu == null) {
- mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0,
- R.string.check_for_updates_now);
- mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- }
- refreshNetworkState();
- }
- }
- }.execute();
- }
-
@Override
public void onResume() {
super.onResume();
mChangedSettings = false;
- UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
final IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
- refreshNetworkState();
new Thread("onResume") {
@Override
@@ -161,8 +124,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
public void onPause() {
super.onPause();
final Activity activity = getActivity();
- UpdateHandler.unregisterUpdateEventListener(this);
- activity.unregisterReceiver(mConnectivityChangedReceiver);
if (mChangedSettings) {
final Intent newDictBroadcast =
new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
@@ -171,36 +132,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
}
}
- @Override
- public void downloadedMetadata(final boolean succeeded) {
- stopLoadingAnimation();
- if (!succeeded) return; // If the download failed nothing changed, so no need to refresh
- new Thread("refreshInterface") {
- @Override
- public void run() {
- refreshInterface();
- }
- }.start();
- }
-
- @Override
- public void wordListDownloadFinished(final String wordListId, final boolean succeeded) {
- final WordListPreference pref = findWordListPreference(wordListId);
- if (null == pref) return;
- // TODO: Report to the user if !succeeded
- final Activity activity = getActivity();
- if (null == activity) return;
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- // We have to re-read the db in case the description has changed, and to
- // find out what state it ended up if the download wasn't successful
- // TODO: don't redo everything, only re-read and set this word list status
- refreshInterface();
- }
- });
- }
-
private WordListPreference findWordListPreference(final String id) {
final PreferenceGroup prefScreen = getPreferenceScreen();
if (null == prefScreen) {
@@ -220,17 +151,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
return null;
}
- @Override
- public void updateCycleCompleted() {}
-
- void refreshNetworkState() {
- /*
- NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
- boolean isConnected = null == info ? false : info.isConnected();
- */
- if (null != mUpdateNowMenu) mUpdateNowMenu.setEnabled(false);
- }
-
void refreshInterface() {
final Activity activity = getActivity();
if (null == activity) return;
@@ -243,8 +163,6 @@ public final class DictionarySettingsFragment extends PreferenceFragment
public void run() {
// TODO: display this somewhere
// if (0 != lastUpdate) mUpdateNowPreference.setSummary(updateNowSummary);
- refreshNetworkState();
-
removeAnyDictSettings(prefScreen);
int i = 0;
for (Preference preference : prefList) {
@@ -361,80 +279,4 @@ public final class DictionarySettingsFragment extends PreferenceFragment
}
}
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case MENU_UPDATE_NOW:
- if (View.GONE == mLoadingView.getVisibility()) {
- startRefresh();
- } else {
- cancelRefresh();
- }
- return true;
- }
- return false;
- }
-
- private void startRefresh() {
- startLoadingAnimation();
- mChangedSettings = true;
- UpdateHandler.registerUpdateEventListener(this);
- final Activity activity = getActivity();
- new Thread("updateByHand") {
- @Override
- public void run() {
- // We call tryUpdate(), which returns whether we could successfully start an update.
- // If we couldn't, we'll never receive the end callback, so we stop the loading
- // animation and return to the previous screen.
- if (!UpdateHandler.tryUpdate(activity)) {
- stopLoadingAnimation();
- }
- }
- }.start();
- }
-
- private void cancelRefresh() {
- UpdateHandler.unregisterUpdateEventListener(this);
- final Context context = getActivity();
- new Thread("cancelByHand") {
- @Override
- public void run() {
- UpdateHandler.cancelUpdate(context, mClientId);
- stopLoadingAnimation();
- }
- }.start();
- }
-
- private void startLoadingAnimation() {
- mLoadingView.setVisibility(View.VISIBLE);
- getView().setVisibility(View.GONE);
- // We come here when the menu element is pressed so presumably it can't be null. But
- // better safe than sorry.
- if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
- }
-
- void stopLoadingAnimation() {
- final View preferenceView = getView();
- final Activity activity = getActivity();
- if (null == activity) return;
- final View loadingView = mLoadingView;
- final MenuItem updateNowMenu = mUpdateNowMenu;
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- loadingView.setVisibility(View.GONE);
- preferenceView.setVisibility(View.VISIBLE);
- loadingView.startAnimation(AnimationUtils.loadAnimation(
- activity, android.R.anim.fade_out));
- preferenceView.startAnimation(AnimationUtils.loadAnimation(
- activity, android.R.anim.fade_in));
- // The menu is created by the framework asynchronously after the activity,
- // which means it's possible to have the activity running but the menu not
- // created yet - hence the necessity for a null check here.
- if (null != updateNowMenu) {
- updateNowMenu.setTitle(R.string.check_for_updates_now);
- }
- }
- });
- }
}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadIdAndStartDate.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadIdAndStartDate.java
deleted file mode 100644
index bdc92148..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadIdAndStartDate.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 org.dslul.openboard.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;
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadManagerWrapper.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadManagerWrapper.java
deleted file mode 100644
index 75caff66..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadManagerWrapper.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.dictionarypack;
-
-import android.app.DownloadManager;
-import android.app.DownloadManager.Query;
-import android.app.DownloadManager.Request;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.FileNotFoundException;
-
-import javax.annotation.Nullable;
-
-/**
- * A class to help with calling DownloadManager methods.
- *
- * Mostly, the problem here is that most methods from DownloadManager may throw SQL exceptions if
- * they can't open the database on disk. We want to avoid crashing in these cases but can't do
- * much more, so this class insulates the callers from these. SQLiteException also inherit from
- * RuntimeException so they are unchecked :(
- * While we're at it, we also insulate callers from the cases where DownloadManager is disabled,
- * and getSystemService returns null.
- */
-public class DownloadManagerWrapper {
- private final static String TAG = DownloadManagerWrapper.class.getSimpleName();
- private final DownloadManager mDownloadManager;
-
- public DownloadManagerWrapper(final Context context) {
- this((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE));
- }
-
- private DownloadManagerWrapper(final DownloadManager downloadManager) {
- mDownloadManager = downloadManager;
- }
-
- public void remove(final long... ids) {
- try {
- if (null != mDownloadManager) {
- mDownloadManager.remove(ids);
- }
- } catch (IllegalArgumentException e) {
- // This is expected to happen on boot when the device is encrypted.
- } catch (SQLiteException e) {
- // We couldn't remove the file from DownloadManager. Apparently, the database can't
- // be opened. It may be a problem with file system corruption. In any case, there is
- // not much we can do apart from avoiding crashing.
- Log.e(TAG, "Can't remove files with ID " + ids + " from download manager", e);
- }
- }
-
- public ParcelFileDescriptor openDownloadedFile(final long fileId) throws FileNotFoundException {
- try {
- if (null != mDownloadManager) {
- return mDownloadManager.openDownloadedFile(fileId);
- }
- } catch (IllegalArgumentException e) {
- // This is expected to happen on boot when the device is encrypted.
- } catch (SQLiteException e) {
- Log.e(TAG, "Can't open downloaded file with ID " + fileId, e);
- }
- // We come here if mDownloadManager is null or if an exception was thrown.
- throw new FileNotFoundException();
- }
-
- @Nullable
- public Cursor query(final Query query) {
- try {
- if (null != mDownloadManager) {
- return mDownloadManager.query(query);
- }
- } catch (IllegalArgumentException e) {
- // This is expected to happen on boot when the device is encrypted.
- } catch (SQLiteException e) {
- Log.e(TAG, "Can't query the download manager", e);
- }
- // We come here if mDownloadManager is null or if an exception was thrown.
- return null;
- }
-
- public long enqueue(final Request request) {
- try {
- if (null != mDownloadManager) {
- return mDownloadManager.enqueue(request);
- }
- } catch (IllegalArgumentException e) {
- // This is expected to happen on boot when the device is encrypted.
- } catch (SQLiteException e) {
- Log.e(TAG, "Can't enqueue a request with the download manager", e);
- }
- return 0;
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadOverMeteredDialog.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
deleted file mode 100644
index a3e518df..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadOverMeteredDialog.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2012 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 org.dslul.openboard.inputmethod.dictionarypack;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.Html;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import org.dslul.openboard.inputmethod.annotations.ExternallyReferenced;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
-
-import javax.annotation.Nullable;
-
-/**
- * This implements the dialog for asking the user whether it's okay to download dictionaries over
- * a metered connection or not (e.g. their mobile data plan).
- */
-public final class DownloadOverMeteredDialog extends Activity {
- final public static String CLIENT_ID_KEY = "client_id";
- final public static String WORDLIST_TO_DOWNLOAD_KEY = "wordlist_to_download";
- final public static String SIZE_KEY = "size";
- final public static String LOCALE_KEY = "locale";
- private String mClientId;
- private String mWordListToDownload;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final Intent intent = getIntent();
- mClientId = intent.getStringExtra(CLIENT_ID_KEY);
- mWordListToDownload = intent.getStringExtra(WORDLIST_TO_DOWNLOAD_KEY);
- final String localeString = intent.getStringExtra(LOCALE_KEY);
- final long size = intent.getIntExtra(SIZE_KEY, 0);
- setContentView(R.layout.download_over_metered);
- setTexts(localeString, size);
- }
-
- private void setTexts(@Nullable final String localeString, final long size) {
- final String promptFormat = getString(R.string.should_download_over_metered_prompt);
- final String allowButtonFormat = getString(R.string.download_over_metered);
- final String language = (null == localeString) ? ""
- : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
- final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt);
- prompt.setText(Html.fromHtml(String.format(promptFormat, language)));
- final Button allowButton = (Button)findViewById(R.id.allow_button);
- allowButton.setText(String.format(allowButtonFormat, ((float)size)/(1024*1024)));
- }
-
- // This method is externally referenced from layout/download_over_metered.xml using onClick
- // attribute of Button.
- @ExternallyReferenced
- @SuppressWarnings("unused")
- public void onClickDeny(final View v) {
- UpdateHandler.setDownloadOverMeteredSetting(this, false);
- finish();
- }
-
- // This method is externally referenced from layout/download_over_metered.xml using onClick
- // attribute of Button.
- @ExternallyReferenced
- @SuppressWarnings("unused")
- public void onClickAllow(final View v) {
- UpdateHandler.setDownloadOverMeteredSetting(this, true);
- UpdateHandler.installIfNeverRequested(this, mClientId, mWordListToDownload);
- finish();
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadRecord.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadRecord.java
deleted file mode 100644
index 2c2a4a65..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/DownloadRecord.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 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 org.dslul.openboard.inputmethod.dictionarypack;
-
-import android.content.ContentValues;
-
-/**
- * Struct class to encapsulate a client ID with content values about a download.
- */
-public class DownloadRecord {
- public final String mClientId;
- // Only word lists have attributes, and the ContentValues should contain the same
- // keys as they do for all MetadataDbHelper functions. Since only word lists have
- // attributes, a null pointer here means this record represents metadata.
- public final ContentValues mAttributes;
- public DownloadRecord(final String clientId, final ContentValues attributes) {
- mClientId = clientId;
- mAttributes = attributes;
- }
- public boolean isMetadata() {
- return null == mAttributes;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/EventHandler.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/EventHandler.java
deleted file mode 100644
index b329344a..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/EventHandler.java
+++ /dev/null
@@ -1,46 +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 org.dslul.openboard.inputmethod.dictionarypack;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public final class EventHandler extends BroadcastReceiver {
- /**
- * Receives a intent broadcast.
- *
- * We receive every day a broadcast indicating that date changed.
- * Then we wait a random amount of time before actually registering
- * the download, to avoid concentrating too many accesses around
- * midnight in more populated timezones.
- * We receive all broadcasts here, so this can be either the DATE_CHANGED broadcast, the
- * UPDATE_NOW private broadcast that we receive when the time-randomizing alarm triggers
- * for regular update or from applications that want to test the dictionary pack, or a
- * broadcast from DownloadManager telling that a download has finished.
- * See inside of AndroidManifest.xml to see which events are caught.
- * Also @see {@link BroadcastReceiver#onReceive(Context, Intent)}
- *
- * @param context the context of the application.
- * @param intent the intent that was broadcast.
- */
- @Override
- public void onReceive(final Context context, final Intent intent) {
- intent.setClass(context, DictionaryService.class);
- context.startService(intent);
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataDbHelper.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataDbHelper.java
index e4f6e8fd..4b5df472 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -211,7 +211,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
final ContentValues defaultMetadataValues = new ContentValues();
defaultMetadataValues.put(CLIENT_CLIENT_ID_COLUMN, "");
defaultMetadataValues.put(CLIENT_METADATA_URI_COLUMN, defaultMetadataUri);
- defaultMetadataValues.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
db.insert(CLIENT_TABLE_NAME, null, defaultMetadataValues);
}
}
@@ -429,31 +428,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
}
- /**
- * Get the metadata download ID for a metadata URI.
- *
- * This will retrieve the download ID for the metadata file that has the passed URI.
- * If this URI is not being downloaded right now, it will return NOT_AN_ID.
- *
- * @param context a context instance to open the database on
- * @param uri the URI to retrieve the metadata download ID of
- * @return the download id and start date, or null if the URL is not known
- */
- 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, CLIENT_LAST_UPDATE_DATE_COLUMN },
- CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
- null, null, null, null);
- try {
- if (!cursor.moveToFirst()) return null;
- return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1));
- } finally {
- cursor.close();
- }
- }
-
public static long getOldestUpdateTime(final Context context) {
SQLiteDatabase defaultDb = getDb(context, null);
final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
@@ -541,9 +515,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0);
// Smallest possible version unless specified
if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1);
- // Assume current format unless specified
- if (null == result.get(FORMATVERSION_COLUMN))
- result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION);
// No flags unless specified
if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0);
return result;
@@ -656,48 +627,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
}
- /**
- * Given a specific download ID, return records for all pending downloads across all clients.
- *
- * If several clients use the same metadata URL, we know to only download it once, and
- * dispatch the update process across all relevant clients when the download ends. This means
- * several clients may share a single download ID if they share a metadata URI.
- * The dispatching is done in
- * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which
- * finds out about the list of relevant clients by calling this method.
- *
- * @param context a context instance to open the databases
- * @param downloadId the download ID to query about
- * @return the list of records. Never null, but may be empty.
- */
- public static ArrayList getDownloadRecordsForDownloadId(final Context context,
- final long downloadId) {
- final SQLiteDatabase defaultDb = getDb(context, "");
- final ArrayList results = new ArrayList<>();
- final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, CLIENT_TABLE_COLUMNS,
- null, null, null, null, null);
- try {
- if (!cursor.moveToFirst()) return results;
- final int clientIdIndex = cursor.getColumnIndex(CLIENT_CLIENT_ID_COLUMN);
- final int pendingIdColumn = cursor.getColumnIndex(CLIENT_PENDINGID_COLUMN);
- do {
- final long pendingId = cursor.getInt(pendingIdColumn);
- final String clientId = cursor.getString(clientIdIndex);
- if (pendingId == downloadId) {
- results.add(new DownloadRecord(clientId, null));
- }
- final ContentValues valuesForThisClient =
- getContentValuesByPendingId(getDb(context, clientId), downloadId);
- if (null != valuesForThisClient) {
- results.add(new DownloadRecord(clientId, valuesForThisClient));
- }
- } while (cursor.moveToNext());
- } finally {
- cursor.close();
- }
- return results;
- }
-
/**
* Gets the info about a specific word list.
*
@@ -716,7 +645,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
new String[]
{ id,
Integer.toString(version),
- Integer.toString(UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION)
+ Integer.toString(version)
},
null /* groupBy */,
null /* having */,
@@ -893,7 +822,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
return;
}
// Default value for a pending ID is NOT_AN_ID
- values.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
final SQLiteDatabase defaultDb = getDb(context, "");
if (-1 == defaultDb.insert(CLIENT_TABLE_NAME, null, values)) {
defaultDb.update(CLIENT_TABLE_NAME, values,
@@ -911,42 +839,6 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
new String[] { CLIENT_CLIENT_ID_COLUMN }, null, null, null, null, null);
}
- /**
- * Register a download ID for a specific metadata URI.
- *
- * This method should be called when a download for a metadata URI is starting. It will
- * search for all clients using this metadata URI and will register for each of them
- * the download ID into the database for later retrieval by
- * {@link #getDownloadRecordsForDownloadId(Context, long)}.
- *
- * @param context a context for opening databases
- * @param uri the metadata URI
- * @param downloadId the download ID
- */
- public static void registerMetadataDownloadId(final Context context, final String uri,
- 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;
- try {
- if (!cursor.moveToFirst()) return;
- do {
- final String clientId = cursor.getString(0);
- final String metadataUri =
- MetadataDbHelper.getMetadataUriAsString(context, clientId);
- if (metadataUri.equals(uri)) {
- defaultDb.update(CLIENT_TABLE_NAME, values,
- CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId });
- }
- } while (cursor.moveToNext());
- } finally {
- cursor.close();
- }
- }
-
/**
* Marks a downloading entry as having successfully downloaded and being installed.
*
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataHandler.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataHandler.java
index 1c3f9b30..2b153fdb 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataHandler.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/MetadataHandler.java
@@ -161,7 +161,6 @@ public class MetadataHandler {
int bestFormatVersion = Integer.MIN_VALUE; // To be sure we can't be inadvertently smaller
for (WordListMetadata wordList : metadata) {
if (id.equals(wordList.mId)
- && wordList.mFormatVersion <= UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION
&& wordList.mFormatVersion > bestFormatVersion) {
bestWordList = wordList;
bestFormatVersion = wordList.mFormatVersion;
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/PrivateLog.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/PrivateLog.java
index 3d9be650..80d31feb 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/PrivateLog.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/PrivateLog.java
@@ -30,7 +30,7 @@ import java.util.Locale;
*/
public class PrivateLog {
- public static final boolean DEBUG = DictionaryProvider.DEBUG;
+ public static final boolean DEBUG = false;
private static final String LOG_DATABASE_NAME = "log";
private static final String LOG_TABLE_NAME = "log";
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/UpdateHandler.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/UpdateHandler.java
deleted file mode 100644
index dd22784f..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/UpdateHandler.java
+++ /dev/null
@@ -1,1137 +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 org.dslul.openboard.inputmethod.dictionarypack;
-
-import android.app.DownloadManager;
-import android.app.DownloadManager.Query;
-import android.app.DownloadManager.Request;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.ConnectivityManager;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.dslul.openboard.inputmethod.compat.ConnectivityManagerCompatUtils;
-import org.dslul.openboard.inputmethod.compat.NotificationCompatUtils;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
-import org.dslul.openboard.inputmethod.latin.makedict.FormatSpec;
-import org.dslul.openboard.inputmethod.latin.utils.ApplicationUtils;
-import org.dslul.openboard.inputmethod.latin.utils.DebugLogUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.annotation.Nullable;
-
-/**
- * Handler for the update process.
- *
- * This class is in charge of coordinating the update process for the various dictionaries
- * stored in the dictionary pack.
- */
-public final class UpdateHandler {
- static final String TAG = "DictionaryProvider:" + UpdateHandler.class.getSimpleName();
- private static final boolean DEBUG = DictionaryProvider.DEBUG;
-
- // Used to prevent trying to read the id of the downloaded file before it is written
- static final Object sSharedIdProtector = new Object();
-
- // Value used to mean this is not a real DownloadManager downloaded file id
- // DownloadManager uses as an ID numbers returned out of an AUTOINCREMENT column
- // in SQLite, so it should never return anything < 0.
- public static final int NOT_AN_ID = -1;
- public static final int MAXIMUM_SUPPORTED_FORMAT_VERSION =
- FormatSpec.MAXIMUM_SUPPORTED_STATIC_VERSION;
-
- // Arbitrary. Probably good if it's a power of 2, and a couple thousand bytes long.
- private static final int FILE_COPY_BUFFER_SIZE = 8192;
-
- // Table fixed values for metadata / downloads
- final static String METADATA_NAME = "metadata";
- final static int METADATA_TYPE = 0;
- final static int WORDLIST_TYPE = 1;
-
- // Suffix for generated dictionary files
- private static final String DICT_FILE_SUFFIX = ".dict";
- // Name of the category for the main dictionary
- public static final String MAIN_DICTIONARY_CATEGORY = "main";
-
- public static final String TEMP_DICT_FILE_SUB = "___";
-
- // The id for the "dictionary available" notification.
- static final int DICT_AVAILABLE_NOTIFICATION_ID = 1;
-
- /**
- * An interface for UIs or services that want to know when something happened.
- *
- * This is chiefly used by the dictionary manager UI.
- */
- public interface UpdateEventListener {
- void downloadedMetadata(boolean succeeded);
- void wordListDownloadFinished(String wordListId, boolean succeeded);
- void updateCycleCompleted();
- }
-
- /**
- * The list of currently registered listeners.
- */
- private static List sUpdateEventListeners
- = Collections.synchronizedList(new LinkedList());
-
- /**
- * Register a new listener to be notified of updates.
- *
- * Don't forget to call unregisterUpdateEventListener when done with it, or
- * it will leak the register.
- */
- public static void registerUpdateEventListener(final UpdateEventListener listener) {
- sUpdateEventListeners.add(listener);
- }
-
- /**
- * Unregister a previously registered listener.
- */
- public static void unregisterUpdateEventListener(final UpdateEventListener listener) {
- sUpdateEventListeners.remove(listener);
- }
-
- private static final String DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY = "downloadOverMetered";
-
- /**
- * Write the DownloadManager ID of the currently downloading metadata to permanent storage.
- *
- * @param context to open shared prefs
- * @param uri the uri of the metadata
- * @param downloadId the id returned by DownloadManager
- */
- private static void writeMetadataDownloadId(final Context context, final String uri,
- final long downloadId) {
- MetadataDbHelper.registerMetadataDownloadId(context, uri, downloadId);
- }
-
- public static final int DOWNLOAD_OVER_METERED_SETTING_UNKNOWN = 0;
- public static final int DOWNLOAD_OVER_METERED_ALLOWED = 1;
- public static final int DOWNLOAD_OVER_METERED_DISALLOWED = 2;
-
- /**
- * Sets the setting that tells us whether we may download over a metered connection.
- */
- public static void setDownloadOverMeteredSetting(final Context context,
- final boolean shouldDownloadOverMetered) {
- final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY, shouldDownloadOverMetered
- ? DOWNLOAD_OVER_METERED_ALLOWED : DOWNLOAD_OVER_METERED_DISALLOWED);
- editor.apply();
- }
-
- /**
- * Gets the setting that tells us whether we may download over a metered connection.
- *
- * This returns one of the constants above.
- */
- public static int getDownloadOverMeteredSetting(final Context context) {
- final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
- final int setting = prefs.getInt(DOWNLOAD_OVER_METERED_SETTING_PREFS_KEY,
- DOWNLOAD_OVER_METERED_SETTING_UNKNOWN);
- return setting;
- }
-
- /**
- * Download latest metadata from the server through DownloadManager for all known clients
- * @param context The context for retrieving resources
- * @return true if an update successfully started, false otherwise.
- */
- public static boolean tryUpdate(final Context context) {
- // TODO: loop through all clients instead of only doing the default one.
- final TreeSet uris = new TreeSet<>();
- final Cursor cursor = MetadataDbHelper.queryClientIds(context);
- if (null == cursor) return false;
- try {
- if (!cursor.moveToFirst()) return false;
- do {
- final String clientId = cursor.getString(0);
- final String metadataUri =
- MetadataDbHelper.getMetadataUriAsString(context, clientId);
- PrivateLog.log("Update for clientId " + DebugLogUtils.s(clientId));
- DebugLogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri);
- uris.add(metadataUri);
- } while (cursor.moveToNext());
- } finally {
- cursor.close();
- }
- boolean started = false;
- for (final String metadataUri : uris) {
- if (!TextUtils.isEmpty(metadataUri)) {
- // If the metadata URI is empty, that means we should never update it at all.
- // It should not be possible to come here with a null metadata URI, because
- // it should have been rejected at the time of client registration; if there
- // is a bug and it happens anyway, doing nothing is the right thing to do.
- // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}.
- updateClientsWithMetadataUri(context, metadataUri);
- started = true;
- }
- }
- return started;
- }
-
- /**
- * Download latest metadata from the server through DownloadManager for all relevant clients
- *
- * @param context The context for retrieving resources
- * @param metadataUri The client to update
- */
- private static void updateClientsWithMetadataUri(
- final Context context, final String metadataUri) {
- Log.i(TAG, "updateClientsWithMetadataUri() : MetadataUri = " + metadataUri);
- // Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
- // DownloadManager also stupidly cuts the extension to replace with its own that it
- // gets from the content-type. We need to circumvent this.
- final String disambiguator = "#" + System.currentTimeMillis()
- + ApplicationUtils.getVersionName(context) + ".json";
- final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator));
- DebugLogUtils.l("Request =", metadataRequest);
-
- final Resources res = context.getResources();
- metadataRequest.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE);
- metadataRequest.setTitle(res.getString(R.string.download_description));
- // Do not show the notification when downloading the metadata.
- metadataRequest.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
- metadataRequest.setVisibleInDownloadsUi(
- res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));
-
- final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- 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 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
- // method will ignore it.
- writeMetadataDownloadId(context, metadataUri, downloadId);
- }
- Log.i(TAG, "updateClientsWithMetadataUri() : DownloadId = " + downloadId);
- }
-
- /**
- * 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 boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context,
- final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) {
- synchronized (sSharedIdProtector) {
- 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
- // has failed.
- for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
- listener.downloadedMetadata(false);
- }
- return false;
- }
-
- /**
- * Cancels a pending update for this client, if there is one.
- *
- * If we are not currently updating metadata for this client, this is a no-op. This is a helper
- * method that gets the download manager service and the metadata URI for this client.
- *
- * @param context the context, to get an instance of DownloadManager
- * @param clientId the ID of the client we want to cancel the update of
- */
- public static void cancelUpdate(final Context context, final String clientId) {
- final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
- maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */);
- }
-
- /**
- * Registers a download request and flags it as downloading in the metadata table.
- *
- * This is a helper method that exists to avoid race conditions where DownloadManager might
- * finish downloading the file before the data is committed to the database.
- * It registers the request with the DownloadManager service and also updates the metadata
- * database directly within a synchronized section.
- * This method has no intelligence about the data it commits to the database aside from the
- * download request id, which is not known before submitting the request to the download
- * manager. Hence, it only updates the relevant line.
- *
- * @param manager a wrapped download manager service to register the request with.
- * @param request the request to register.
- * @param db the metadata database.
- * @param id the id of the word list.
- * @param version the version of the word list.
- * @return the download id returned by the download manager.
- */
- public static long registerDownloadRequest(final DownloadManagerWrapper manager,
- final Request request, final SQLiteDatabase db, final String id, final int version) {
- Log.i(TAG, "registerDownloadRequest() : Id = " + id + " : Version = " + version);
- final long downloadId;
- synchronized (sSharedIdProtector) {
- downloadId = manager.enqueue(request);
- Log.i(TAG, "registerDownloadRequest() : DownloadId = " + downloadId);
- MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId);
- }
- return downloadId;
- }
-
- /**
- * Retrieve information about a specific download from DownloadManager.
- */
- private static CompletedDownloadInfo getCompletedDownloadInfo(
- final DownloadManagerWrapper manager, final long downloadId) {
- final Query query = new Query().setFilterById(downloadId);
- final Cursor cursor = manager.query(query);
-
- if (null == cursor) {
- return new CompletedDownloadInfo(null, downloadId, DownloadManager.STATUS_FAILED);
- }
- try {
- final String uri;
- final int status;
- if (cursor.moveToNext()) {
- final int columnStatus = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
- final int columnError = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
- final int columnUri = cursor.getColumnIndex(DownloadManager.COLUMN_URI);
- final int error = cursor.getInt(columnError);
- status = cursor.getInt(columnStatus);
- final String uriWithAnchor = cursor.getString(columnUri);
- int anchorIndex = uriWithAnchor.indexOf('#');
- if (anchorIndex != -1) {
- uri = uriWithAnchor.substring(0, anchorIndex);
- } else {
- uri = uriWithAnchor;
- }
- if (DownloadManager.STATUS_SUCCESSFUL != status) {
- Log.e(TAG, "Permanent failure of download " + downloadId
- + " with error code: " + error);
- }
- } else {
- uri = null;
- status = DownloadManager.STATUS_FAILED;
- }
- return new CompletedDownloadInfo(uri, downloadId, status);
- } finally {
- cursor.close();
- }
- }
-
- private static ArrayList getDownloadRecordsForCompletedDownloadInfo(
- final Context context, final CompletedDownloadInfo downloadInfo) {
- // Get and check the ID of the file we are waiting for, compare them to downloaded ones
- synchronized(sSharedIdProtector) {
- final ArrayList downloadRecords =
- MetadataDbHelper.getDownloadRecordsForDownloadId(context,
- downloadInfo.mDownloadId);
- // If any of these is metadata, we should update the DB
- boolean hasMetadata = false;
- for (DownloadRecord record : downloadRecords) {
- if (record.isMetadata()) {
- hasMetadata = true;
- break;
- }
- }
- if (hasMetadata) {
- writeMetadataDownloadId(context, downloadInfo.mUri, NOT_AN_ID);
- MetadataDbHelper.saveLastUpdateTimeOfUri(context, downloadInfo.mUri);
- }
- return downloadRecords;
- }
- }
-
- /**
- * Take appropriate action after a download finished, in success or in error.
- *
- * This is called by the system upon broadcast from the DownloadManager that a file
- * has been downloaded successfully.
- * After a simple check that this is actually the file we are waiting for, this
- * method basically coordinates the parsing and comparison of metadata, and fires
- * the computation of the list of actions that should be taken then executes them.
- *
- * @param context The context for this action.
- * @param intent The intent from the DownloadManager containing details about the download.
- */
- /* package */ static void downloadFinished(final Context context, final Intent intent) {
- // Get and check the ID of the file that was downloaded
- final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID);
- Log.i(TAG, "downloadFinished() : DownloadId = " + fileId);
- if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
-
- final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
- final CompletedDownloadInfo downloadInfo = getCompletedDownloadInfo(manager, fileId);
-
- final ArrayList recordList =
- getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo);
- if (null == recordList) return; // It was someone else's download.
- DebugLogUtils.l("Received result for download ", fileId);
-
- // TODO: handle gracefully a null pointer here. This is practically impossible because
- // we come here only when DownloadManager explicitly called us when it ended a
- // download, so we are pretty sure it's alive. It's theoretically possible that it's
- // disabled right inbetween the firing of the intent and the control reaching here.
-
- for (final DownloadRecord record : recordList) {
- // downloadSuccessful is not final because we may still have exceptions from now on
- boolean downloadSuccessful = false;
- try {
- if (downloadInfo.wasSuccessful()) {
- downloadSuccessful = handleDownloadedFile(context, record, manager, fileId);
- Log.i(TAG, "downloadFinished() : Success = " + downloadSuccessful);
- }
- } finally {
- final String resultMessage = downloadSuccessful ? "Success" : "Failure";
- if (record.isMetadata()) {
- Log.i(TAG, "downloadFinished() : Metadata " + resultMessage);
- publishUpdateMetadataCompleted(context, downloadSuccessful);
- } else {
- Log.i(TAG, "downloadFinished() : WordList " + resultMessage);
- final SQLiteDatabase db = MetadataDbHelper.getDb(context, record.mClientId);
- publishUpdateWordListCompleted(context, downloadSuccessful, fileId,
- db, record.mAttributes, record.mClientId);
- }
- }
- }
- // Now that we're done using it, we can remove this download from DLManager
- manager.remove(fileId);
- }
-
- /**
- * Sends a broadcast informing listeners that the dictionaries were updated.
- *
- * This will call all local listeners through the UpdateEventListener#downloadedMetadata
- * callback (for example, the dictionary provider interface uses this to stop the Loading
- * animation) and send a broadcast about the metadata having been updated. For a client of
- * the dictionary pack like Latin IME, this means it should re-query the dictionary pack
- * for any relevant new data.
- *
- * @param context the context, to send the broadcast.
- * @param downloadSuccessful whether the download of the metadata was successful or not.
- */
- public static void publishUpdateMetadataCompleted(final Context context,
- final boolean downloadSuccessful) {
- // We need to warn all listeners of what happened. But some listeners may want to
- // remove themselves or re-register something in response. Hence we should take a
- // snapshot of the listener list and warn them all. This also prevents any
- // concurrent modification problem of the static list.
- for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
- listener.downloadedMetadata(downloadSuccessful);
- }
- publishUpdateCycleCompletedEvent(context);
- }
-
- private static void publishUpdateWordListCompleted(final Context context,
- final boolean downloadSuccessful, final long fileId,
- final SQLiteDatabase db, final ContentValues downloadedFileRecord,
- final String clientId) {
- synchronized(sSharedIdProtector) {
- if (downloadSuccessful) {
- final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.InstallAfterDownloadAction(clientId,
- downloadedFileRecord));
- actions.execute(context, new LogProblemReporter(TAG));
- } else {
- MetadataDbHelper.deleteDownloadingEntry(db, fileId);
- }
- }
- // See comment above about #linkedCopyOfLists
- for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
- listener.wordListDownloadFinished(downloadedFileRecord.getAsString(
- MetadataDbHelper.WORDLISTID_COLUMN), downloadSuccessful);
- }
- publishUpdateCycleCompletedEvent(context);
- }
-
- private static void publishUpdateCycleCompletedEvent(final Context context) {
- // Even if this is not successful, we have to publish the new state.
- PrivateLog.log("Publishing update cycle completed event");
- DebugLogUtils.l("Publishing update cycle completed event");
- for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
- listener.updateCycleCompleted();
- }
- signalNewDictionaryState(context);
- }
-
- private static boolean handleDownloadedFile(final Context context,
- final DownloadRecord downloadRecord, final DownloadManagerWrapper manager,
- final long fileId) {
- try {
- // {@link handleWordList(Context,InputStream,ContentValues)}.
- // Handle the downloaded file according to its type
- if (downloadRecord.isMetadata()) {
- DebugLogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId);
- // #handleMetadata() closes its InputStream argument
- handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream(
- manager.openDownloadedFile(fileId)), downloadRecord.mClientId);
- } else {
- DebugLogUtils.l("Data D/L'd is a word list");
- final int wordListStatus = downloadRecord.mAttributes.getAsInteger(
- MetadataDbHelper.STATUS_COLUMN);
- if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) {
- // #handleWordList() closes its InputStream argument
- handleWordList(context, new ParcelFileDescriptor.AutoCloseInputStream(
- manager.openDownloadedFile(fileId)), downloadRecord);
- } else {
- Log.e(TAG, "Spurious download ended. Maybe a cancelled download?");
- }
- }
- return true;
- } catch (FileNotFoundException e) {
- Log.e(TAG, "A file was downloaded but it can't be opened", e);
- } catch (IOException e) {
- // Can't read the file... disk damage?
- Log.e(TAG, "Can't read a file", e);
- // TODO: Check with UX how we should warn the user.
- } catch (IllegalStateException e) {
- // The format of the downloaded file is incorrect. We should maybe report upstream?
- Log.e(TAG, "Incorrect data received", e);
- } catch (BadFormatException e) {
- // The format of the downloaded file is incorrect. We should maybe report upstream?
- Log.e(TAG, "Incorrect data received", e);
- }
- return false;
- }
-
- /**
- * Returns a copy of the specified list, with all elements copied.
- *
- * This returns a linked list.
- */
- private static List linkedCopyOfList(final List src) {
- // Instantiation of a parameterized type is not possible in Java, so it's not possible to
- // return the same type of list that was passed - probably the same reason why Collections
- // does not do it. So we need to decide statically which concrete type to return.
- return new LinkedList<>(src);
- }
-
- /**
- * Warn Android Keyboard that the state of dictionaries changed and it should refresh its data.
- */
- private static void signalNewDictionaryState(final Context context) {
- // TODO: Also provide the locale of the updated dictionary so that the LatinIme
- // does not have to reset if it is a different locale.
- final Intent newDictBroadcast =
- new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
- context.sendBroadcast(newDictBroadcast);
- }
-
- /**
- * Parse metadata and take appropriate action (that is, upgrade dictionaries).
- * @param context the context to read settings.
- * @param stream an input stream pointing to the downloaded data. May not be null.
- * Will be closed upon finishing.
- * @param clientId the ID of the client to update
- * @throws BadFormatException if the metadata is not in a known format.
- * @throws IOException if the downloaded file can't be read from the disk
- */
- public static void handleMetadata(final Context context, final InputStream stream,
- final String clientId) throws IOException, BadFormatException {
- DebugLogUtils.l("Entering handleMetadata");
- final List newMetadata;
- final InputStreamReader reader = new InputStreamReader(stream);
- try {
- // According to the doc InputStreamReader buffers, so no need to add a buffering layer
- newMetadata = MetadataHandler.readMetadata(reader);
- } finally {
- reader.close();
- }
-
- DebugLogUtils.l("Downloaded metadata :", newMetadata);
- PrivateLog.log("Downloaded metadata\n" + newMetadata);
-
- final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata);
- // TODO: Check with UX how we should report to the user
- // TODO: add an action to close the database
- actions.execute(context, new LogProblemReporter(TAG));
- }
-
- /**
- * Handle a word list: put it in its right place, and update the passed content values.
- * @param context the context for opening files.
- * @param inputStream an input stream pointing to the downloaded data. May not be null.
- * Will be closed upon finishing.
- * @param downloadRecord the content values to fill the file name in.
- * @throws IOException if files can't be read or written.
- * @throws BadFormatException if the md5 checksum doesn't match the metadata.
- */
- private static void handleWordList(final Context context,
- final InputStream inputStream, final DownloadRecord downloadRecord)
- throws IOException, BadFormatException {
-
- // DownloadManager does not have the ability to put the file directly where we want
- // it, so we had it download to a temporary place. Now we move it. It will be deleted
- // automatically by DownloadManager.
- DebugLogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString(
- MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId);
- PrivateLog.log("Downloaded a new word list with description : "
- + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN)
- + " for " + downloadRecord.mClientId);
-
- final String locale =
- downloadRecord.mAttributes.getAsString(MetadataDbHelper.LOCALE_COLUMN);
- final String destinationFile = getTempFileName(context, locale);
- downloadRecord.mAttributes.put(MetadataDbHelper.LOCAL_FILENAME_COLUMN, destinationFile);
-
- FileOutputStream outputStream = null;
- try {
- outputStream = context.openFileOutput(destinationFile, Context.MODE_PRIVATE);
- copyFile(inputStream, outputStream);
- } finally {
- inputStream.close();
- if (outputStream != null) {
- outputStream.close();
- }
- }
-
- // TODO: Consolidate this MD5 calculation with file copying above.
- // We need to reopen the file because the inputstream bytes have been consumed, and there
- // is nothing in InputStream to reopen or rewind the stream
- FileInputStream copiedFile = null;
- final String md5sum;
- try {
- copiedFile = context.openFileInput(destinationFile);
- md5sum = MD5Calculator.checksum(copiedFile);
- } finally {
- if (copiedFile != null) {
- copiedFile.close();
- }
- }
- if (TextUtils.isEmpty(md5sum)) {
- return; // We can't compute the checksum anyway, so return and hope for the best
- }
- if (!md5sum.equals(downloadRecord.mAttributes.getAsString(
- MetadataDbHelper.CHECKSUM_COLUMN))) {
- context.deleteFile(destinationFile);
- throw new BadFormatException("MD5 checksum check failed : \"" + md5sum + "\" <> \""
- + downloadRecord.mAttributes.getAsString(MetadataDbHelper.CHECKSUM_COLUMN)
- + "\"");
- }
- }
-
- /**
- * Copies in to out using FileChannels.
- *
- * This tries to use channels for fast copying. If it doesn't work, fall back to
- * copyFileFallBack below.
- *
- * @param in the stream to copy from.
- * @param out the stream to copy to.
- * @throws IOException if both the normal and fallback methods raise exceptions.
- */
- private static void copyFile(final InputStream in, final OutputStream out)
- throws IOException {
- DebugLogUtils.l("Copying files");
- if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) {
- DebugLogUtils.l("Not the right types");
- copyFileFallback(in, out);
- } else {
- try {
- final FileChannel sourceChannel = ((FileInputStream) in).getChannel();
- final FileChannel destinationChannel = ((FileOutputStream) out).getChannel();
- sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel);
- } catch (IOException e) {
- // Can't work with channels, or something went wrong. Copy by hand.
- DebugLogUtils.l("Won't work");
- copyFileFallback(in, out);
- }
- }
- }
-
- /**
- * Copies in to out with read/write methods, not FileChannels.
- *
- * @param in the stream to copy from.
- * @param out the stream to copy to.
- * @throws IOException if a read or a write fails.
- */
- private static void copyFileFallback(final InputStream in, final OutputStream out)
- throws IOException {
- DebugLogUtils.l("Falling back to slow copy");
- final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE];
- for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer))
- out.write(buffer, 0, readBytes);
- }
-
- /**
- * Creates and returns a new file to store a dictionary
- * @param context the context to use to open the file.
- * @param locale the locale for this dictionary, to make the file name more readable.
- * @return the file name, or throw an exception.
- * @throws IOException if the file cannot be created.
- */
- private static String getTempFileName(final Context context, final String locale)
- throws IOException {
- DebugLogUtils.l("Entering openTempFileOutput");
- final File dir = context.getFilesDir();
- final File f = File.createTempFile(locale + TEMP_DICT_FILE_SUB, DICT_FILE_SUFFIX, dir);
- DebugLogUtils.l("File name is", f.getName());
- return f.getName();
- }
-
- /**
- * Compare metadata (collections of word lists).
- *
- * This method takes whole metadata sets directly and compares them, matching the wordlists in
- * each of them on the id. It creates an ActionBatch object that can be .execute()'d to perform
- * the actual upgrade from `from' to `to'.
- *
- * @param context the context to open databases on.
- * @param clientId the id of the client.
- * @param from the dictionary descriptor (as a list of wordlists) to upgrade from.
- * @param to the dictionary descriptor (as a list of wordlists) to upgrade to.
- * @return an ordered list of runnables to be called to upgrade.
- */
- private static ActionBatch compareMetadataForUpgrade(final Context context,
- final String clientId, @Nullable final List from,
- @Nullable final List to) {
- final ActionBatch actions = new ActionBatch();
- // Upgrade existing word lists
- DebugLogUtils.l("Comparing dictionaries");
- final Set wordListIds = new TreeSet<>();
- // TODO: Can these be null?
- final List fromList = (from == null) ? new ArrayList()
- : from;
- final List toList = (to == null) ? new ArrayList()
- : to;
- for (WordListMetadata wlData : fromList) wordListIds.add(wlData.mId);
- for (WordListMetadata wlData : toList) wordListIds.add(wlData.mId);
- for (String id : wordListIds) {
- final WordListMetadata currentInfo = MetadataHandler.findWordListById(fromList, id);
- final WordListMetadata metadataInfo = MetadataHandler.findWordListById(toList, id);
- // TODO: Remove the following unnecessary check, since we are now doing the filtering
- // inside findWordListById.
- final WordListMetadata newInfo = null == metadataInfo
- || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION
- ? null : metadataInfo;
- DebugLogUtils.l("Considering updating ", id, "currentInfo =", currentInfo);
-
- if (null == currentInfo && null == newInfo) {
- // This may happen if a new word list appeared that we can't handle.
- if (null == metadataInfo) {
- // What happened? Bug in Set<>?
- Log.e(TAG, "Got an id for a wordlist that is neither in from nor in to");
- } else {
- // We may come here if there is a new word list that we can't handle.
- Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format"
- + " version " + metadataInfo.mFormatVersion + " and the maximum version"
- + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
- }
- continue;
- } else if (null == currentInfo) {
- // This is the case where a new list that we did not know of popped on the server.
- // Make it available.
- actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo));
- } else if (null == newInfo) {
- // This is the case where an old list we had is not in the server data any more.
- // Pass false to ForgetAction: this may be installed and we still want to apply
- // a forget-like action (remove the URL) if it is, so we want to turn off the
- // status == AVAILABLE check. If it's DELETING, this is the right thing to do,
- // as we want to leave the record as long as Android Keyboard has not deleted it ;
- // the record will be removed when the file is actually deleted.
- actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, false));
- } else {
- final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
- if (newInfo.mVersion == currentInfo.mVersion) {
- if (TextUtils.equals(newInfo.mRemoteFilename, currentInfo.mRemoteFilename)) {
- // If the dictionary url hasn't changed, we should preserve the retryCount.
- newInfo.mRetryCount = currentInfo.mRetryCount;
- }
- // If it's the same id/version, we update the DB with the new values.
- // It doesn't matter too much if they didn't change.
- actions.add(new ActionBatch.UpdateDataAction(clientId, newInfo));
- } else if (newInfo.mVersion > currentInfo.mVersion) {
- // If it's a new version, it's a different entry in the database. Make it
- // available, and if it's installed, also start the download.
- final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
- currentInfo.mId, currentInfo.mVersion);
- final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
- actions.add(new ActionBatch.MakeAvailableAction(clientId, newInfo));
- if (status == MetadataDbHelper.STATUS_INSTALLED
- || status == MetadataDbHelper.STATUS_DISABLED) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, newInfo));
- } else {
- // Pass true to ForgetAction: this is indeed an update to a non-installed
- // word list, so activate status == AVAILABLE check
- // In case the status is DELETING, this is the right thing to do. It will
- // leave the entry as DELETING and remove its URL so that Android Keyboard
- // can delete it the next time it starts up.
- actions.add(new ActionBatch.ForgetAction(clientId, currentInfo, true));
- }
- } else if (DEBUG) {
- Log.i(TAG, "Not updating word list " + id
- + " : current list timestamp is " + currentInfo.mLastUpdate
- + " ; new list timestamp is " + newInfo.mLastUpdate);
- }
- }
- }
- return actions;
- }
-
- /**
- * Computes an upgrade from the current state of the dictionaries to some desired state.
- * @param context the context for reading settings and files.
- * @param clientId the id of the client.
- * @param newMetadata the state we want to upgrade to.
- * @return the upgrade from the current state to the desired state, ready to be executed.
- */
- public static ActionBatch computeUpgradeTo(final Context context, final String clientId,
- final List newMetadata) {
- final List currentMetadata =
- MetadataHandler.getCurrentMetadata(context, clientId);
- return compareMetadataForUpgrade(context, clientId, currentMetadata, newMetadata);
- }
-
- /**
- * Shows the notification that informs the user a dictionary is available.
- *
- * When this notification is clicked, the dialog for downloading the dictionary
- * over a metered connection is shown.
- */
- private static void showDictionaryAvailableNotification(final Context context,
- final String clientId, final ContentValues installCandidate) {
- final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN);
- final Intent intent = new Intent();
- intent.setClass(context, DownloadOverMeteredDialog.class);
- intent.putExtra(DownloadOverMeteredDialog.CLIENT_ID_KEY, clientId);
- intent.putExtra(DownloadOverMeteredDialog.WORDLIST_TO_DOWNLOAD_KEY,
- installCandidate.getAsString(MetadataDbHelper.WORDLISTID_COLUMN));
- intent.putExtra(DownloadOverMeteredDialog.SIZE_KEY,
- installCandidate.getAsInteger(MetadataDbHelper.FILESIZE_COLUMN));
- intent.putExtra(DownloadOverMeteredDialog.LOCALE_KEY, localeString);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- final PendingIntent notificationIntent = PendingIntent.getActivity(context,
- 0 /* requestCode */, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- final NotificationManager notificationManager =
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- // None of those are expected to happen, but just in case...
- if (null == notificationIntent || null == notificationManager) return;
-
- final String language = (null == localeString) ? ""
- : LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
- final String titleFormat = context.getString(R.string.dict_available_notification_title);
- final String notificationTitle = String.format(titleFormat, language);
- final Notification.Builder builder = new Notification.Builder(context)
- .setAutoCancel(true)
- .setContentIntent(notificationIntent)
- .setContentTitle(notificationTitle)
- .setContentText(context.getString(R.string.dict_available_notification_description))
- .setTicker(notificationTitle)
- .setOngoing(false)
- .setOnlyAlertOnce(true)
- .setSmallIcon(R.drawable.ic_notify_dictionary);
- NotificationCompatUtils.setColor(builder,
- context.getResources().getColor(R.color.notification_accent_color));
- NotificationCompatUtils.setPriorityToLow(builder);
- NotificationCompatUtils.setVisibilityToSecret(builder);
- NotificationCompatUtils.setCategoryToRecommendation(builder);
- final Notification notification = NotificationCompatUtils.build(builder);
- notificationManager.notify(DICT_AVAILABLE_NOTIFICATION_ID, notification);
- }
-
- /**
- * Installs a word list if it has never been requested.
- *
- * This is called when a word list is requested, and is available but not installed. It checks
- * the conditions for auto-installation: if the dictionary is a main dictionary for this
- * language, and it has never been opted out through the dictionary interface, then we start
- * installing it. For the user who enables a language and uses it for the first time, the
- * dictionary should magically start being used a short time after they start typing.
- * The mayPrompt argument indicates whether we should prompt the user for a decision to
- * download or not, in case we decide we are in the case where we should download - this
- * roughly happens when the current connectivity is 3G. See
- * DictionaryProvider#getDictionaryWordListsForContentUri for details.
- */
- // As opposed to many other methods, this method does not need the version of the word
- // list because it may only install the latest version we know about for this specific
- // word list ID / client ID combination.
- public static void installIfNeverRequested(final Context context, final String clientId,
- final String wordlistId) {
- Log.i(TAG, "installIfNeverRequested() : ClientId = " + clientId
- + " : WordListId = " + wordlistId);
- final String[] idArray = wordlistId.split(DictionaryProvider.ID_CATEGORY_SEPARATOR);
- // If we have a new-format dictionary id (category:manual_id), then use the
- // specified category. Otherwise, it is a main dictionary, so force the
- // MAIN category upon it.
- final String category = 2 == idArray.length ? idArray[0] : MAIN_DICTIONARY_CATEGORY;
- if (!MAIN_DICTIONARY_CATEGORY.equals(category)) {
- // Not a main dictionary. We only auto-install main dictionaries, so we can return now.
- return;
- }
- if (CommonPreferences.getCommonPreferences(context).contains(wordlistId)) {
- // If some kind of settings has been done in the past for this specific id, then
- // this is not a candidate for auto-install. Because it already is either true,
- // in which case it may be installed or downloading or whatever, and we don't
- // need to care about it because it's already handled or being handled, or it's false
- // in which case it means the user explicitely turned it off and don't want to have
- // it installed. So we quit right away.
- return;
- }
-
- final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
- final ContentValues installCandidate =
- MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId);
- if (MetadataDbHelper.STATUS_AVAILABLE
- != installCandidate.getAsInteger(MetadataDbHelper.STATUS_COLUMN)) {
- // If it's not "AVAILABLE", we want to stop now. Because candidates for auto-install
- // are lists that we know are available, but we also know have never been installed.
- // It does obviously not concern already installed lists, or downloading lists,
- // or those that have been disabled, flagged as deleting... So anything else than
- // AVAILABLE means we don't auto-install.
- return;
- }
-
- // We decided against prompting the user for a decision. This may be because we were
- // explicitly asked not to, or because we are currently on wi-fi anyway, or because we
- // already know the answer to the question. We'll enqueue a request ; StartDownloadAction
- // knows to use the correct type of network according to the current settings.
-
- // Also note that once it's auto-installed, a word list will be marked as INSTALLED. It will
- // thus receive automatic updates if there are any, which is what we want. If the user does
- // not want this word list, they will have to go to the settings and change them, which will
- // change the shared preferences. So there is no way for a word list that has been
- // auto-installed once to get auto-installed again, and that's what we want.
- final ActionBatch actions = new ActionBatch();
- WordListMetadata metadata = WordListMetadata.createFromContentValues(installCandidate);
- actions.add(new ActionBatch.StartDownloadAction(clientId, metadata));
- final String localeString = installCandidate.getAsString(MetadataDbHelper.LOCALE_COLUMN);
-
- // We are in a content provider: we can't do any UI at all. We have to defer the displaying
- // itself to the service. Also, we only display this when the user does not have a
- // dictionary for this language already. During setup wizard, however, this UI is
- // suppressed.
- final boolean deviceProvisioned = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- if (deviceProvisioned) {
- final Intent intent = new Intent();
- intent.setClass(context, DictionaryService.class);
- intent.setAction(DictionaryService.SHOW_DOWNLOAD_TOAST_INTENT_ACTION);
- intent.putExtra(DictionaryService.LOCALE_INTENT_ARGUMENT, localeString);
- context.startService(intent);
- } else {
- Log.i(TAG, "installIfNeverRequested() : Don't show download toast");
- }
-
- Log.i(TAG, "installIfNeverRequested() : StartDownloadAction for " + metadata);
- actions.execute(context, new LogProblemReporter(TAG));
- }
-
- /**
- * Marks the word list with the passed id as used.
- *
- * This will download/install the list as required. The action will see that the destination
- * word list is a valid list, and take appropriate action - in this case, mark it as used.
- * @see ActionBatch.Action#execute
- *
- * @param context the context for using action batches.
- * @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as installed.
- * @param version the version of the word list to mark as installed.
- * @param status the current status of the word list.
- * @param allowDownloadOnMeteredData whether to download even on metered data connection
- */
- // The version argument is not used yet, because we don't need it to retrieve the information
- // we need. However, the pair (id, version) being the primary key to a word list in the database
- // it feels better for consistency to pass it, and some methods retrieving information about a
- // word list need it so we may need it in the future.
- public static void markAsUsed(final Context context, final String clientId,
- final String wordlistId, final int version,
- final int status, final boolean allowDownloadOnMeteredData) {
- final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
- context, clientId, wordlistId, version);
-
- if (null == wordListMetaData) return;
-
- final ActionBatch actions = new ActionBatch();
- if (MetadataDbHelper.STATUS_DISABLED == status
- || MetadataDbHelper.STATUS_DELETING == status) {
- actions.add(new ActionBatch.EnableAction(clientId, wordListMetaData));
- } else if (MetadataDbHelper.STATUS_AVAILABLE == status) {
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData));
- } else {
- Log.e(TAG, "Unexpected state of the word list for markAsUsed : " + status);
- }
- actions.execute(context, new LogProblemReporter(TAG));
- signalNewDictionaryState(context);
- }
-
- /**
- * Marks the word list with the passed id as unused.
- *
- * This leaves the file on the disk for ulterior use. The action will see that the destination
- * word list is null, and take appropriate action - in this case, mark it as unused.
- * @see ActionBatch.Action#execute
- *
- * @param context the context for using action batches.
- * @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as installed.
- * @param version the version of the word list to mark as installed.
- * @param status the current status of the word list.
- */
- // The version and status arguments are not used yet, but this method matches its interface to
- // markAsUsed for consistency.
- public static void markAsUnused(final Context context, final String clientId,
- final String wordlistId, final int version, final int status) {
-
- final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
- context, clientId, wordlistId, version);
-
- if (null == wordListMetaData) return;
- final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
- actions.execute(context, new LogProblemReporter(TAG));
- signalNewDictionaryState(context);
- }
-
- /**
- * Marks the word list with the passed id as deleting.
- *
- * This basically means that on the next chance there is (right away if Android Keyboard
- * happens to be up, or the next time it gets up otherwise) the dictionary pack will
- * supply an empty dictionary to it that will replace whatever dictionary is installed.
- * This allows to release the space taken by a dictionary (except for the few bytes the
- * empty dictionary takes up), and override a built-in default dictionary so that we
- * can fake delete a built-in dictionary.
- *
- * @param context the context to open the database on.
- * @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as deleted.
- * @param version the version of the word list to mark as deleted.
- * @param status the current status of the word list.
- */
- public static void markAsDeleting(final Context context, final String clientId,
- final String wordlistId, final int version, final int status) {
-
- final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
- context, clientId, wordlistId, version);
-
- if (null == wordListMetaData) return;
- final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.DisableAction(clientId, wordListMetaData));
- actions.add(new ActionBatch.StartDeleteAction(clientId, wordListMetaData));
- actions.execute(context, new LogProblemReporter(TAG));
- signalNewDictionaryState(context);
- }
-
- /**
- * Marks the word list with the passed id as actually deleted.
- *
- * This reverts to available status or deletes the row as appropriate.
- *
- * @param context the context to open the database on.
- * @param clientId the id of the client.
- * @param wordlistId the id of the word list to mark as deleted.
- * @param version the version of the word list to mark as deleted.
- * @param status the current status of the word list.
- */
- public static void markAsDeleted(final Context context, final String clientId,
- final String wordlistId, final int version, final int status) {
- final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
- context, clientId, wordlistId, version);
-
- if (null == wordListMetaData) return;
-
- final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.FinishDeleteAction(clientId, wordListMetaData));
- actions.execute(context, new LogProblemReporter(TAG));
- signalNewDictionaryState(context);
- }
-
- /**
- * Checks whether the word list should be downloaded again; in which case an download &
- * installation attempt is made. Otherwise the word list is marked broken.
- *
- * @param context the context to open the database on.
- * @param clientId the id of the client.
- * @param wordlistId the id of the word list which is broken.
- * @param version the version of the broken word list.
- */
- public static void markAsBrokenOrRetrying(final Context context, final String clientId,
- final String wordlistId, final int version) {
- boolean isRetryPossible = MetadataDbHelper.maybeMarkEntryAsRetrying(
- MetadataDbHelper.getDb(context, clientId), wordlistId, version);
-
- if (isRetryPossible) {
- if (DEBUG) {
- Log.d(TAG, "Attempting to download & install the wordlist again.");
- }
- final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
- context, clientId, wordlistId, version);
- if (wordListMetaData == null) {
- return;
- }
-
- final ActionBatch actions = new ActionBatch();
- actions.add(new ActionBatch.StartDownloadAction(clientId, wordListMetaData));
- actions.execute(context, new LogProblemReporter(TAG));
- } else {
- if (DEBUG) {
- Log.d(TAG, "Retries for wordlist exhausted, deleting the wordlist from table.");
- }
- MetadataDbHelper.deleteEntry(MetadataDbHelper.getDb(context, clientId),
- wordlistId, version);
- }
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/WordListPreference.java b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/WordListPreference.java
index 1b93d7b5..ae3572bc 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/WordListPreference.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/dictionarypack/WordListPreference.java
@@ -169,7 +169,6 @@ public final class WordListPreference extends Preference {
final Context context = getContext();
final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
- UpdateHandler.markAsUnused(context, mClientId, mWordlistId, mVersion, mStatus);
if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) {
setStatus(MetadataDbHelper.STATUS_AVAILABLE);
} else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) {
@@ -185,8 +184,6 @@ public final class WordListPreference extends Preference {
final Context context = getContext();
final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.enable(prefs, mWordlistId);
- // Explicit enabling by the user : allow downloading on metered data connection.
- UpdateHandler.markAsUsed(context, mClientId, mWordlistId, mVersion, mStatus, true);
if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) {
setStatus(MetadataDbHelper.STATUS_DOWNLOADING);
} else if (MetadataDbHelper.STATUS_DISABLED == mStatus
@@ -207,7 +204,6 @@ public final class WordListPreference extends Preference {
final SharedPreferences prefs = CommonPreferences.getCommonPreferences(context);
CommonPreferences.disable(prefs, mWordlistId);
setStatus(MetadataDbHelper.STATUS_DELETING);
- UpdateHandler.markAsDeleting(context, mClientId, mWordlistId, mVersion, mStatus);
}
@Override
@@ -215,16 +211,6 @@ public final class WordListPreference extends Preference {
super.onBindView(view);
((ViewGroup)view).setLayoutTransition(null);
- final DictionaryDownloadProgressBar progressBar =
- (DictionaryDownloadProgressBar)view.findViewById(R.id.dictionary_line_progress_bar);
- final TextView status = (TextView)view.findViewById(android.R.id.summary);
- progressBar.setIds(mClientId, mWordlistId);
- progressBar.setMax(mFilesize);
- final boolean showProgressBar = (MetadataDbHelper.STATUS_DOWNLOADING == mStatus);
- setSummary(getSummary(mStatus));
- status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
- progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE);
-
final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)view.findViewById(
R.id.wordlist_button_switcher);
// We need to clear the state of the button switcher, because we reuse views; if we didn't
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryFileDumper.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryFileDumper.java
index 578c7388..b2ee751d 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -29,7 +29,6 @@ import android.util.Log;
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants;
import org.dslul.openboard.inputmethod.dictionarypack.MD5Calculator;
-import org.dslul.openboard.inputmethod.dictionarypack.UpdateHandler;
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants;
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils;
@@ -221,152 +220,6 @@ public final class BinaryDictionaryFileDumper {
}
}
- /**
- * Stages a word list the id of which is passed as an argument. This will write the file
- * to the cache file name designated by its id and locale, overwriting it if already present
- * and creating it (and its containing directory) if necessary.
- */
- private static void installWordListToStaging(final String wordlistId, final String locale,
- final String rawChecksum, final ContentProviderClient providerClient,
- final Context context) {
- final int COMPRESSED_CRYPTED_COMPRESSED = 0;
- final int CRYPTED_COMPRESSED = 1;
- final int COMPRESSED_CRYPTED = 2;
- final int COMPRESSED_ONLY = 3;
- final int CRYPTED_ONLY = 4;
- final int NONE = 5;
- final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
- final int MODE_MAX = NONE;
-
- final String clientId = context.getString(R.string.dictionary_pack_client_id);
- final Uri.Builder wordListUriBuilder;
- try {
- wordListUriBuilder = getContentUriBuilderForType(clientId,
- providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
- } catch (RemoteException e) {
- Log.e(TAG, "Can't communicate with the dictionary pack", e);
- return;
- }
- final String finalFileName =
- DictionaryInfoUtils.getStagingFileName(wordlistId, locale, context);
- String tempFileName;
- try {
- tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
- } catch (IOException e) {
- Log.e(TAG, "Can't open the temporary file", e);
- return;
- }
-
- for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) {
- final InputStream originalSourceStream;
- InputStream inputStream = null;
- InputStream uncompressedStream = null;
- InputStream decryptedStream = null;
- BufferedInputStream bufferedInputStream = null;
- File outputFile = null;
- BufferedOutputStream bufferedOutputStream = null;
- AssetFileDescriptor afd = null;
- final Uri wordListUri = wordListUriBuilder.build();
- try {
- // Open input.
- afd = openAssetFileDescriptor(providerClient, wordListUri);
- // If we can't open it at all, don't even try a number of times.
- if (null == afd) return;
- originalSourceStream = afd.createInputStream();
- // Open output.
- outputFile = new File(tempFileName);
- // Just to be sure, delete the file. This may fail silently, and return false: this
- // is the right thing to do, as we just want to continue anyway.
- outputFile.delete();
- // Get the appropriate decryption method for this try
- switch (mode) {
- case COMPRESSED_CRYPTED_COMPRESSED:
- uncompressedStream =
- FileTransforms.getUncompressedStream(originalSourceStream);
- decryptedStream = FileTransforms.getDecryptedStream(uncompressedStream);
- inputStream = FileTransforms.getUncompressedStream(decryptedStream);
- break;
- case CRYPTED_COMPRESSED:
- decryptedStream = FileTransforms.getDecryptedStream(originalSourceStream);
- inputStream = FileTransforms.getUncompressedStream(decryptedStream);
- break;
- case COMPRESSED_CRYPTED:
- uncompressedStream =
- FileTransforms.getUncompressedStream(originalSourceStream);
- inputStream = FileTransforms.getDecryptedStream(uncompressedStream);
- break;
- case COMPRESSED_ONLY:
- inputStream = FileTransforms.getUncompressedStream(originalSourceStream);
- break;
- case CRYPTED_ONLY:
- inputStream = FileTransforms.getDecryptedStream(originalSourceStream);
- break;
- case NONE:
- inputStream = originalSourceStream;
- break;
- }
- bufferedInputStream = new BufferedInputStream(inputStream);
- bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
- checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
- bufferedOutputStream.flush();
- bufferedOutputStream.close();
-
- if (SHOULD_VERIFY_CHECKSUM) {
- final String actualRawChecksum = MD5Calculator.checksum(
- new BufferedInputStream(new FileInputStream(outputFile)));
- Log.i(TAG, "Computed checksum for downloaded dictionary. Expected = "
- + rawChecksum + " ; actual = " + actualRawChecksum);
- if (!TextUtils.isEmpty(rawChecksum) && !rawChecksum.equals(actualRawChecksum)) {
- throw new IOException(
- "Could not decode the file correctly : checksum differs");
- }
- }
-
- // move the output file to the final staging file.
- final File finalFile = new File(finalFileName);
- if (!FileUtils.renameTo(outputFile, finalFile)) {
- Log.e(TAG, String.format("Failed to rename from %s to %s.",
- outputFile.getAbsoluteFile(), finalFile.getAbsoluteFile()));
- }
-
- wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
- QUERY_PARAMETER_SUCCESS);
- if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
- Log.e(TAG, "Could not have the dictionary pack delete a word list");
- }
- Log.d(TAG, "Successfully copied file for wordlist ID " + wordlistId);
- // Success! Close files (through the finally{} clause) and return.
- return;
- } catch (Exception e) {
- if (DEBUG) {
- Log.e(TAG, "Can't open word list in mode " + mode, e);
- }
- if (null != outputFile) {
- // This may or may not fail. The file may not have been created if the
- // exception was thrown before it could be. Hence, both failure and
- // success are expected outcomes, so we don't check the return value.
- outputFile.delete();
- }
- // Try the next method.
- } finally {
- // Ignore exceptions while closing files.
- closeAssetFileDescriptorAndReportAnyException(afd);
- closeCloseableAndReportAnyException(inputStream);
- closeCloseableAndReportAnyException(uncompressedStream);
- closeCloseableAndReportAnyException(decryptedStream);
- closeCloseableAndReportAnyException(bufferedInputStream);
- closeCloseableAndReportAnyException(bufferedOutputStream);
- }
- }
-
- // We could not copy the file at all. This is very unexpected.
- // I'd rather not print the word list ID to the log out of security concerns
- Log.e(TAG, "Could not copy a word list. Will not be able to use it.");
- // If we can't copy it we should warn the dictionary provider so that it can mark it
- // as invalid.
- reportBrokenFileToDictionaryProvider(providerClient, clientId, wordlistId);
- }
-
public static boolean reportBrokenFileToDictionaryProvider(
final ContentProviderClient providerClient, final String clientId,
final String wordlistId) {
@@ -405,53 +258,6 @@ public final class BinaryDictionaryFileDumper {
}
}
- /**
- * Queries a content provider for word list data for some locale and stage the returned files
- *
- * This will query a content provider for word list data for a given locale, and copy the
- * files locally so that they can be mmap'ed. This may overwrite previously cached word lists
- * with newer versions if a newer version is made available by the content provider.
- * @throw FileNotFoundException if the provider returns non-existent data.
- * @throw IOException if the provider-returned data could not be read.
- */
- public static void installDictToStagingFromContentProvider(final Locale locale,
- final Context context, final boolean hasDefaultWordList) {
- final ContentProviderClient providerClient;
- try {
- providerClient = context.getContentResolver().
- acquireContentProviderClient(getProviderUriBuilder("").build());
- } catch (final SecurityException e) {
- Log.e(TAG, "No permission to communicate with the dictionary provider", e);
- return;
- }
- if (null == providerClient) {
- Log.e(TAG, "Can't establish communication with the dictionary provider");
- return;
- }
- try {
- final List idList = getWordListWordListInfos(locale, context,
- hasDefaultWordList);
- for (WordListInfo id : idList) {
- installWordListToStaging(id.mId, id.mLocale, id.mRawChecksum, providerClient,
- context);
- }
- } finally {
- providerClient.release();
- }
- }
-
- /**
- * Downloads the dictionary if it was never requested/used.
- *
- * @param locale locale to download
- * @param context the context for resources and providers.
- * @param hasDefaultWordList whether the default wordlist exists in the resources.
- */
- public static void downloadDictIfNeverRequested(final Locale locale,
- final Context context, final boolean hasDefaultWordList) {
- getWordListWordListInfos(locale, context, hasDefaultWordList);
- }
-
/**
* Copies the data in an input stream to a target file if the magic number matches.
*
@@ -533,7 +339,6 @@ public final class BinaryDictionaryFileDumper {
InputStream inputStream = null;
try {
inputStream = context.getResources().openRawResource(metadataResourceId);
- UpdateHandler.handleMetadata(context, inputStream, clientId);
} catch (Exception e) {
Log.w(TAG, "Failed to read metadata.json from resources", e);
} finally {
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java
index 44b5d104..08c7026d 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java
@@ -241,19 +241,6 @@ final public class BinaryDictionaryGetter {
*/
public static ArrayList getDictionaryFiles(final Locale locale,
final Context context, boolean notifyDictionaryPackForUpdates) {
- if (notifyDictionaryPackForUpdates) {
- final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable(
- context, locale);
- // It makes sure that the first time keyboard comes up and the dictionaries are reset,
- // the DB is populated with the appropriate values for each locale. Helps in downloading
- // the dictionaries when the user enables and switches new languages before the
- // DictionaryService runs.
- BinaryDictionaryFileDumper.downloadDictIfNeverRequested(
- locale, context, hasDefaultWordList);
-
- // Move a staging files to the cache ddirectories if any.
- DictionaryInfoUtils.moveStagingFilesIfExists(context);
- }
final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
final DictPackSettings dictPackSettings = new DictPackSettings(context);
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/SystemBroadcastReceiver.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/SystemBroadcastReceiver.java
index 68eb57c6..d597b197 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/SystemBroadcastReceiver.java
@@ -32,7 +32,6 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants;
-import org.dslul.openboard.inputmethod.dictionarypack.DownloadManagerWrapper;
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
import org.dslul.openboard.inputmethod.latin.setup.SetupActivity;
@@ -77,12 +76,6 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
toggleAppIcon(context);
-
- // Remove all the previously scheduled downloads. This will also makes sure
- // that any erroneously stuck downloads will get cleared. (b/21797386)
- removeOldDownloads(context);
- // b/21797386
- // downloadLatestDictionaries(context);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
toggleAppIcon(context);
@@ -110,38 +103,6 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
}
}
- private void removeOldDownloads(Context context) {
- try {
- Log.i(TAG, "Removing the old downloads in progress of the previous keyboard version.");
- final DownloadManagerWrapper downloadManagerWrapper = new DownloadManagerWrapper(
- context);
- final DownloadManager.Query q = new DownloadManager.Query();
- // Query all the download statuses except the succeeded ones.
- q.setFilterByStatus(DownloadManager.STATUS_FAILED
- | DownloadManager.STATUS_PAUSED
- | DownloadManager.STATUS_PENDING
- | DownloadManager.STATUS_RUNNING);
- final Cursor c = downloadManagerWrapper.query(q);
- if (c != null) {
- for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
- final long downloadId = c
- .getLong(c.getColumnIndex(DownloadManager.COLUMN_ID));
- downloadManagerWrapper.remove(downloadId);
- Log.i(TAG, "Removed the download with Id: " + downloadId);
- }
- c.close();
- }
- } catch (Exception e) {
- Log.e(TAG, "Exception while removing old downloads.");
- }
- }
-
- private void downloadLatestDictionaries(Context context) {
- final Intent updateIntent = new Intent(
- DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION);
- context.sendBroadcast(updateIntent);
- }
-
public static void toggleAppIcon(final Context context) {
final int appInfoFlags = context.getApplicationInfo().flags;
final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0;
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountStateChangedListener.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountStateChangedListener.java
deleted file mode 100644
index 5a14862f..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountStateChangedListener.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.latin.accounts;
-
-import androidx.annotation.NonNull;
-
-import javax.annotation.Nullable;
-
-/**
- * Handles changes to account used to sign in to the keyboard.
- * e.g. account switching/sign-in/sign-out from the keyboard
- * user toggling the sync preference.
- */
-public class AccountStateChangedListener {
-
- /**
- * Called when the current account being used in keyboard is signed out.
- *
- * @param oldAccount the account that was signed out of.
- */
- public static void onAccountSignedOut(@NonNull String oldAccount) {
- }
-
- /**
- * Called when the user signs-in to the keyboard.
- * This may be called when the user switches accounts to sign in with a different account.
- *
- * @param oldAccount the previous account that was being used for sign-in.
- * May be null for a fresh sign-in.
- * @param newAccount the account being used for sign-in.
- */
- public static void onAccountSignedIn(@Nullable String oldAccount, @NonNull String newAccount) {
- }
-
- /**
- * Called when the user toggles the sync preference.
- *
- * @param account the account being used for sync.
- * @param syncEnabled indicates whether sync has been enabled or not.
- */
- public static void onSyncPreferenceChanged(@Nullable String account, boolean syncEnabled) {
- }
-
- /**
- * Forces an immediate sync to happen.
- * This should only be used for debugging purposes.
- *
- * @param account the account to use for sync.
- */
- public static void forceSync(@Nullable String account) {
- }
-
- /**
- * Forces an immediate deletion of user's data.
- * This should only be used for debugging purposes.
- *
- * @param account the account to use for sync.
- */
- public static void forceDelete(@Nullable String account) {
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountsChangedReceiver.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountsChangedReceiver.java
deleted file mode 100644
index 7bdca052..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AccountsChangedReceiver.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.latin.accounts;
-
-import android.accounts.AccountManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
-import org.dslul.openboard.inputmethod.latin.settings.LocalSettingsConstants;
-
-/**
- * {@link BroadcastReceiver} for {@link AccountManager#LOGIN_ACCOUNTS_CHANGED_ACTION}.
- */
-public class AccountsChangedReceiver extends BroadcastReceiver {
- static final String TAG = "AccountsChangedReceiver";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
- Log.w(TAG, "Received unknown broadcast: " + intent);
- return;
- }
-
- // Ideally the account preference could live in a different preferences file
- // that wasn't being backed up and restored, however the preference fragments
- // currently only deal with the default shared preferences which is why
- // separating this out into a different file is not trivial currently.
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- final String currentAccount = prefs.getString(
- LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
- removeUnknownAccountFromPreference(prefs, getAccountsForLogin(context), currentAccount);
- }
-
- /**
- * Helper method to help test this receiver.
- */
- @UsedForTesting
- protected String[] getAccountsForLogin(Context context) {
- return LoginAccountUtils.getAccountsForLogin(context);
- }
-
- /**
- * Removes the currentAccount from preferences if it's not found
- * in the list of current accounts.
- */
- private static void removeUnknownAccountFromPreference(final SharedPreferences prefs,
- final String[] accounts, final String currentAccount) {
- if (currentAccount == null) {
- return;
- }
- for (final String account : accounts) {
- if (TextUtils.equals(currentAccount, account)) {
- return;
- }
- }
- Log.i(TAG, "The current account was removed from the system: " + currentAccount);
- prefs.edit()
- .remove(LocalSettingsConstants.PREF_ACCOUNT_NAME)
- .apply();
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AuthUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AuthUtils.java
deleted file mode 100644
index 12823b25..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/AuthUtils.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.latin.accounts;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-
-import java.io.IOException;
-
-/**
- * Utility class that handles generation/invalidation of auth tokens in the app.
- */
-public class AuthUtils {
- private final AccountManager mAccountManager;
-
- public AuthUtils(Context context) {
- mAccountManager = AccountManager.get(context);
- }
-
- /**
- * @see AccountManager#invalidateAuthToken(String, String)
- */
- public void invalidateAuthToken(final String accountType, final String authToken) {
- mAccountManager.invalidateAuthToken(accountType, authToken);
- }
-
- /**
- * @see AccountManager#getAuthToken(
- * Account, String, Bundle, boolean, AccountManagerCallback, Handler)
- */
- public AccountManagerFuture getAuthToken(final Account account,
- final String authTokenType, final Bundle options, final boolean notifyAuthFailure,
- final AccountManagerCallback callback, final Handler handler) {
- return mAccountManager.getAuthToken(account, authTokenType, options, notifyAuthFailure,
- callback, handler);
- }
-
- /**
- * @see AccountManager#blockingGetAuthToken(Account, String, boolean)
- */
- public String blockingGetAuthToken(final Account account, final String authTokenType,
- final boolean notifyAuthFailure) throws OperationCanceledException,
- AuthenticatorException, IOException {
- return mAccountManager.blockingGetAuthToken(account, authTokenType, notifyAuthFailure);
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/LoginAccountUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/LoginAccountUtils.java
deleted file mode 100644
index d4e27c42..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/accounts/LoginAccountUtils.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.latin.accounts;
-
-import android.content.Context;
-
-import javax.annotation.Nonnull;
-
-/**
- * Utility class for retrieving accounts that may be used for login.
- */
-public class LoginAccountUtils {
- /**
- * This defines the type of account this class deals with.
- * This account type is used when listing the accounts available on the device for login.
- */
- public static final String ACCOUNT_TYPE = "";
-
- private LoginAccountUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /**
- * Get the accounts available for login.
- *
- * @return an array of accounts. Empty (never null) if no accounts are available for login.
- */
- @Nonnull
- @SuppressWarnings("unused")
- public static String[] getAccountsForLogin(final Context context) {
- return new String[0];
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/define/ProductionFlags.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/define/ProductionFlags.java
index 2c7f4acb..76a7686c 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/define/ProductionFlags.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/define/ProductionFlags.java
@@ -39,20 +39,4 @@ public final class ProductionFlags {
*/
public static final boolean IS_SPLIT_KEYBOARD_SUPPORTED = true;
- /**
- * When {@code false}, account sign-in in keyboard is not yet ready to be enabled.
- */
- public static final boolean ENABLE_ACCOUNT_SIGN_IN = false;
-
- /**
- * When {@code true}, user history dictionary sync feature is ready to be enabled.
- */
- public static final boolean ENABLE_USER_HISTORY_DICTIONARY_SYNC =
- ENABLE_ACCOUNT_SIGN_IN && false;
-
- /**
- * When {@code true}, the IME maintains per account {@link UserHistoryDictionary}.
- */
- public static final boolean ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY =
- ENABLE_ACCOUNT_SIGN_IN && false;
}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/personalization/UserHistoryDictionary.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/personalization/UserHistoryDictionary.java
index 66d75bf9..f78c5374 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -58,25 +58,7 @@ public class UserHistoryDictionary extends ExpandableBinaryDictionary {
@UsedForTesting
static String getUserHistoryDictName(final String name, final Locale locale,
@Nullable final File dictFile, @Nullable final String account) {
- if (!ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) {
- return getDictName(name, locale, dictFile);
- }
- return getUserHistoryDictNamePerAccount(name, locale, dictFile, account);
- }
-
- /**
- * Uses the currently signed in account to determine the dictionary name.
- */
- private static String getUserHistoryDictNamePerAccount(final String name, final Locale locale,
- @Nullable final File dictFile, @Nullable final String account) {
- if (dictFile != null) {
- return dictFile.getName();
- }
- String dictName = name + "." + locale.toString();
- if (account != null) {
- dictName += "." + account;
- }
- return dictName;
+ return getDictName(name, locale, dictFile);
}
// Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AccountsSettingsFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AccountsSettingsFragment.java
deleted file mode 100644
index 66eb917b..00000000
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AccountsSettingsFragment.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * 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 org.dslul.openboard.inputmethod.latin.settings;
-
-import static org.dslul.openboard.inputmethod.latin.settings.LocalSettingsConstants.PREF_ACCOUNT_NAME;
-import static org.dslul.openboard.inputmethod.latin.settings.LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC;
-
-import android.Manifest;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnShowListener;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.TwoStatePreference;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
-import org.dslul.openboard.inputmethod.latin.R;
-import org.dslul.openboard.inputmethod.latin.accounts.AccountStateChangedListener;
-import org.dslul.openboard.inputmethod.latin.accounts.LoginAccountUtils;
-import org.dslul.openboard.inputmethod.latin.define.ProductionFlags;
-import org.dslul.openboard.inputmethod.latin.permissions.PermissionsUtil;
-import org.dslul.openboard.inputmethod.latin.utils.ManagedProfileUtils;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.annotation.Nullable;
-
-/**
- * "Accounts & Privacy" settings sub screen.
- *
- * This settings sub screen handles the following preferences:
- * Account selection/management for IME
- * Sync preferences
- * Privacy preferences
- */
-public final class AccountsSettingsFragment extends SubScreenFragment {
- private static final String PREF_ENABLE_SYNC_NOW = "pref_enable_cloud_sync";
- private static final String PREF_SYNC_NOW = "pref_sync_now";
- private static final String PREF_CLEAR_SYNC_DATA = "pref_clear_sync_data";
-
- static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
-
- /**
- * Onclick listener for sync now pref.
- */
- private final Preference.OnPreferenceClickListener mSyncNowListener =
- new SyncNowListener();
- /**
- * Onclick listener for delete sync pref.
- */
- private final Preference.OnPreferenceClickListener mDeleteSyncDataListener =
- new DeleteSyncDataListener();
-
- /**
- * Onclick listener for enable sync pref.
- */
- private final Preference.OnPreferenceClickListener mEnableSyncClickListener =
- new EnableSyncClickListener();
-
- /**
- * Enable sync checkbox pref.
- */
- private TwoStatePreference mEnableSyncPreference;
-
- /**
- * Enable sync checkbox pref.
- */
- private Preference mSyncNowPreference;
-
- /**
- * Clear sync data pref.
- */
- private Preference mClearSyncDataPreference;
-
- /**
- * Account switcher preference.
- */
- private Preference mAccountSwitcher;
-
- /**
- * Stores if we are currently detecting a managed profile.
- */
- private AtomicBoolean mManagedProfileBeingDetected = new AtomicBoolean(true);
-
- /**
- * Stores if we have successfully detected if the device has a managed profile.
- */
- private AtomicBoolean mHasManagedProfile = new AtomicBoolean(false);
-
- @Override
- public void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- addPreferencesFromResource(R.xml.prefs_screen_accounts);
-
- mAccountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
- mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW);
- mSyncNowPreference = findPreference(PREF_SYNC_NOW);
- mClearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA);
-
- if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
- final Preference enableMetricsLogging =
- findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
- final Resources res = getResources();
- if (enableMetricsLogging != null) {
- final String enableMetricsLoggingTitle = res.getString(
- R.string.enable_metrics_logging, getApplicationName());
- enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
- }
- } else {
- removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
- }
-
- if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
- removeSyncPreferences();
- } else {
- // Disable by default till we are sure we can enable this.
- disableSyncPreferences();
- new ManagedProfileCheckerTask(this).execute();
- }
- }
-
- /**
- * Task to check work profile. If found, it removes the sync prefs. If not,
- * it enables them.
- */
- private static class ManagedProfileCheckerTask extends AsyncTask {
- private final AccountsSettingsFragment mFragment;
-
- private ManagedProfileCheckerTask(final AccountsSettingsFragment fragment) {
- mFragment = fragment;
- }
-
- @Override
- protected void onPreExecute() {
- mFragment.mManagedProfileBeingDetected.set(true);
- }
- @Override
- protected Boolean doInBackground(Void... params) {
- return ManagedProfileUtils.getInstance().hasWorkProfile(mFragment.getActivity());
- }
-
- @Override
- protected void onPostExecute(final Boolean hasWorkProfile) {
- mFragment.mHasManagedProfile.set(hasWorkProfile);
- mFragment.mManagedProfileBeingDetected.set(false);
- mFragment.refreshSyncSettingsUI();
- }
- }
-
- private void enableSyncPreferences(final String[] accountsForLogin,
- final String currentAccountName) {
- if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
- return;
- }
- mAccountSwitcher.setEnabled(true);
-
- mEnableSyncPreference.setEnabled(true);
- mEnableSyncPreference.setOnPreferenceClickListener(mEnableSyncClickListener);
-
- mSyncNowPreference.setEnabled(true);
- mSyncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
-
- mClearSyncDataPreference.setEnabled(true);
- mClearSyncDataPreference.setOnPreferenceClickListener(mDeleteSyncDataListener);
-
- if (currentAccountName != null) {
- mAccountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- if (accountsForLogin.length > 0) {
- // TODO: Add addition of account.
- createAccountPicker(accountsForLogin, getSignedInAccountName(),
- new AccountChangedListener(null)).show();
- }
- return true;
- }
- });
- }
- }
-
- /**
- * Two reasons for disable - work profile or no accounts on device.
- */
- private void disableSyncPreferences() {
- if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
- return;
- }
-
- mAccountSwitcher.setEnabled(false);
- mEnableSyncPreference.setEnabled(false);
- mSyncNowPreference.setEnabled(false);
- mClearSyncDataPreference.setEnabled(false);
- }
-
- /**
- * Called only when ProductionFlag is turned off.
- */
- private void removeSyncPreferences() {
- removePreference(PREF_ACCCOUNT_SWITCHER);
- removePreference(PREF_ENABLE_CLOUD_SYNC);
- removePreference(PREF_SYNC_NOW);
- removePreference(PREF_CLEAR_SYNC_DATA);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- refreshSyncSettingsUI();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
- if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) {
- refreshSyncSettingsUI();
- } else if (TextUtils.equals(key, PREF_ENABLE_CLOUD_SYNC)) {
- mEnableSyncPreference = (TwoStatePreference) findPreference(PREF_ENABLE_SYNC_NOW);
- final boolean syncEnabled = prefs.getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
- if (isSyncEnabled()) {
- mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary));
- } else {
- mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled));
- }
- AccountStateChangedListener.onSyncPreferenceChanged(getSignedInAccountName(),
- syncEnabled);
- }
- }
-
- /**
- * Checks different states like whether account is present or managed profile is present
- * and sets the sync settings accordingly.
- */
- private void refreshSyncSettingsUI() {
- if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
- return;
- }
- boolean hasAccountsPermission = PermissionsUtil.checkAllPermissionsGranted(
- getActivity(), Manifest.permission.READ_CONTACTS);
-
- final String[] accountsForLogin = hasAccountsPermission ?
- LoginAccountUtils.getAccountsForLogin(getActivity()) : new String[0];
- final String currentAccount = hasAccountsPermission ? getSignedInAccountName() : null;
-
- if (hasAccountsPermission && !mManagedProfileBeingDetected.get() &&
- !mHasManagedProfile.get() && accountsForLogin.length > 0) {
- // Sync can be used by user; enable all preferences.
- enableSyncPreferences(accountsForLogin, currentAccount);
- } else {
- // Sync cannot be used by user; disable all preferences.
- disableSyncPreferences();
- }
- refreshSyncSettingsMessaging(hasAccountsPermission, mManagedProfileBeingDetected.get(),
- mHasManagedProfile.get(), accountsForLogin.length > 0,
- currentAccount);
- }
-
- /**
- * @param hasAccountsPermission whether the app has the permission to read accounts.
- * @param managedProfileBeingDetected whether we are in process of determining work profile.
- * @param hasManagedProfile whether the device has work profile.
- * @param hasAccountsForLogin whether the device has enough accounts for login.
- * @param currentAccount the account currently selected in the application.
- */
- private void refreshSyncSettingsMessaging(boolean hasAccountsPermission,
- boolean managedProfileBeingDetected,
- boolean hasManagedProfile,
- boolean hasAccountsForLogin,
- String currentAccount) {
- if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
- return;
- }
-
- if (!hasAccountsPermission) {
- mEnableSyncPreference.setChecked(false);
- mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled));
- mAccountSwitcher.setSummary("");
- return;
- } else if (managedProfileBeingDetected) {
- // If we are determining eligiblity, we show empty summaries.
- // Once we have some deterministic result, we set summaries based on different results.
- mEnableSyncPreference.setSummary("");
- mAccountSwitcher.setSummary("");
- } else if (hasManagedProfile) {
- mEnableSyncPreference.setSummary(
- getString(R.string.cloud_sync_summary_disabled_work_profile));
- } else if (!hasAccountsForLogin) {
- mEnableSyncPreference.setSummary(getString(R.string.add_account_to_enable_sync));
- } else if (isSyncEnabled()) {
- mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary));
- } else {
- mEnableSyncPreference.setSummary(getString(R.string.cloud_sync_summary_disabled));
- }
-
- // Set some interdependent settings.
- // No account automatically turns off sync.
- if (!managedProfileBeingDetected && !hasManagedProfile) {
- if (currentAccount != null) {
- mAccountSwitcher.setSummary(getString(R.string.account_selected, currentAccount));
- } else {
- mEnableSyncPreference.setChecked(false);
- mAccountSwitcher.setSummary(getString(R.string.no_accounts_selected));
- }
- }
- }
-
- @Nullable
- String getSignedInAccountName() {
- return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
- }
-
- boolean isSyncEnabled() {
- return getSharedPreferences().getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
- }
-
- /**
- * Creates an account picker dialog showing the given accounts in a list and selecting
- * the selected account by default. The list of accounts must not be null/empty.
- *
- * Package-private for testing.
- *
- * @param accounts list of accounts on the device.
- * @param selectedAccount currently selected account
- * @param positiveButtonClickListener listener that gets called when positive button is
- * clicked
- */
- @UsedForTesting
- AlertDialog createAccountPicker(final String[] accounts,
- final String selectedAccount,
- final DialogInterface.OnClickListener positiveButtonClickListener) {
- if (accounts == null || accounts.length == 0) {
- throw new IllegalArgumentException("List of accounts must not be empty");
- }
-
- // See if the currently selected account is in the list.
- // If it is, the entry is selected, and a sign-out button is provided.
- // If it isn't, select the 0th account by default which will get picked up
- // if the user presses OK.
- int index = 0;
- boolean isSignedIn = false;
- for (int i = 0; i < accounts.length; i++) {
- if (TextUtils.equals(accounts[i], selectedAccount)) {
- index = i;
- isSignedIn = true;
- break;
- }
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.account_select_title)
- .setSingleChoiceItems(accounts, index, null)
- .setPositiveButton(R.string.account_select_ok, positiveButtonClickListener)
- .setNegativeButton(R.string.account_select_cancel, null);
- if (isSignedIn) {
- builder.setNeutralButton(R.string.account_select_sign_out, positiveButtonClickListener);
- }
- return builder.create();
- }
-
- /**
- * Listener for a account selection changes from the picker.
- * Persists/removes the account to/from shared preferences and sets up sync if required.
- */
- class AccountChangedListener implements DialogInterface.OnClickListener {
- /**
- * Represents preference that should be changed based on account chosen.
- */
- private TwoStatePreference mDependentPreference;
-
- AccountChangedListener(final TwoStatePreference dependentPreference) {
- mDependentPreference = dependentPreference;
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- final String oldAccount = getSignedInAccountName();
- switch (which) {
- case DialogInterface.BUTTON_POSITIVE: // Signed in
- final ListView lv = ((AlertDialog)dialog).getListView();
- final String newAccount =
- (String) lv.getItemAtPosition(lv.getCheckedItemPosition());
- getSharedPreferences()
- .edit()
- .putString(PREF_ACCOUNT_NAME, newAccount)
- .apply();
- AccountStateChangedListener.onAccountSignedIn(oldAccount, newAccount);
- if (mDependentPreference != null) {
- mDependentPreference.setChecked(true);
- }
- break;
- case DialogInterface.BUTTON_NEUTRAL: // Signed out
- AccountStateChangedListener.onAccountSignedOut(oldAccount);
- getSharedPreferences()
- .edit()
- .remove(PREF_ACCOUNT_NAME)
- .apply();
- break;
- }
- }
- }
-
- /**
- * Listener that initiates the process of sync in the background.
- */
- class SyncNowListener implements Preference.OnPreferenceClickListener {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- AccountStateChangedListener.forceSync(getSignedInAccountName());
- return true;
- }
- }
-
- /**
- * Listener that initiates the process of deleting user's data from the cloud.
- */
- class DeleteSyncDataListener implements Preference.OnPreferenceClickListener {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- final AlertDialog confirmationDialog = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.clear_sync_data_title)
- .setMessage(R.string.clear_sync_data_confirmation)
- .setPositiveButton(R.string.clear_sync_data_ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- AccountStateChangedListener.forceDelete(
- getSignedInAccountName());
- }
- }
- })
- .setNegativeButton(R.string.cloud_sync_cancel, null /* OnClickListener */)
- .create();
- confirmationDialog.show();
- return true;
- }
- }
-
- /**
- * Listens to events when user clicks on "Enable sync" feature.
- */
- class EnableSyncClickListener implements OnShowListener, Preference.OnPreferenceClickListener {
- // TODO(cvnguyen): Write tests.
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- final TwoStatePreference syncPreference = (TwoStatePreference) preference;
- if (syncPreference.isChecked()) {
- // Uncheck for now.
- syncPreference.setChecked(false);
-
- // Show opt-in.
- final AlertDialog optInDialog = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.cloud_sync_title)
- .setMessage(R.string.cloud_sync_opt_in_text)
- .setPositiveButton(R.string.account_select_ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- final Context context = getActivity();
- final String[] accountsForLogin =
- LoginAccountUtils.getAccountsForLogin(context);
- createAccountPicker(accountsForLogin,
- getSignedInAccountName(),
- new AccountChangedListener(syncPreference))
- .show();
- }
- }
- })
- .setNegativeButton(R.string.cloud_sync_cancel, null)
- .create();
- optInDialog.setOnShowListener(this);
- optInDialog.show();
- }
- return true;
- }
-
- @Override
- public void onShow(DialogInterface dialog) {
- TextView messageView = (TextView) ((AlertDialog) dialog).findViewById(
- android.R.id.message);
- if (messageView != null) {
- messageView.setMovementMethod(LinkMovementMethod.getInstance());
- }
- }
- }
-}
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java
index 20c867ec..679f60e6 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java
@@ -46,7 +46,6 @@ import javax.annotation.Nonnull;
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = Settings.class.getSimpleName();
// Settings screens
- public static final String SCREEN_ACCOUNTS = "screen_accounts";
public static final String SCREEN_THEME = "screen_theme";
public static final String SCREEN_DEBUG = "screen_debug";
public static final String SCREEN_GESTURE = "screen_gesture";
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsFragment.java
index 7256939b..b0cea4d5 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsFragment.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsFragment.java
@@ -52,10 +52,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.setTitle(
ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class));
- if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
- final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS);
- preferenceScreen.removePreference(accountsPreference);
- }
if (!JniUtils.sHaveGestureLib) {
final Preference gesturePreference = findPreference(Settings.SCREEN_GESTURE);
preferenceScreen.removePreference(gesturePreference);
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java
index 883cbec6..2d83f47f 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -25,7 +25,6 @@ import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
-import org.dslul.openboard.inputmethod.dictionarypack.UpdateHandler;
import org.dslul.openboard.inputmethod.latin.AssetFileAddress;
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter;
import org.dslul.openboard.inputmethod.latin.R;
@@ -61,8 +60,6 @@ public class DictionaryInfoUtils {
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
- private static final String TEMP_DICT_FILE_SUB = UpdateHandler.TEMP_DICT_FILE_SUB;
-
public static class DictionaryInfo {
private static final String LOCALE_COLUMN = "locale";
private static final String WORDLISTID_COLUMN = "id";
@@ -207,17 +204,6 @@ public class DictionaryInfoUtils {
return new File(DictionaryInfoUtils.getWordListStagingDirectory(context)).listFiles();
}
- @Nullable
- public static File[] getUnusedDictionaryList(final Context context) {
- return context.getFilesDir().listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String filename) {
- return !TextUtils.isEmpty(filename) && filename.endsWith(".dict")
- && filename.contains(TEMP_DICT_FILE_SUB);
- }
- });
- }
-
/**
* Returns the category for a given file name.
*
@@ -253,75 +239,6 @@ public class DictionaryInfoUtils {
return absoluteDirectoryName;
}
- /**
- * Generates a file name for the id and locale passed as an argument.
- *
- * In the current implementation the file name returned will always be unique for
- * any id/locale pair, but please do not expect that the id can be the same for
- * different dictionaries with different locales. An id should be unique for any
- * dictionary.
- * The file name is pretty much an URL-encoded version of the id inside a directory
- * named like the locale, except it will also escape characters that look dangerous
- * to some file systems.
- * @param id the id of the dictionary for which to get a file name
- * @param locale the locale for which to get the file name as a string
- * @param context the context to use for getting the directory
- * @return the name of the file to be created
- */
- public static String getCacheFileName(String id, String locale, Context context) {
- final String fileName = replaceFileNameDangerousCharacters(id);
- return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
- }
-
- public static String getStagingFileName(String id, String locale, Context context) {
- final String stagingDirectory = getWordListStagingDirectory(context);
- // create the directory if it does not exist.
- final File directory = new File(stagingDirectory);
- if (!directory.exists()) {
- if (!directory.mkdirs()) {
- Log.e(TAG, "Could not create the staging directory.");
- }
- }
- // e.g. id="main:en_in", locale ="en_IN"
- final String fileName = replaceFileNameDangerousCharacters(
- locale + TEMP_DICT_FILE_SUB + id);
- return stagingDirectory + File.separator + fileName;
- }
-
- public static void moveStagingFilesIfExists(Context context) {
- final File[] stagingFiles = DictionaryInfoUtils.getStagingDirectoryList(context);
- if (stagingFiles != null && stagingFiles.length > 0) {
- for (final File stagingFile : stagingFiles) {
- final String fileName = stagingFile.getName();
- final int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
- if (index == -1) {
- // This should never happen.
- Log.e(TAG, "Staging file does not have ___ substring.");
- continue;
- }
- final String[] localeAndFileId = fileName.split(TEMP_DICT_FILE_SUB);
- if (localeAndFileId.length != 2) {
- Log.e(TAG, String.format("malformed staging file %s. Deleting.",
- stagingFile.getAbsoluteFile()));
- stagingFile.delete();
- continue;
- }
-
- final String locale = localeAndFileId[0];
- // already escaped while moving to staging.
- final String fileId = localeAndFileId[1];
- final String cacheDirectoryForLocale = getCacheDirectoryForLocale(locale, context);
- final String cacheFilename = cacheDirectoryForLocale + File.separator + fileId;
- final File cacheFile = new File(cacheFilename);
- // move the staging file to cache file.
- if (!FileUtils.renameTo(stagingFile, cacheFile)) {
- Log.e(TAG, String.format("Failed to rename from %s to %s.",
- stagingFile.getAbsoluteFile(), cacheFile.getAbsoluteFile()));
- }
- }
- }
- }
-
public static boolean isMainWordListId(final String id) {
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
@@ -521,25 +438,6 @@ public class DictionaryInfoUtils {
}
}
- // Retrieve downloaded dictionaries from the unused dictionaries.
- File[] unusedDictionaryList = getUnusedDictionaryList(context);
- if (unusedDictionaryList != null) {
- for (File dictionaryFile : unusedDictionaryList) {
- String fileName = dictionaryFile.getName();
- int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
- if (index == -1) {
- continue;
- }
- String locale = fileName.substring(0, index);
- DictionaryInfo dictionaryInfo = createDictionaryInfoForUnCachedFile(
- AssetFileAddress.makeFromFile(dictionaryFile),
- LocaleUtils.constructLocaleFromString(locale));
- if (dictionaryInfo != null) {
- addOrUpdateDictInfo(dictList, dictionaryInfo);
- }
- }
- }
-
// Retrieve files from assets
final Resources resources = context.getResources();
final AssetManager assets = resources.getAssets();
diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/FragmentUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/FragmentUtils.java
index 0a496539..9bfe5e51 100644
--- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/FragmentUtils.java
+++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/FragmentUtils.java
@@ -18,7 +18,6 @@ package org.dslul.openboard.inputmethod.latin.utils;
import org.dslul.openboard.inputmethod.dictionarypack.DictionarySettingsFragment;
import org.dslul.openboard.inputmethod.latin.about.AboutPreferences;
-import org.dslul.openboard.inputmethod.latin.settings.AccountsSettingsFragment;
import org.dslul.openboard.inputmethod.latin.settings.AdvancedSettingsFragment;
import org.dslul.openboard.inputmethod.latin.settings.AppearanceSettingsFragment;
import org.dslul.openboard.inputmethod.latin.settings.CorrectionSettingsFragment;
@@ -42,7 +41,6 @@ public class FragmentUtils {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
sLatinImeFragments.add(PreferencesSettingsFragment.class.getName());
- sLatinImeFragments.add(AccountsSettingsFragment.class.getName());
sLatinImeFragments.add(AppearanceSettingsFragment.class.getName());
sLatinImeFragments.add(ThemeSettingsFragment.class.getName());
sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName());
diff --git a/app/src/main/res/xml/prefs.xml b/app/src/main/res/xml/prefs.xml
index 429e2342..6688c893 100644
--- a/app/src/main/res/xml/prefs.xml
+++ b/app/src/main/res/xml/prefs.xml
@@ -22,10 +22,6 @@
android:fragment="org.dslul.openboard.inputmethod.latin.settings.PreferencesSettingsFragment"
android:title="@string/settings_screen_preferences"
android:key="screen_preferences" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-