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 @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 public boolean hasPriorityOver(final int otherPrefStatus) { 110 // Both of these should be one of MetadataDbHelper.STATUS_* 111 return mStatus > otherPrefStatus; 112 } 113 114 private String getSummary(final int status) { 115 switch (status) { 116 // If we are deleting the word list, for the user it's like it's already deleted. 117 // It should be reinstallable. Exposing to the user the whole complexity of 118 // the delayed deletion process between the dictionary pack and Android Keyboard 119 // would only be confusing. 120 case MetadataDbHelper.STATUS_DELETING: 121 case MetadataDbHelper.STATUS_AVAILABLE: 122 return mContext.getString(R.string.dictionary_available); 123 case MetadataDbHelper.STATUS_DOWNLOADING: 124 return mContext.getString(R.string.dictionary_downloading); 125 case MetadataDbHelper.STATUS_INSTALLED: 126 return mContext.getString(R.string.dictionary_installed); 127 case MetadataDbHelper.STATUS_DISABLED: 128 return mContext.getString(R.string.dictionary_disabled); 129 default: 130 return NO_STATUS_MESSAGE; 131 } 132 } 133 134 // The table below needs to be kept in sync with MetadataDbHelper.STATUS_* since it uses 135 // the values as indices. 136 private static final int sStatusActionList[][] = { 137 // MetadataDbHelper.STATUS_UNKNOWN 138 {}, 139 // MetadataDbHelper.STATUS_AVAILABLE 140 { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT }, 141 // MetadataDbHelper.STATUS_DOWNLOADING 142 { ButtonSwitcher.STATUS_CANCEL, ACTION_DISABLE_DICT }, 143 // MetadataDbHelper.STATUS_INSTALLED 144 { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, 145 // MetadataDbHelper.STATUS_DISABLED 146 { ButtonSwitcher.STATUS_DELETE, ACTION_DELETE_DICT }, 147 // MetadataDbHelper.STATUS_DELETING 148 // We show 'install' because the file is supposed to be deleted. 149 // The user may reinstall it. 150 { ButtonSwitcher.STATUS_INSTALL, ACTION_ENABLE_DICT } 151 }; 152 153 private int getButtonSwitcherStatus(final int status) { 154 if (status >= sStatusActionList.length) { 155 Log.e(TAG, "Unknown status " + status); 156 return ButtonSwitcher.STATUS_NO_BUTTON; 157 } 158 return sStatusActionList[status][0]; 159 } 160 161 private static int getActionIdFromStatusAndMenuEntry(final int status) { 162 if (status >= sStatusActionList.length) { 163 Log.e(TAG, "Unknown status " + status); 164 return ACTION_UNKNOWN; 165 } 166 return sStatusActionList[status][1]; 167 } 168 169 private void disableDict() { 170 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 171 CommonPreferences.disable(prefs, mWordlistId); 172 UpdateHandler.markAsUnused(mContext, 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 private void enableDict() { 184 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 185 CommonPreferences.enable(prefs, mWordlistId); 186 // Explicit enabling by the user : allow downloading on metered data connection. 187 UpdateHandler.markAsUsed(mContext, mClientId, mWordlistId, mVersion, mStatus, true); 188 if (MetadataDbHelper.STATUS_AVAILABLE == mStatus) { 189 setStatus(MetadataDbHelper.STATUS_DOWNLOADING); 190 } else if (MetadataDbHelper.STATUS_DISABLED == mStatus 191 || MetadataDbHelper.STATUS_DELETING == mStatus) { 192 // If the status is DELETING, it means Android Keyboard 193 // has not deleted the word list yet, so we can safely 194 // turn it to 'installed'. The status DISABLED is still supported internally to 195 // avoid breaking older installations and all but there should not be a way to 196 // disable a word list through the interface any more. 197 setStatus(MetadataDbHelper.STATUS_INSTALLED); 198 } else { 199 Log.e(TAG, "Unexpected state of the word list for enabling " + mStatus); 200 } 201 } 202 private void deleteDict() { 203 SharedPreferences prefs = CommonPreferences.getCommonPreferences(mContext); 204 CommonPreferences.disable(prefs, mWordlistId); 205 setStatus(MetadataDbHelper.STATUS_DELETING); 206 UpdateHandler.markAsDeleting(mContext, mClientId, mWordlistId, mVersion, mStatus); 207 } 208 209 @Override 210 protected void onBindView(final View view) { 211 super.onBindView(view); 212 ((ViewGroup)view).setLayoutTransition(null); 213 214 final DictionaryDownloadProgressBar progressBar = 215 (DictionaryDownloadProgressBar)view.findViewById(R.id.dictionary_line_progress_bar); 216 final TextView status = (TextView)view.findViewById(android.R.id.summary); 217 progressBar.setIds(mClientId, mWordlistId); 218 progressBar.setMax(mFilesize); 219 final boolean showProgressBar = (MetadataDbHelper.STATUS_DOWNLOADING == mStatus); 220 status.setVisibility(showProgressBar ? View.INVISIBLE : View.VISIBLE); 221 progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.INVISIBLE); 222 223 final ButtonSwitcher buttonSwitcher = 224 (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher); 225 // We need to clear the state of the button switcher, because we reuse views; if we didn't 226 // reset it would animate from whatever its old state was. 227 buttonSwitcher.reset(mInterfaceState); 228 if (mInterfaceState.isOpen(mWordlistId)) { 229 // The button is open. 230 final int previousStatus = mInterfaceState.getStatus(mWordlistId); 231 buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(previousStatus)); 232 if (previousStatus != mStatus) { 233 // We come here if the status has changed since last time. We need to animate 234 // the transition. 235 buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus)); 236 mInterfaceState.setOpen(mWordlistId, mStatus); 237 } 238 } else { 239 // The button is closed. 240 buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON); 241 } 242 buttonSwitcher.setInternalOnClickListener(mActionButtonClickHandler); 243 view.setOnClickListener(mPreferenceClickHandler); 244 } 245 246 private class OnWordListPreferenceClick implements View.OnClickListener { 247 @Override 248 public void onClick(final View v) { 249 // Note : v is the preference view 250 final ViewParent parent = v.getParent(); 251 // Just in case something changed in the framework, test for the concrete class 252 if (!(parent instanceof ListView)) return; 253 final ListView listView = (ListView)parent; 254 final int indexToOpen; 255 // Close all first, we'll open back any item that needs to be open. 256 final boolean wasOpen = mInterfaceState.isOpen(mWordlistId); 257 mInterfaceState.closeAll(); 258 if (wasOpen) { 259 // This button being shown. Take note that we don't want to open any button in the 260 // loop below. 261 indexToOpen = -1; 262 } else { 263 // This button was not being shown. Open it, and remember the index of this 264 // child as the one to open in the following loop. 265 mInterfaceState.setOpen(mWordlistId, mStatus); 266 indexToOpen = listView.indexOfChild(v); 267 } 268 final int lastDisplayedIndex = 269 listView.getLastVisiblePosition() - listView.getFirstVisiblePosition(); 270 // The "lastDisplayedIndex" is actually displayed, hence the <= 271 for (int i = 0; i <= lastDisplayedIndex; ++i) { 272 final ButtonSwitcher buttonSwitcher = (ButtonSwitcher)listView.getChildAt(i) 273 .findViewById(R.id.wordlist_button_switcher); 274 if (i == indexToOpen) { 275 buttonSwitcher.setStatusAndUpdateVisuals(getButtonSwitcherStatus(mStatus)); 276 } else { 277 buttonSwitcher.setStatusAndUpdateVisuals(ButtonSwitcher.STATUS_NO_BUTTON); 278 } 279 } 280 } 281 } 282 283 private class OnActionButtonClick implements View.OnClickListener { 284 @Override 285 public void onClick(final View v) { 286 switch (getActionIdFromStatusAndMenuEntry(mStatus)) { 287 case ACTION_ENABLE_DICT: 288 enableDict(); 289 break; 290 case ACTION_DISABLE_DICT: 291 disableDict(); 292 break; 293 case ACTION_DELETE_DICT: 294 deleteDict(); 295 break; 296 default: 297 Log.e(TAG, "Unknown menu item pressed"); 298 } 299 } 300 } 301 } 302