aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java147
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListPreference.java6
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java28
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java15
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsFragment.java5
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupActivity.java11
6 files changed, 181 insertions, 31 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
index 39cfb60ff..88b5032e3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
@@ -16,11 +16,28 @@
package com.android.inputmethod.dictionarypack;
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Handler;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
import android.widget.ProgressBar;
public class DictionaryDownloadProgressBar extends ProgressBar {
+ @SuppressWarnings("unused")
+ private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName();
+ private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0;
+
+ private String mClientId;
+ private String mWordlistId;
+ private boolean mIsCurrentlyAttachedToWindow = false;
+ private Thread mReporterThread = null;
+
public DictionaryDownloadProgressBar(final Context context) {
super(context);
}
@@ -28,4 +45,134 @@ public class DictionaryDownloadProgressBar extends ProgressBar {
public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
+
+ public void setIds(final String clientId, final String wordlistId) {
+ mClientId = clientId;
+ mWordlistId = wordlistId;
+ }
+
+ static private int getDownloadManagerPendingIdFromWordlistId(final Context context,
+ final String clientId, final String wordlistId) {
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
+ final ContentValues wordlistValues =
+ MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId);
+ if (null == wordlistValues) {
+ // We don't know anything about a word list with this id. Bug? This should never
+ // happen, but still return to prevent a crash.
+ Log.e(TAG, "Unexpected word list ID: " + wordlistId);
+ return NOT_A_DOWNLOADMANAGER_PENDING_ID;
+ }
+ return wordlistValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN);
+ }
+
+ /*
+ * This method will stop any running updater thread for this progress bar and create and run
+ * a new one only if the progress bar is visible.
+ * Hence, as a result of calling this method, the progress bar will have an updater thread
+ * running if and only if the progress bar is visible.
+ */
+ private void updateReporterThreadRunningStatusAccordingToVisibility() {
+ if (null != mReporterThread) mReporterThread.interrupt();
+ if (mIsCurrentlyAttachedToWindow && View.VISIBLE == getVisibility()) {
+ final int downloadManagerPendingId =
+ getDownloadManagerPendingIdFromWordlistId(getContext(), mClientId, mWordlistId);
+ if (NOT_A_DOWNLOADMANAGER_PENDING_ID == downloadManagerPendingId) {
+ // Can't get the ID. This is never supposed to happen, but still clear the updater
+ // thread and return to avoid a crash.
+ mReporterThread = null;
+ return;
+ }
+ final UpdaterThread updaterThread =
+ new UpdaterThread(getContext(), downloadManagerPendingId);
+ updaterThread.start();
+ mReporterThread = updaterThread;
+ } else {
+ // We're not going to restart the thread anyway, so we may as well garbage collect it.
+ mReporterThread = null;
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ mIsCurrentlyAttachedToWindow = true;
+ updateReporterThreadRunningStatusAccordingToVisibility();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mIsCurrentlyAttachedToWindow = false;
+ updateReporterThreadRunningStatusAccordingToVisibility();
+ }
+
+ private class UpdaterThread extends Thread {
+ private final static int REPORT_PERIOD = 150; // how often to report progress, in ms
+ final DownloadManager mDownloadManager;
+ final int mId;
+ public UpdaterThread(final Context context, final int id) {
+ super();
+ mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ mId = id;
+ }
+ @Override
+ public void run() {
+ try {
+ // It's almost impossible that mDownloadManager is null (it would mean it has been
+ // disabled between pressing the 'install' button and displaying the progress
+ // bar), but just in case.
+ if (null == mDownloadManager) return;
+ final UpdateHelper updateHelper = new UpdateHelper();
+ final Query query = new Query().setFilterById(mId);
+ int lastProgress = 0;
+ setIndeterminate(true);
+ while (!isInterrupted()) {
+ final Cursor cursor = mDownloadManager.query(query);
+ if (null == cursor) {
+ // Can't contact DownloadManager: this should never happen.
+ return;
+ }
+ try {
+ if (cursor.moveToNext()) {
+ final int columnBytesDownloadedSoFar = cursor.getColumnIndex(
+ DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
+ final int bytesDownloadedSoFar =
+ cursor.getInt(columnBytesDownloadedSoFar);
+ updateHelper.setProgressFromAnotherThread(bytesDownloadedSoFar);
+ } else {
+ // Download has finished and DownloadManager has already been asked to
+ // clean up the db entry.
+ updateHelper.setProgressFromAnotherThread(getMax());
+ return;
+ }
+ } finally {
+ cursor.close();
+ }
+ Thread.sleep(REPORT_PERIOD);
+ }
+ } catch (InterruptedException e) {
+ // Do nothing and terminate normally.
+ }
+ }
+
+ private class UpdateHelper implements Runnable {
+ private int mProgress;
+ @Override
+ public void run() {
+ setIndeterminate(false);
+ setProgress(mProgress);
+ }
+ public void setProgressFromAnotherThread(final int progress) {
+ if (mProgress != progress) {
+ mProgress = progress;
+ // For some unknown reason, setProgress just does not work from a separate
+ // thread, although the code in ProgressBar looks like it should. Thus, we
+ // resort to a runnable posted to the handler of the view.
+ final Handler handler = getHandler();
+ // It's possible to come here before this view has been laid out. If so,
+ // just ignore the call - it will be updated again later.
+ if (null == handler) return;
+ handler.post(this);
+ }
+ }
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 1340a41fc..29015d61b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -24,7 +24,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ListView;
-import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.inputmethod.latin.R;
@@ -195,9 +194,10 @@ public final class WordListPreference extends Preference {
super.onBindView(view);
((ViewGroup)view).setLayoutTransition(null);
- final ProgressBar progressBar =
- (ProgressBar)view.findViewById(R.id.dictionary_line_progress_bar);
+ 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);
status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 4a2c3bb80..a9b58de44 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -210,7 +210,7 @@ public final class BinaryDictionaryFileDumper {
* 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 AssetFileAddress cacheWordList(final String wordlistId, final String locale,
+ private static void cacheWordList(final String wordlistId, final String locale,
final ContentProviderClient providerClient, final Context context) {
final int COMPRESSED_CRYPTED_COMPRESSED = 0;
final int CRYPTED_COMPRESSED = 1;
@@ -228,7 +228,7 @@ public final class BinaryDictionaryFileDumper {
providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
} catch (RemoteException e) {
Log.e(TAG, "Can't communicate with the dictionary pack", e);
- return null;
+ return;
}
final String finalFileName =
DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
@@ -237,11 +237,11 @@ public final class BinaryDictionaryFileDumper {
tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
} catch (IOException e) {
Log.e(TAG, "Can't open the temporary file", e);
- return null;
+ return;
}
for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) {
- InputStream originalSourceStream = null;
+ final InputStream originalSourceStream;
InputStream inputStream = null;
InputStream uncompressedStream = null;
InputStream decryptedStream = null;
@@ -254,7 +254,7 @@ public final class BinaryDictionaryFileDumper {
// 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 null;
+ if (null == afd) return;
originalSourceStream = afd.createInputStream();
// Open output.
outputFile = new File(tempFileName);
@@ -305,7 +305,7 @@ public final class BinaryDictionaryFileDumper {
}
BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
// Success! Close files (through the finally{} clause) and return.
- return AssetFileAddress.makeFromFileName(finalFileName);
+ return;
} catch (Exception e) {
if (DEBUG) {
Log.i(TAG, "Can't open word list in mode " + mode, e);
@@ -320,7 +320,7 @@ public final class BinaryDictionaryFileDumper {
} finally {
// Ignore exceptions while closing files.
try {
- // inputStream.close() will close afd, we should not call afd.close().
+ if (null != afd) afd.close();
if (null != inputStream) inputStream.close();
if (null != uncompressedStream) uncompressedStream.close();
if (null != decryptedStream) decryptedStream.close();
@@ -350,7 +350,6 @@ public final class BinaryDictionaryFileDumper {
} catch (RemoteException e) {
Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
}
- return null;
}
/**
@@ -359,30 +358,23 @@ public final class BinaryDictionaryFileDumper {
* 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.
- * @returns the addresses of the word list files, or null if no data could be obtained.
* @throw FileNotFoundException if the provider returns non-existent data.
* @throw IOException if the provider-returned data could not be read.
*/
- public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale,
+ public static void cacheWordListsFromContentProvider(final Locale locale,
final Context context, final boolean hasDefaultWordList) {
final ContentProviderClient providerClient = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
if (null == providerClient) {
Log.e(TAG, "Can't establish communication with the dictionary provider");
- return CollectionUtils.newArrayList();
+ return;
}
try {
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
- final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
for (WordListInfo id : idList) {
- final AssetFileAddress afd =
- cacheWordList(id.mId, id.mLocale, providerClient, context);
- if (null != afd) {
- fileAddressList.add(afd);
- }
+ cacheWordList(id.mId, id.mLocale, providerClient, context);
}
- return fileAddressList;
} finally {
providerClient.release();
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index ddd72f18e..98eadcacb 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -95,8 +95,16 @@ final class BinaryDictionaryGetter {
+ fallbackResId);
return null;
}
- return AssetFileAddress.makeFromFileNameAndOffset(
- context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+ try {
+ return AssetFileAddress.makeFromFileNameAndOffset(
+ context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
+ } finally {
+ try {
+ afd.close();
+ } catch (IOException e) {
+ // Ignored
+ }
+ }
}
private static final class DictPackSettings {
@@ -282,9 +290,6 @@ final class BinaryDictionaryGetter {
final Context context) {
final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
- // cacheWordListsFromContentProvider returns the list of files it copied to local
- // storage, but we don't really care about what was copied NOW: what we want is the
- // list of everything we ever cached, so we ignore the return value.
// TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
// Service yet
if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 830cae9b8..835ef7b46 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -420,10 +420,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
// not present or disabled. In this case we need to remove the preference.
getPreferenceScreen().removePreference(userDictionaryPreference);
} else if (localeList.size() <= 1) {
- final Intent intent =
- new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION);
- userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
- userDictionaryPreference.setIntent(intent);
userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
// If the size of localeList is 0, we don't set the locale parameter in the
// extras. This will be interpreted by the UserDictionarySettings class as
@@ -436,7 +432,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
userDictionaryPreference.getExtras().putString("locale", locale);
}
} else {
- userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
}
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index 651fea6ab..044180bd6 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -26,6 +26,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.provider.Settings;
+import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -44,6 +45,8 @@ import java.util.ArrayList;
// TODO: Use Fragment to implement welcome screen and setup steps.
public final class SetupActivity extends Activity implements View.OnClickListener {
+ private static final String TAG = SetupActivity.class.getSimpleName();
+
private View mWelcomeScreen;
private View mSetupScreen;
private Uri mWelcomeVideoUri;
@@ -198,6 +201,14 @@ public final class SetupActivity extends Activity implements View.OnClickListene
mWelcomeVideoView.setBackgroundResource(0);
}
});
+ mWelcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(final MediaPlayer mp, final int what, final int extra) {
+ Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra);
+ mWelcomeVideoView.setVisibility(View.GONE);
+ return true;
+ }
+ });
mActionStart = findViewById(R.id.setup_start_label);
mActionStart.setOnClickListener(this);