Home | History | Annotate | Download | only in dictionarypack
      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