WordListPreference.java revision 2b34b4eab6b04299205f47a92311d4d00f9d805f
1/**
2 * Copyright (C) 2011 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.content.Context;
20import android.content.SharedPreferences;
21import android.preference.Preference;
22import android.util.Log;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewParent;
26import android.widget.ListView;
27import android.widget.TextView;
28
29import com.android.inputmethod.latin.R;
30
31import java.util.Locale;
32
33/**
34 * A preference for one word list.
35 *
36 * This preference refers to a single word list, as available in the dictionary
37 * pack. Upon being pressed, it displays a menu to allow the user to install, disable,
38 * enable or delete it as appropriate for the current state of the word list.
39 */
40public final class WordListPreference extends Preference {
41    static final private String TAG = WordListPreference.class.getSimpleName();
42
43    // What to display in the "status" field when we receive unknown data as a status from
44    // the content provider. Empty string sounds sensible.
45    static final private String NO_STATUS_MESSAGE = "";
46
47    /// Actions
48    static final private int ACTION_UNKNOWN = 0;
49    static final private int ACTION_ENABLE_DICT = 1;
50    static final private int ACTION_DISABLE_DICT = 2;
51    static final private int ACTION_DELETE_DICT = 3;
52
53    // Members
54    // The context to get resources
55    final Context mContext;
56    // The id of the client for which this preference is.
57    final String mClientId;
58    // The metadata word list id and version of this word list.
59    public final String mWordlistId;
60    public final int mVersion;
61    public final Locale mLocale;
62    public final String mDescription;
63    // The status
64    public int mStatus;
65    // The size of the dictionary file
66    private final int mFilesize;
67
68    private final DictionaryListInterfaceState mInterfaceState;
69    private final OnWordListPreferenceClick mPreferenceClickHandler =
70            new OnWordListPreferenceClick();
71    private final OnActionButtonClick mActionButtonClickHandler =
72            new OnActionButtonClick();
73
74    public WordListPreference(final Context context,
75            final DictionaryListInterfaceState dictionaryListInterfaceState, final String clientId,
76            final String wordlistId, final int version, final Locale locale,
77            final String description, final int status, final int filesize) {
78        super(context, null);
79        mContext = context;
80        mInterfaceState = dictionaryListInterfaceState;
81        mClientId = clientId;
82        mVersion = version;
83        mWordlistId = wordlistId;
84        mFilesize = filesize;
85        mLocale = locale;
86        mDescription = description;
87
88        setLayoutResource(R.layout.dictionary_line);
89
90        setTitle(description);
91        setStatus(status);
92        setKey(wordlistId);
93    }
94
95    private void setStatus(final int status) {
96        if (status == mStatus) return;
97        mStatus = status;
98        setSummary(getSummary(status));
99    }
100
101    @Override
102    public View onCreateView(final ViewGroup parent) {
103        final View orphanedView = mInterfaceState.findFirstOrphanedView();
104        if (null != orphanedView) return orphanedView; // Will be sent to onBindView
105        final View newView = super.onCreateView(parent);
106        return mInterfaceState.addToCacheAndReturnView(newView);
107    }
108
109    private String getSummary(final int status) {
110        switch (status) {
111            // If we are deleting the word list, for the user it's like it's already deleted.
112            // It should be reinstallable. Exposing to the user the whole complexity of
113            // the delayed deletion process between the dictionary pack and Android Keyboard
114            // would only be confusing.
115            case MetadataDbHelper.STATUS_DELETING:
116            case MetadataDbHelper.STATUS_AVAILABLE:
117                return mContext.getString(R.string.dictionary_available);
118            case MetadataDbHelper.STATUS_DOWNLOADING:
119                return mContext.getString(R.string.dictionary_downloading);
120            case MetadataDbHelper.STATUS_INSTALLED:
121                return mContext.getString(R.string.dictionary_installed);
122            case MetadataDbHelper.STATUS_DISABLED:
123                return mContext.getString(R.string.dictionary_disabled);
124            default:
125                return NO_STATUS_MESSAGE;
126        }
127    }
128
129    // The table below needs to be kept in sync with MetadataDbHelper.STATUS_* since it uses
130    // the values as indices.
131    private static final int sStatusActionList[][] = {
132        // MetadataDbHelper.STATUS_UNKNOWN
133        {},
134        // MetadataDbHelper.STATUS_AVAILABLE
135        { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT },
136        // MetadataDbHelper.STATUS_DOWNLOADING
137        { ButtonSwitcher.STATUS_CANCEL, ACTION_DISABLE_DICT },
138        // MetadataDbHelper.STATUS_INSTALLED
139        { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT },
140        // MetadataDbHelper.STATUS_DISABLED
141        { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT },
142        // MetadataDbHelper.STATUS_DELETING
143        // We show 'install' because the file is supposed to be deleted.
144        // The user may reinstall it.
145        { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }
146    };
147
148    private int getButtonSwitcherStatus(final int status) {
149        if (status >= sStatusActionList.length) {
150            Log.e(TAG, "Unknown status " + status);
151            return ButtonSwitcher.STATUS_NO_BUTTON;
152        }
153        return sStatusActionList[status][0];
154    }
155
156    private static int getActionIdFromStatusAndMenuEntry(final int status) {
157        if (status >= sStatusActionList.length) {
158            Log.e(TAG, "Unknown status " + status);
159            return ACTION_UNKNOWN;
160        }
161        return sStatusActionList[status][1];
162    }
163
164    private void disableDict() {
165        SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
166        CommonPreferences.disable(prefs, mWordlistId);
167        UpdateHandler.markAsUnused(mContext, mClientId, mWordlistId, mVersion, mStatus);
168        if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) {
169            setStatus(MetadataDbHelper.STATUS_AVAILABLE);
170        } else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) {
171            // Interface-wise, we should no longer be able to come here. However, this is still
172            // the right thing to do if we do come here.
173            setStatus(MetadataDbHelper.STATUS_DISABLED);
174        } else {
175            Log.e(TAG, "Unexpected state of the word list for disabling " + mStatus);
176        }
177    }
178    private void enableDict() {
179        SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
180        CommonPreferences.enable(prefs, mWordlistId);
181        // Explicit enabling by the user : allow downloading on metered data connection.
182        UpdateHandler.markAsUsed(mContext, mClientId, mWordlistId, mVersion, mStatus, true);
183        if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) {
184            setStatus(MetadataDbHelper.STATUS_DOWNLOADING);
185        } else if (MetadataDbHelper.STATUS_DISABLED == mStatus
186                || MetadataDbHelper.STATUS_DELETING == mStatus) {
187            // If the status is DELETING, it means Android Keyboard
188            // has not deleted the word list yet, so we can safely
189            // turn it to 'installed'. The status DISABLED is still supported internally to
190            // avoid breaking older installations and all but there should not be a way to
191            // disable a word list through the interface any more.
192            setStatus(MetadataDbHelper.STATUS_INSTALLED);
193        } else {
194            Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus);
195        }
196    }
197    private void deleteDict() {
198        SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext);
199        CommonPreferences.disable(prefs, mWordlistId);
200        setStatus(MetadataDbHelper.STATUS_DELETING);
201        UpdateHandler.markAsDeleting(mContext, mClientId, mWordlistId, mVersion, mStatus);
202    }
203
204    @Override
205    protected void onBindView(final View view) {
206        super.onBindView(view);
207        ((ViewGroup)view).setLayoutTransition(null);
208
209        final DictionaryDownloadProgressBar progressBar =
210                (DictionaryDownloadProgressBar)view.findViewById(R.id.dictionary_line_progress_bar);
211        final TextView status = (TextView)view.findViewById(android.R.id.summary);
212        progressBar.setIds(mClientId, mWordlistId);
213        progressBar.setMax(mFilesize);
214        final boolean showProgressBar = (MetadataDbHelper.STATUS_DOWNLOADING == mStatus);
215        status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE);
216        progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE);
217
218        final ButtonSwitcher buttonSwitcher =
219                (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
220        // We need to clear the state of the button switcher, because we reuse views; if we didn't
221        // reset it would animate from whatever its old state was.
222        buttonSwitcher.reset();
223        if (mInterfaceState.isOpen(mWordlistId)) {
224            // The button is open.
225            final int previousStatus = mInterfaceState.getStatus(mWordlistId);
226            buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(previousStatus));
227            if (previousStatus != mStatus) {
228                // We come here if the status has changed since last time. We need to animate
229                // the transition.
230                buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
231                mInterfaceState.setOpen(mWordlistId, mStatus);
232            }
233        } else {
234            // The button is closed.
235            buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
236        }
237        buttonSwitcher.setInternalOnClickListener(mActionButtonClickHandler);
238        view.setOnClickListener(mPreferenceClickHandler);
239    }
240
241    private class OnWordListPreferenceClick implements View.OnClickListener {
242        @Override
243        public void onClick(final View v) {
244            // Note : v is the preference view
245            final ViewParent parent = v.getParent();
246            // Just in case something changed in the framework, test for the concrete class
247            if (!(parent instanceof ListView)) return;
248            final ListView listView = (ListView)parent;
249            final int indexToOpen;
250            // Close all first, we'll open back any item that needs to be open.
251            final boolean wasOpen = mInterfaceState.isOpen(mWordlistId);
252            mInterfaceState.closeAll();
253            if (wasOpen) {
254                // This button being shown. Take note that we don't want to open any button in the
255                // loop below.
256                indexToOpen = -1;
257            } else {
258                // This button was not being shown. Open it, and remember the index of this
259                // child as the one to open in the following loop.
260                mInterfaceState.setOpen(mWordlistId, mStatus);
261                indexToOpen = listView.indexOfChild(v);
262            }
263            final int lastDisplayedIndex =
264                    listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
265            // The "lastDisplayedIndex" is actually displayed, hence the <=
266            for (int i = 0; i <= lastDisplayedIndex; ++i) {
267                final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i)
268                        .findViewById(R.id.wordlist_button_switcher);
269                if (i == indexToOpen) {
270                    buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus));
271                } else {
272                    buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON);
273                }
274            }
275        }
276    }
277
278    private class OnActionButtonClick implements View.OnClickListener {
279        @Override
280        public void onClick(final View v) {
281            switch (getActionIdFromStatusAndMenuEntry(mStatus)) {
282            case ACTION_ENABLE_DICT:
283                enableDict();
284                break;
285            case ACTION_DISABLE_DICT:
286                disableDict();
287                break;
288            case ACTION_DELETE_DICT:
289                deleteDict();
290                break;
291            default:
292                Log.e(TAG, "Unknown menu item pressed");
293            }
294        }
295    }
296}
297