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