aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java5
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java4
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryService.java59
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java17
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java24
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java2
6 files changed, 65 insertions, 46 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index c5aca174a..6d6c8f5c6 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -47,6 +47,7 @@ public class ButtonSwitcher extends FrameLayout {
private Button mInstallButton;
private Button mCancelButton;
private Button mDeleteButton;
+ private DictionaryListInterfaceState mInterfaceState;
private OnClickListener mOnClickListener;
public ButtonSwitcher(Context context, AttributeSet attrs) {
@@ -57,9 +58,10 @@ public class ButtonSwitcher extends FrameLayout {
super(context, attrs, defStyle);
}
- public void reset() {
+ public void reset(final DictionaryListInterfaceState interfaceState) {
mStatus = NOT_INITIALIZED;
mAnimateToStatus = NOT_INITIALIZED;
+ mInterfaceState = interfaceState;
}
@Override
@@ -153,6 +155,7 @@ public class ButtonSwitcher extends FrameLayout {
private ViewPropertyAnimator animateButton(final View button, final int direction) {
final float outerX = getWidth();
final float innerX = button.getX() - button.getTranslationX();
+ mInterfaceState.removeFromCache((View)getParent());
if (ANIMATION_IN == direction) {
button.setClickable(true);
return button.animate().translationX(0);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index f1a2a8333..13c07de35 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -80,4 +80,8 @@ public class DictionaryListInterfaceState {
mViewCache.add(view);
return view;
}
+
+ public void removeFromCache(final View view) {
+ mViewCache.remove(view);
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 767f895dc..939c25f10 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,13 +22,14 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
-import android.util.Log;
import android.widget.Toast;
import com.android.inputmethod.latin.R;
import java.util.Locale;
import java.util.Random;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
@@ -92,21 +93,27 @@ public final class DictionaryService extends Service {
private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
/**
- * The last seen start Id. This must be stored because we must only call stopSelfResult() with
- * the last seen Id, or the service won't stop.
+ * An executor that serializes tasks given to it.
*/
- private int mLastSeenStartId;
-
- /**
- * The command count. We need this because we need to not call stopSelfResult() while we still
- * have commands running.
- */
- private int mCommandCount;
+ private ThreadPoolExecutor mExecutor;
+ private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;
@Override
public void onCreate() {
- mLastSeenStartId = 0;
- mCommandCount = 0;
+ // By default, a thread pool executor does not timeout its core threads, so it will
+ // never kill them when there isn't any work to do any more. That would mean the service
+ // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
+ // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
+ // the process to be reclaimed by the system any time after that if it's not doing
+ // anything else.
+ // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
+ // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
+ // so we can't use that.
+ mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+ WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
+ TimeUnit.SECONDS /* unit for keepAliveTime */,
+ new LinkedBlockingQueue<Runnable>() /* workQueue */);
+ mExecutor.allowCoreThreadTimeOut(true);
}
@Override
@@ -131,33 +138,35 @@ public final class DictionaryService extends Service {
* - 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;
- mLastSeenStartId = startId;
- mCommandCount += 1;
if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
// This is a UI action, it can't be run in another thread
showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
} else {
- // If it's a command that does not require UI, create a thread to do the work
- // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands.
- new Thread("updateOrFinishDownload") {
+ // 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);
- synchronized(self) {
- if (--mCommandCount <= 0) {
- if (!stopSelfResult(mLastSeenStartId)) {
- Log.e(TAG, "Can't stop ourselves");
- }
- }
- }
+ // 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);
}
- }.start();
+ });
}
return Service.START_REDELIVER_INTENT;
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 99cc5b924..ff5aba6d8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -199,6 +199,7 @@ 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);
}
}
@@ -358,21 +359,21 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
}
/**
- * Get the metadata download ID for a client ID.
+ * Get the metadata download ID for a metadata URI.
*
- * This will retrieve the download ID for the metadata file associated with a client ID.
- * If there is no metadata download in progress for this client, it will return NOT_AN_ID.
+ * 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 clientId the client ID to retrieve the metadata download ID of
+ * @param uri the URI to retrieve the metadata download ID of
* @return the metadata download ID, or NOT_AN_ID if no download is in progress
*/
- public static long getMetadataDownloadIdForClient(final Context context,
- final String clientId) {
+ public static long getMetadataDownloadIdForURI(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_CLIENT_ID_COLUMN + " = ?", new String[] { clientId },
+ CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
null, null, null, null);
try {
if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
@@ -782,6 +783,8 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
" but the values " + "contain a different ID : ", valuesClientId);
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,
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index f66ef8733..0e7c3bb7e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -272,23 +272,22 @@ public final class UpdateHandler {
}
/**
- * Cancels a pending update, if there is one.
+ * Cancels downloading a file, if there is one for this URI.
*
- * If none, this is a no-op.
+ * 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 clientId the id of the client
+ * @param metadataUri the URI to cancel
* @param manager an instance of DownloadManager
*/
private static void cancelUpdateWithDownloadManager(final Context context,
- final String clientId, final DownloadManager manager) {
+ final String metadataUri, final DownloadManager manager) {
synchronized (sSharedIdProtector) {
final long metadataDownloadId =
- MetadataDbHelper.getMetadataDownloadIdForClient(context, clientId);
+ MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
if (NOT_AN_ID == metadataDownloadId) return;
manager.remove(metadataDownloadId);
- writeMetadataDownloadId(context,
- MetadataDbHelper.getMetadataUriAsString(context, clientId), NOT_AN_ID);
+ writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
}
// Consider a cancellation as a failure. As such, inform listeners that the download
// has failed.
@@ -298,10 +297,10 @@ public final class UpdateHandler {
}
/**
- * Cancels a pending update, if there is one.
+ * Cancels a pending update for this client, if there is one.
*
- * If there is none, this is a no-op. This is a helper method that gets the
- * download manager service.
+ * 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
@@ -309,7 +308,8 @@ public final class UpdateHandler {
public static void cancelUpdate(final Context context, final String clientId) {
final DownloadManager manager =
(DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
- if (null != manager) cancelUpdateWithDownloadManager(context, clientId, manager);
+ final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
+ if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager);
}
/**
@@ -773,7 +773,7 @@ public final class UpdateHandler {
// 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);
+ + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
}
continue;
} else if (null == currentInfo) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 7ec7e9c13..ba1fce1a8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -224,7 +224,7 @@ public final class WordListPreference extends Preference {
(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
// reset it would animate from whatever its old state was.
- buttonSwitcher.reset();
+ buttonSwitcher.reset(mInterfaceState);
if (mInterfaceState.isOpen(mWordlistId)) {
// The button is open.
final int previousStatus = mInterfaceState.getStatus(mWordlistId);