Home | History | Annotate | Download | only in dictionarypack
      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 
     40     public DictionaryDownloadProgressBar(final Context context) {
     41         super(context);
     42     }
     43 
     44     public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) {
     45         super(context, attrs);
     46     }
     47 
     48     public void setIds(final String clientId, final String wordlistId) {
     49         mClientId = clientId;
     50         mWordlistId = wordlistId;
     51     }
     52 
     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      */
     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
     95     protected void onAttachedToWindow() {
     96         mIsCurrentlyAttachedToWindow = true;
     97         updateReporterThreadRunningStatusAccordingToVisibility();
     98     }
     99 
    100     @Override
    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;
    111         public UpdaterThread(final Context context, final int id) {
    112             super();
    113             mDownloadManagerWrapper = new DownloadManagerWrapper(context);
    114             mId = id;
    115         }
    116         @Override
    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
    154             public void run() {
    155                 setIndeterminate(false);
    156                 setProgress(mProgress);
    157             }
    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