1 /** 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.inputmethod.dictionarypack; 18 19 import android.app.DownloadManager; 20 import android.app.DownloadManager.Query; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.os.Handler; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.ProgressBar; 30 31 public class DictionaryDownloadProgressBar extends ProgressBar { 32 private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName(); 33 private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0; 34 35 private String mClientId; 36 private String mWordlistId; 37 private boolean mIsCurrentlyAttachedToWindow = false; 38 private Thread mReporterThread = null; 39 DictionaryDownloadProgressBar(final Context context)40 public DictionaryDownloadProgressBar(final Context context) { 41 super(context); 42 } 43 DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs)44 public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) { 45 super(context, attrs); 46 } 47 setIds(final String clientId, final String wordlistId)48 public void setIds(final String clientId, final String wordlistId) { 49 mClientId = clientId; 50 mWordlistId = wordlistId; 51 } 52 getDownloadManagerPendingIdFromWordlistId(final Context context, final String clientId, final String wordlistId)53 static private int getDownloadManagerPendingIdFromWordlistId(final Context context, 54 final String clientId, final String wordlistId) { 55 final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); 56 final ContentValues wordlistValues = 57 MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId); 58 if (null == wordlistValues) { 59 // We don't know anything about a word list with this id. Bug? This should never 60 // happen, but still return to prevent a crash. 61 Log.e(TAG, "Unexpected word list ID: " + wordlistId); 62 return NOT_A_DOWNLOADMANAGER_PENDING_ID; 63 } 64 return wordlistValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN); 65 } 66 67 /* 68 * This method will stop any running updater thread for this progress bar and create and run 69 * a new one only if the progress bar is visible. 70 * Hence, as a result of calling this method, the progress bar will have an updater thread 71 * running if and only if the progress bar is visible. 72 */ updateReporterThreadRunningStatusAccordingToVisibility()73 private void updateReporterThreadRunningStatusAccordingToVisibility() { 74 if (null != mReporterThread) mReporterThread.interrupt(); 75 if (mIsCurrentlyAttachedToWindow && View.VISIBLE == getVisibility()) { 76 final int downloadManagerPendingId = 77 getDownloadManagerPendingIdFromWordlistId(getContext(), mClientId, mWordlistId); 78 if (NOT_A_DOWNLOADMANAGER_PENDING_ID == downloadManagerPendingId) { 79 // Can't get the ID. This is never supposed to happen, but still clear the updater 80 // thread and return to avoid a crash. 81 mReporterThread = null; 82 return; 83 } 84 final UpdaterThread updaterThread = 85 new UpdaterThread(getContext(), downloadManagerPendingId); 86 updaterThread.start(); 87 mReporterThread = updaterThread; 88 } else { 89 // We're not going to restart the thread anyway, so we may as well garbage collect it. 90 mReporterThread = null; 91 } 92 } 93 94 @Override onAttachedToWindow()95 protected void onAttachedToWindow() { 96 mIsCurrentlyAttachedToWindow = true; 97 updateReporterThreadRunningStatusAccordingToVisibility(); 98 } 99 100 @Override onDetachedFromWindow()101 protected void onDetachedFromWindow() { 102 super.onDetachedFromWindow(); 103 mIsCurrentlyAttachedToWindow = false; 104 updateReporterThreadRunningStatusAccordingToVisibility(); 105 } 106 107 private class UpdaterThread extends Thread { 108 private final static int REPORT_PERIOD = 150; // how often to report progress, in ms 109 final DownloadManagerWrapper mDownloadManagerWrapper; 110 final int mId; UpdaterThread(final Context context, final int id)111 public UpdaterThread(final Context context, final int id) { 112 super(); 113 mDownloadManagerWrapper = new DownloadManagerWrapper(context); 114 mId = id; 115 } 116 @Override run()117 public void run() { 118 try { 119 final UpdateHelper updateHelper = new UpdateHelper(); 120 final Query query = new Query().setFilterById(mId); 121 setIndeterminate(true); 122 while (!isInterrupted()) { 123 final Cursor cursor = mDownloadManagerWrapper.query(query); 124 if (null == cursor) { 125 // Can't contact DownloadManager: this should never happen. 126 return; 127 } 128 try { 129 if (cursor.moveToNext()) { 130 final int columnBytesDownloadedSoFar = cursor.getColumnIndex( 131 DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); 132 final int bytesDownloadedSoFar = 133 cursor.getInt(columnBytesDownloadedSoFar); 134 updateHelper.setProgressFromAnotherThread(bytesDownloadedSoFar); 135 } else { 136 // Download has finished and DownloadManager has already been asked to 137 // clean up the db entry. 138 updateHelper.setProgressFromAnotherThread(getMax()); 139 return; 140 } 141 } finally { 142 cursor.close(); 143 } 144 Thread.sleep(REPORT_PERIOD); 145 } 146 } catch (InterruptedException e) { 147 // Do nothing and terminate normally. 148 } 149 } 150 151 class UpdateHelper implements Runnable { 152 private int mProgress; 153 @Override run()154 public void run() { 155 setIndeterminate(false); 156 setProgress(mProgress); 157 } setProgressFromAnotherThread(final int progress)158 public void setProgressFromAnotherThread(final int progress) { 159 if (mProgress != progress) { 160 mProgress = progress; 161 // For some unknown reason, setProgress just does not work from a separate 162 // thread, although the code in ProgressBar looks like it should. Thus, we 163 // resort to a runnable posted to the handler of the view. 164 final Handler handler = getHandler(); 165 // It's possible to come here before this view has been laid out. If so, 166 // just ignore the call - it will be updated again later. 167 if (null == handler) return; 168 handler.post(this); 169 } 170 } 171 } 172 } 173 } 174