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