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     @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