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     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