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
17package com.android.inputmethod.dictionarypack;
18
19import android.app.DownloadManager;
20import android.app.DownloadManager.Query;
21import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.database.sqlite.SQLiteDatabase;
25import android.os.Handler;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.View;
29import android.widget.ProgressBar;
30
31public 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