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 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 private 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 public void setStatus(final int status) { 96 if (status == mStatus) return; 97 mStatus = status; 98 setSummary(getSummary(status)); 99 } 100 101 public boolean hasStatus(final int status) { 102 return status == mStatus; 103 } 104 105 @Override 106 public View onCreateView(final ViewGroup parent) { 107 final View orphanedView = mInterfaceState.findFirstOrphanedView(); 108 if (null != orphanedView) return orphanedView; // Will be sent to onBindView 109 final View newView = super.onCreateView(parent); 110 return mInterfaceState.addToCacheAndReturnView(newView); 111 } 112 113 public boolean hasPriorityOver(final int otherPrefStatus) { 114 // Both of these should be one of MetadataDbHelper.STATUS_* 115 return mStatus > otherPrefStatus; 116 } 117 118 private String getSummary(final int status) { 119 switch (status) { 120 // If we are deleting the word list, for the user it's like it's already deleted. 121 // It should be reinstallable. Exposing to the user the whole complexity of 122 // the delayed deletion process between the dictionary pack and Android Keyboard 123 // would only be confusing. 124 case MetadataDbHelper.STATUS_DELETING: 125 case MetadataDbHelper.STATUS_AVAILABLE: 126 return mContext.getString(R.string.dictionary_available); 127 case MetadataDbHelper.STATUS_DOWNLOADING: 128 return mContext.getString(R.string.dictionary_downloading); 129 case MetadataDbHelper.STATUS_INSTALLED: 130 return mContext.getString(R.string.dictionary_installed); 131 case MetadataDbHelper.STATUS_DISABLED: 132 return mContext.getString(R.string.dictionary_disabled); 133 default: 134 return NO_STATUS_MESSAGE; 135 } 136 } 137 138 // The table below needs to be kept in sync with MetadataDbHelper.STATUS_* since it uses 139 // the values as indices. 140 private static final int sStatusActionList[][] = { 141 // MetadataDbHelper.STATUS_UNKNOWN 142 {}, 143 // MetadataDbHelper.STATUS_AVAILABLE 144 { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }, 145 // MetadataDbHelper.STATUS_DOWNLOADING 146 { ButtonSwitcher.STATUS_CANCEL, ACTION_DISABLE_DICT }, 147 // MetadataDbHelper.STATUS_INSTALLED 148 { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, 149 // MetadataDbHelper.STATUS_DISABLED 150 { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, 151 // MetadataDbHelper.STATUS_DELETING 152 // We show 'install' because the file is supposed to be deleted. 153 // The user may reinstall it. 154 { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT } 155 }; 156 157 private int getButtonSwitcherStatus(final int status) { 158 if (status >= sStatusActionList.length) { 159 Log.e(TAG, "Unknown status " + status); 160 return ButtonSwitcher.STATUS_NO_BUTTON; 161 } 162 return sStatusActionList[status][0]; 163 } 164 165 private static int getActionIdFromStatusAndMenuEntry(final int status) { 166 if (status >= sStatusActionList.length) { 167 Log.e(TAG, "Unknown status " + status); 168 return ACTION_UNKNOWN; 169 } 170 return sStatusActionList[status][1]; 171 } 172 173 private void disableDict() { 174 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 175 CommonPreferences.disable(prefs, mWordlistId); 176 UpdateHandler.markAsUnused(mContext, mClientId, mWordlistId, mVersion, mStatus); 177 if (MetadataDbHelper.STATUS_DOWNLOADING == mStatus) { 178 setStatus(MetadataDbHelper.STATUS_AVAILABLE); 179 } else if (MetadataDbHelper.STATUS_INSTALLED == mStatus) { 180 // Interface-wise, we should no longer be able to come here. However, this is still 181 // the right thing to do if we do come here. 182 setStatus(MetadataDbHelper.STATUS_DISABLED); 183 } else { 184 Log.e(TAG, "Unexpected state of the word list for disabling " + mStatus); 185 } 186 } 187 private void enableDict() { 188 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 189 CommonPreferences.enable(prefs, mWordlistId); 190 // Explicit enabling by the user : allow downloading on metered data connection. 191 UpdateHandler.markAsUsed(mContext, mClientId, mWordlistId, mVersion, mStatus, true); 192 if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) { 193 setStatus(MetadataDbHelper.STATUS_DOWNLOADING); 194 } else if (MetadataDbHelper.STATUS_DISABLED == mStatus 195 || MetadataDbHelper.STATUS_DELETING == mStatus) { 196 // If the status is DELETING, it means Android Keyboard 197 // has not deleted the word list yet, so we can safely 198 // turn it to 'installed'. The status DISABLED is still supported internally to 199 // avoid breaking older installations and all but there should not be a way to 200 // disable a word list through the interface any more. 201 setStatus(MetadataDbHelper.STATUS_INSTALLED); 202 } else { 203 Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus); 204 } 205 } 206 private void deleteDict() { 207 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 208 CommonPreferences.disable(prefs, mWordlistId); 209 setStatus(MetadataDbHelper.STATUS_DELETING); 210 UpdateHandler.markAsDeleting(mContext, mClientId, mWordlistId, mVersion, mStatus); 211 } 212 213 @Override 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 = 229 (ButtonSwitcher)view.findViewById(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(mActionButtonClickHandler); 248 view.setOnClickListener(mPreferenceClickHandler); 249 } 250 251 private class OnWordListPreferenceClick implements View.OnClickListener { 252 @Override 253 public void onClick(final View v) { 254 // Note : v is the preference view 255 final ViewParent parent = v.getParent(); 256 // Just in case something changed in the framework, test for the concrete class 257 if (!(parent instanceof ListView)) return; 258 final ListView listView = (ListView)parent; 259 final int indexToOpen; 260 // Close all first, we'll open back any item that needs to be open. 261 final boolean wasOpen = mInterfaceState.isOpen(mWordlistId); 262 mInterfaceState.closeAll(); 263 if (wasOpen) { 264 // This button being shown. Take note that we don't want to open any button in the 265 // loop below. 266 indexToOpen = -1; 267 } else { 268 // This button was not being shown. Open it, and remember the index of this 269 // child as the one to open in the following loop. 270 mInterfaceState.setOpen(mWordlistId, mStatus); 271 indexToOpen = listView.indexOfChild(v); 272 } 273 final int lastDisplayedIndex = 274 listView.getLastVisiblePosition() - listView.getFirstVisiblePosition(); 275 // The "lastDisplayedIndex" is actually displayed, hence the <= 276 for (int i = 0; i <= lastDisplayedIndex; ++i) { 277 final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i) 278 .findViewById(R.id.wordlist_button_switcher); 279 if (i == indexToOpen) { 280 buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus)); 281 } else { 282 buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON); 283 } 284 } 285 } 286 } 287 288 private class OnActionButtonClick implements View.OnClickListener { 289 @Override 290 public void onClick(final View v) { 291 switch (getActionIdFromStatusAndMenuEntry(mStatus)) { 292 case ACTION_ENABLE_DICT: 293 enableDict(); 294 break; 295 case ACTION_DISABLE_DICT: 296 disableDict(); 297 break; 298 case ACTION_DELETE_DICT: 299 deleteDict(); 300 break; 301 default: 302 Log.e(TAG, "Unknown menu item pressed"); 303 } 304 } 305 } 306 } 307