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