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 @SuppressWarnings("unused") 33 private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName(); 34 private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0; 35 36 private String mClientId; 37 private String mWordlistId; 38 private boolean mIsCurrentlyAttachedToWindow = false; 39 private Thread mReporterThread = null; 40 41 public DictionaryDownloadProgressBar(final Context context) { 42 super(context); 43 } 44 45 public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) { 46 super(context, attrs); 47 } 48 49 public void setIds(final String clientId, final String wordlistId) { 50 mClientId = clientId; 51 mWordlistId = wordlistId; 52 } 53 54 static private int getDownloadManagerPendingIdFromWordlistId(final Context context, 55 final String clientId, final String wordlistId) { 56 final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId); 57 final ContentValues wordlistValues = 58 MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId); 59 if (null == wordlistValues) { 60 // We don't know anything about a word list with this id. Bug? This should never 61 // happen, but still return to prevent a crash. 62 Log.e(TAG, "Unexpected word list ID: " + wordlistId); 63 return NOT_A_DOWNLOADMANAGER_PENDING_ID; 64 } 65 return wordlistValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN); 66 } 67 68 /* 69 * This method will stop any running updater thread for this progress bar and create and run 70 * a new one only if the progress bar is visible. 71 * Hence, as a result of calling this method, the progress bar will have an updater thread 72 * running if and only if the progress bar is visible. 73 */ 74 private void updateReporterThreadRunningStatusAccordingToVisibility() { 75 if (null != mReporterThread) mReporterThread.interrupt(); 76 if (mIsCurrentlyAttachedToWindow && View.VISIBLE == getVisibility()) { 77 final int downloadManagerPendingId = 78 getDownloadManagerPendingIdFromWordlistId(getContext(), mClientId, mWordlistId); 79 if (NOT_A_DOWNLOADMANAGER_PENDING_ID == downloadManagerPendingId) { 80 // Can't get the ID. This is never supposed to happen, but still clear the updater 81 // thread and return to avoid a crash. 82 mReporterThread = null; 83 return; 84 } 85 final UpdaterThread updaterThread = 86 new UpdaterThread(getContext(), downloadManagerPendingId); 87 updaterThread.start(); 88 mReporterThread = updaterThread; 89 } else { 90 // We're not going to restart the thread anyway, so we may as well garbage collect it. 91 mReporterThread = null; 92 } 93 } 94 95 @Override 96 protected void onAttachedToWindow() { 97 mIsCurrentlyAttachedToWindow = true; 98 updateReporterThreadRunningStatusAccordingToVisibility(); 99 } 100 101 @Override 102 protected void 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 DownloadManager mDownloadManager; 110 final int mId; 111 public UpdaterThread(final Context context, final int id) { 112 super(); 113 mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 114 mId = id; 115 } 116 @Override 117 public void run() { 118 try { 119 // It's almost impossible that mDownloadManager is null (it would mean it has been 120 // disabled between pressing the 'install' button and displaying the progress 121 // bar), but just in case. 122 if (null == mDownloadManager) return; 123 final UpdateHelper updateHelper = new UpdateHelper(); 124 final Query query = new Query().setFilterById(mId); 125 int lastProgress = 0; 126 setIndeterminate(true); 127 while (!isInterrupted()) { 128 final Cursor cursor = mDownloadManager.query(query); 129 if (null == cursor) { 130 // Can't contact DownloadManager: this should never happen. 131 return; 132 } 133 try { 134 if (cursor.moveToNext()) { 135 final int columnBytesDownloadedSoFar = cursor.getColumnIndex( 136 DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); 137 final int bytesDownloadedSoFar = 138 cursor.getInt(columnBytesDownloadedSoFar); 139 updateHelper.setProgressFromAnotherThread(bytesDownloadedSoFar); 140 } else { 141 // Download has finished and DownloadManager has already been asked to 142 // clean up the db entry. 143 updateHelper.setProgressFromAnotherThread(getMax()); 144 return; 145 } 146 } finally { 147 cursor.close(); 148 } 149 Thread.sleep(REPORT_PERIOD); 150 } 151 } catch (InterruptedException e) { 152 // Do nothing and terminate normally. 153 } 154 } 155 156 private class UpdateHelper implements Runnable { 157 private int mProgress; 158 @Override 159 public void run() { 160 setIndeterminate(false); 161 setProgress(mProgress); 162 } 163 public void setProgressFromAnotherThread(final int progress) { 164 if (mProgress != progress) { 165 mProgress = progress; 166 // For some unknown reason, setProgress just does not work from a separate 167 // thread, although the code in ProgressBar looks like it should. Thus, we 168 // resort to a runnable posted to the handler of the view. 169 final Handler handler = getHandler(); 170 // It's possible to come here before this view has been laid out. If so, 171 // just ignore the call - it will be updated again later. 172 if (null == handler) return; 173 handler.post(this); 174 } 175 } 176 } 177 } 178 } 179