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