• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DictionaryDownloadProgressBar(final Context context)41     public DictionaryDownloadProgressBar(final Context context) {
42         super(context);
43     }
44 
DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs)45     public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) {
46         super(context, attrs);
47     }
48 
setIds(final String clientId, final String wordlistId)49     public void setIds(final String clientId, final String wordlistId) {
50         mClientId = clientId;
51         mWordlistId = wordlistId;
52     }
53 
getDownloadManagerPendingIdFromWordlistId(final Context context, final String clientId, final String wordlistId)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      */
updateReporterThreadRunningStatusAccordingToVisibility()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
onAttachedToWindow()96     protected void onAttachedToWindow() {
97         mIsCurrentlyAttachedToWindow = true;
98         updateReporterThreadRunningStatusAccordingToVisibility();
99     }
100 
101     @Override
onDetachedFromWindow()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;
UpdaterThread(final Context context, final int id)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
run()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
run()159             public void run() {
160                 setIndeterminate(false);
161                 setProgress(mProgress);
162             }
setProgressFromAnotherThread(final int progress)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