diff options
Diffstat (limited to 'java/src')
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); |