aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java
blob: 1d84e5888c79d0ab9c77758646c3fa58338016ba (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.android.inputmethod.dictionarypack;

import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;

public class DictionaryDownloadProgressBar extends ProgressBar {
    private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName();
    private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0;

    private String mClientId;
    private String mWordlistId;
    private boolean mIsCurrentlyAttachedToWindow = false;
    private Thread mReporterThread = null;

    public DictionaryDownloadProgressBar(final Context context) {
        super(context);
    }

    public DictionaryDownloadProgressBar(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public void setIds(final String clientId, final String wordlistId) {
        mClientId = clientId;
        mWordlistId = wordlistId;
    }

    static private int getDownloadManagerPendingIdFromWordlistId(final Context context,
            final String clientId, final String wordlistId) {
        final SQLiteDatabase db = MetadataDbHelper.getDb(context, clientId);
        final ContentValues wordlistValues =
                MetadataDbHelper.getContentValuesOfLatestAvailableWordlistById(db, wordlistId);
        if (null == wordlistValues) {
            // We don't know anything about a word list with this id. Bug? This should never
            // happen, but still return to prevent a crash.
            Log.e(TAG, "Unexpected word list ID: " + wordlistId);
            return NOT_A_DOWNLOADMANAGER_PENDING_ID;
        }
        return wordlistValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN);
    }

    /*
     * This method will stop any running updater thread for this progress bar and create and run
     * a new one only if the progress bar is visible.
     * Hence, as a result of calling this method, the progress bar will have an updater thread
     * running if and only if the progress bar is visible.
     */
    private void updateReporterThreadRunningStatusAccordingToVisibility() {
        if (null != mReporterThread) mReporterThread.interrupt();
        if (mIsCurrentlyAttachedToWindow && View.VISIBLE == getVisibility()) {
            final int downloadManagerPendingId =
                    getDownloadManagerPendingIdFromWordlistId(getContext(), mClientId, mWordlistId);
            if (NOT_A_DOWNLOADMANAGER_PENDING_ID == downloadManagerPendingId) {
                // Can't get the ID. This is never supposed to happen, but still clear the updater
                // thread and return to avoid a crash.
                mReporterThread = null;
                return;
            }
            final UpdaterThread updaterThread =
                    new UpdaterThread(getContext(), downloadManagerPendingId);
            updaterThread.start();
            mReporterThread = updaterThread;
        } else {
            // We're not going to restart the thread anyway, so we may as well garbage collect it.
            mReporterThread = null;
        }
    }

    @Override
    protected void onAttachedToWindow() {
        mIsCurrentlyAttachedToWindow = true;
        updateReporterThreadRunningStatusAccordingToVisibility();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mIsCurrentlyAttachedToWindow = false;
        updateReporterThreadRunningStatusAccordingToVisibility();
    }

    private class UpdaterThread extends Thread {
        private final static int REPORT_PERIOD = 150; // how often to report progress, in ms
        final DownloadManagerWrapper mDownloadManagerWrapper;
        final int mId;
        public UpdaterThread(final Context context, final int id) {
            super();
            mDownloadManagerWrapper = new DownloadManagerWrapper(context);
            mId = id;
        }
        @Override
        public void run() {
            try {
                final UpdateHelper updateHelper = new UpdateHelper();
                final Query query = new Query().setFilterById(mId);
                setIndeterminate(true);
                while (!isInterrupted()) {
                    final Cursor cursor = mDownloadManagerWrapper.query(query);
                    if (null == cursor) {
                        // Can't contact DownloadManager: this should never happen.
                        return;
                    }
                    try {
                        if (cursor.moveToNext()) {
                            final int columnBytesDownloadedSoFar = cursor.getColumnIndex(
                                    DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
                            final int bytesDownloadedSoFar =
                                    cursor.getInt(columnBytesDownloadedSoFar);
                            updateHelper.setProgressFromAnotherThread(bytesDownloadedSoFar);
                        } else {
                            // Download has finished and DownloadManager has already been asked to
                            // clean up the db entry.
                            updateHelper.setProgressFromAnotherThread(getMax());
                            return;
                        }
                    } finally {
                        cursor.close();
                    }
                    Thread.sleep(REPORT_PERIOD);
                }
            } catch (InterruptedException e) {
                // Do nothing and terminate normally.
            }
        }

        private class UpdateHelper implements Runnable {
            private int mProgress;
            @Override
            public void run() {
                setIndeterminate(false);
                setProgress(mProgress);
            }
            public void setProgressFromAnotherThread(final int progress) {
                if (mProgress != progress) {
                    mProgress = progress;
                    // For some unknown reason, setProgress just does not work from a separate
                    // thread, although the code in ProgressBar looks like it should. Thus, we
                    // resort to a runnable posted to the handler of the view.
                    final Handler handler = getHandler();
                    // It's possible to come here before this view has been laid out. If so,
                    // just ignore the call - it will be updated again later.
                    if (null == handler) return;
                    handler.post(this);
                }
            }
        }
    }
}