Home | History | Annotate | Download | only in tablet
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.statusbar.tablet;
     18 
     19 import com.android.systemui.R;
     20 
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.pm.PackageManager;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.IBinder;
     28 import android.provider.Settings;
     29 import android.text.TextUtils;
     30 import android.util.AttributeSet;
     31 import android.util.Log;
     32 import android.util.Pair;
     33 import android.view.MotionEvent;
     34 import android.view.View;
     35 import android.view.inputmethod.InputMethodInfo;
     36 import android.view.inputmethod.InputMethodManager;
     37 import android.view.inputmethod.InputMethodSubtype;
     38 import android.widget.ImageView;
     39 import android.widget.LinearLayout;
     40 import android.widget.RadioButton;
     41 import android.widget.Switch;
     42 import android.widget.TextView;
     43 
     44 import java.util.Comparator;
     45 import java.util.HashMap;
     46 import java.util.List;
     47 import java.util.Map;
     48 import java.util.Set;
     49 import java.util.TreeMap;
     50 
     51 public class InputMethodsPanel extends LinearLayout implements StatusBarPanel,
     52         View.OnClickListener {
     53     private static final boolean DEBUG = TabletStatusBar.DEBUG;
     54     private static final String TAG = "InputMethodsPanel";
     55 
     56     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     57         @Override
     58         public void onReceive(Context context, Intent intent) {
     59             onPackageChanged();
     60         }
     61     };
     62 
     63     private final InputMethodManager mImm;
     64     private final IntentFilter mIntentFilter = new IntentFilter();
     65     private final HashMap<View, Pair<InputMethodInfo, InputMethodSubtype>> mRadioViewAndImiMap =
     66             new HashMap<View, Pair<InputMethodInfo, InputMethodSubtype>>();
     67     private final TreeMap<InputMethodInfo, List<InputMethodSubtype>>
     68             mEnabledInputMethodAndSubtypesCache =
     69                     new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
     70                             new InputMethodComparator());
     71 
     72     private boolean mAttached = false;
     73     private boolean mPackageChanged = false;
     74     private Context mContext;
     75     private IBinder mToken;
     76     private InputMethodButton mInputMethodSwitchButton;
     77     private LinearLayout mInputMethodMenuList;
     78     private boolean mHardKeyboardAvailable;
     79     private boolean mHardKeyboardEnabled;
     80     private OnHardKeyboardEnabledChangeListener mHardKeyboardEnabledChangeListener;
     81     private LinearLayout mHardKeyboardSection;
     82     private Switch mHardKeyboardSwitch;
     83     private PackageManager mPackageManager;
     84     private String mEnabledInputMethodAndSubtypesCacheStr;
     85     private String mLastSystemLocaleString;
     86     private View mConfigureImeShortcut;
     87 
     88     private class InputMethodComparator implements Comparator<InputMethodInfo> {
     89         @Override
     90         public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
     91             if (imi2 == null) return 0;
     92             if (imi1 == null) return 1;
     93             if (mPackageManager == null) {
     94                 return imi1.getId().compareTo(imi2.getId());
     95             }
     96             CharSequence imiId1 = imi1.loadLabel(mPackageManager) + "/" + imi1.getId();
     97             CharSequence imiId2 = imi2.loadLabel(mPackageManager) + "/" + imi2.getId();
     98             return imiId1.toString().compareTo(imiId2.toString());
     99         }
    100     }
    101 
    102     public InputMethodsPanel(Context context, AttributeSet attrs) {
    103         this(context, attrs, 0);
    104     }
    105 
    106     public InputMethodsPanel(Context context, AttributeSet attrs, int defStyle) {
    107         super(context, attrs, defStyle);
    108         mContext = context;
    109         mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
    110         mIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
    111         mIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
    112         mIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    113         mIntentFilter.addDataScheme("package");
    114     }
    115 
    116     public void setHardKeyboardEnabledChangeListener(
    117             OnHardKeyboardEnabledChangeListener listener) {
    118         mHardKeyboardEnabledChangeListener = listener;
    119     }
    120 
    121     @Override
    122     protected void onDetachedFromWindow() {
    123         super.onDetachedFromWindow();
    124         if (mAttached) {
    125             getContext().unregisterReceiver(mBroadcastReceiver);
    126             mAttached = false;
    127         }
    128     }
    129 
    130     @Override
    131     protected void onAttachedToWindow() {
    132         super.onAttachedToWindow();
    133         if (!mAttached) {
    134             getContext().registerReceiver(mBroadcastReceiver, mIntentFilter);
    135             mAttached = true;
    136         }
    137     }
    138 
    139     @Override
    140     public void onFinishInflate() {
    141         mInputMethodMenuList = (LinearLayout) findViewById(R.id.input_method_menu_list);
    142         mHardKeyboardSection = (LinearLayout) findViewById(R.id.hard_keyboard_section);
    143         mHardKeyboardSwitch = (Switch) findViewById(R.id.hard_keyboard_switch);
    144         mConfigureImeShortcut = findViewById(R.id.ime_settings_shortcut);
    145         mConfigureImeShortcut.setOnClickListener(this);
    146         // TODO: If configurations for IME are not changed, do not update
    147         // by checking onConfigurationChanged.
    148         updateUiElements();
    149     }
    150 
    151     @Override
    152     public boolean isInContentArea(int x, int y) {
    153         return false;
    154     }
    155 
    156     @Override
    157     public void onClick(View view) {
    158         if (view == mConfigureImeShortcut) {
    159             showConfigureInputMethods();
    160             closePanel(true);
    161         }
    162     }
    163 
    164     @Override
    165     public boolean dispatchHoverEvent(MotionEvent event) {
    166         // Ignore hover events outside of this panel bounds since such events
    167         // generate spurious accessibility events with the panel content when
    168         // tapping outside of it, thus confusing the user.
    169         final int x = (int) event.getX();
    170         final int y = (int) event.getY();
    171         if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
    172             return super.dispatchHoverEvent(event);
    173         }
    174         return true;
    175     }
    176 
    177     private void updateHardKeyboardEnabled() {
    178         if (mHardKeyboardAvailable) {
    179             final boolean checked = mHardKeyboardSwitch.isChecked();
    180             if (mHardKeyboardEnabled != checked) {
    181                 mHardKeyboardEnabled = checked;
    182                 if (mHardKeyboardEnabledChangeListener != null)
    183                     mHardKeyboardEnabledChangeListener.onHardKeyboardEnabledChange(checked);
    184             }
    185         }
    186     }
    187 
    188     public void openPanel() {
    189         setVisibility(View.VISIBLE);
    190         updateUiElements();
    191         if (mInputMethodSwitchButton != null) {
    192             mInputMethodSwitchButton.setIconImage(R.drawable.ic_sysbar_ime_pressed);
    193         }
    194     }
    195 
    196     public void closePanel(boolean closeKeyboard) {
    197         setVisibility(View.GONE);
    198         if (mInputMethodSwitchButton != null) {
    199             mInputMethodSwitchButton.setIconImage(R.drawable.ic_sysbar_ime);
    200         }
    201         if (closeKeyboard) {
    202             mImm.hideSoftInputFromWindow(getWindowToken(), 0);
    203         }
    204     }
    205 
    206     private void startActivity(Intent intent) {
    207         mContext.startActivity(intent);
    208     }
    209 
    210     private void showConfigureInputMethods() {
    211         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
    212         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    213                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    214                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    215         startActivity(intent);
    216     }
    217 
    218     private View createInputMethodItem(
    219             final InputMethodInfo imi, final InputMethodSubtype subtype) {
    220         final CharSequence subtypeName;
    221         if (subtype == null || subtype.overridesImplicitlyEnabledSubtype()) {
    222             subtypeName = null;
    223         } else {
    224             subtypeName = getSubtypeName(imi, subtype);
    225         }
    226         final CharSequence imiName = getIMIName(imi);
    227         final Drawable icon = getSubtypeIcon(imi, subtype);
    228         final View view = View.inflate(mContext, R.layout.system_bar_input_methods_item, null);
    229         final ImageView subtypeIcon = (ImageView)view.findViewById(R.id.item_icon);
    230         final TextView itemTitle = (TextView)view.findViewById(R.id.item_title);
    231         final TextView itemSubtitle = (TextView)view.findViewById(R.id.item_subtitle);
    232         final ImageView settingsIcon = (ImageView)view.findViewById(R.id.item_settings_icon);
    233         final View subtypeView = view.findViewById(R.id.item_subtype);
    234         if (subtypeName == null) {
    235             itemTitle.setText(imiName);
    236             itemSubtitle.setVisibility(View.GONE);
    237         } else {
    238             itemTitle.setText(subtypeName);
    239             itemSubtitle.setVisibility(View.VISIBLE);
    240             itemSubtitle.setText(imiName);
    241         }
    242         subtypeIcon.setImageDrawable(icon);
    243         subtypeIcon.setContentDescription(itemTitle.getText());
    244         final String settingsActivity = imi.getSettingsActivity();
    245         if (!TextUtils.isEmpty(settingsActivity)) {
    246             settingsIcon.setOnClickListener(new View.OnClickListener() {
    247                 @Override
    248                 public void onClick(View arg0) {
    249                     Intent intent = new Intent(Intent.ACTION_MAIN);
    250                     intent.setClassName(imi.getPackageName(), settingsActivity);
    251                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    252                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    253                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    254                     startActivity(intent);
    255                     closePanel(true);
    256                 }
    257             });
    258         } else {
    259             // Do not show the settings icon if the IME does not have a settings preference
    260             view.findViewById(R.id.item_vertical_separator).setVisibility(View.GONE);
    261             settingsIcon.setVisibility(View.GONE);
    262         }
    263         mRadioViewAndImiMap.put(
    264                 subtypeView, new Pair<InputMethodInfo, InputMethodSubtype> (imi, subtype));
    265         subtypeView.setOnClickListener(new View.OnClickListener() {
    266             @Override
    267             public void onClick(View v) {
    268                 Pair<InputMethodInfo, InputMethodSubtype> imiAndSubtype =
    269                         updateRadioButtonsByView(v);
    270                 closePanel(false);
    271                 setInputMethodAndSubtype(imiAndSubtype.first, imiAndSubtype.second);
    272             }
    273         });
    274         return view;
    275     }
    276 
    277     private void updateUiElements() {
    278         updateHardKeyboardSection();
    279 
    280         // TODO: Reuse subtype views.
    281         mInputMethodMenuList.removeAllViews();
    282         mRadioViewAndImiMap.clear();
    283         mPackageManager = mContext.getPackageManager();
    284 
    285         Map<InputMethodInfo, List<InputMethodSubtype>> enabledIMIs =
    286                 getEnabledInputMethodAndSubtypeList();
    287         Set<InputMethodInfo> cachedImiSet = enabledIMIs.keySet();
    288         for (InputMethodInfo imi: cachedImiSet) {
    289             List<InputMethodSubtype> subtypes = enabledIMIs.get(imi);
    290             if (subtypes == null || subtypes.size() == 0) {
    291                 mInputMethodMenuList.addView(
    292                         createInputMethodItem(imi, null));
    293                 continue;
    294             }
    295             for (InputMethodSubtype subtype: subtypes) {
    296                 mInputMethodMenuList.addView(createInputMethodItem(imi, subtype));
    297             }
    298         }
    299         updateRadioButtons();
    300     }
    301 
    302     public void setImeToken(IBinder token) {
    303         mToken = token;
    304     }
    305 
    306     public void setImeSwitchButton(InputMethodButton imb) {
    307         mInputMethodSwitchButton = imb;
    308     }
    309 
    310     private void setInputMethodAndSubtype(InputMethodInfo imi, InputMethodSubtype subtype) {
    311         if (mToken != null) {
    312             mImm.setInputMethodAndSubtype(mToken, imi.getId(), subtype);
    313         } else {
    314             Log.w(TAG, "IME Token is not set yet.");
    315         }
    316     }
    317 
    318     public void setHardKeyboardStatus(boolean available, boolean enabled) {
    319         if (mHardKeyboardAvailable != available || mHardKeyboardEnabled != enabled) {
    320             mHardKeyboardAvailable = available;
    321             mHardKeyboardEnabled = enabled;
    322             updateHardKeyboardSection();
    323         }
    324     }
    325 
    326     private void updateHardKeyboardSection() {
    327         if (mHardKeyboardAvailable) {
    328             mHardKeyboardSection.setVisibility(View.VISIBLE);
    329             if (mHardKeyboardSwitch.isChecked() != mHardKeyboardEnabled) {
    330                 mHardKeyboardSwitch.setChecked(mHardKeyboardEnabled);
    331                 updateHardKeyboardEnabled();
    332             }
    333         } else {
    334             mHardKeyboardSection.setVisibility(View.GONE);
    335         }
    336     }
    337 
    338     // Turn on the selected radio button when the user chooses the item
    339     private Pair<InputMethodInfo, InputMethodSubtype> updateRadioButtonsByView(View selectedView) {
    340         Pair<InputMethodInfo, InputMethodSubtype> selectedImiAndSubtype = null;
    341         if (mRadioViewAndImiMap.containsKey(selectedView)) {
    342             for (View radioView: mRadioViewAndImiMap.keySet()) {
    343                 RadioButton subtypeRadioButton =
    344                         (RadioButton) radioView.findViewById(R.id.item_radio);
    345                 if (subtypeRadioButton == null) {
    346                     Log.w(TAG, "RadioButton was not found in the selected subtype view");
    347                     return null;
    348                 }
    349                 if (radioView == selectedView) {
    350                     Pair<InputMethodInfo, InputMethodSubtype> imiAndSubtype =
    351                         mRadioViewAndImiMap.get(radioView);
    352                     selectedImiAndSubtype = imiAndSubtype;
    353                     subtypeRadioButton.setChecked(true);
    354                 } else {
    355                     subtypeRadioButton.setChecked(false);
    356                 }
    357             }
    358         }
    359         return selectedImiAndSubtype;
    360     }
    361 
    362     private void updateRadioButtons() {
    363         updateRadioButtonsByImiAndSubtype(
    364                 getCurrentInputMethodInfo(), mImm.getCurrentInputMethodSubtype());
    365     }
    366 
    367     // Turn on the selected radio button at startup
    368     private void updateRadioButtonsByImiAndSubtype(
    369             InputMethodInfo imi, InputMethodSubtype subtype) {
    370         if (imi == null) return;
    371         if (DEBUG) {
    372             Log.d(TAG, "Update radio buttons by " + imi.getId() + ", " + subtype);
    373         }
    374         for (View radioView: mRadioViewAndImiMap.keySet()) {
    375             RadioButton subtypeRadioButton =
    376                     (RadioButton) radioView.findViewById(R.id.item_radio);
    377             if (subtypeRadioButton == null) {
    378                 Log.w(TAG, "RadioButton was not found in the selected subtype view");
    379                 return;
    380             }
    381             Pair<InputMethodInfo, InputMethodSubtype> imiAndSubtype =
    382                     mRadioViewAndImiMap.get(radioView);
    383             if (imiAndSubtype.first.getId().equals(imi.getId())
    384                     && (imiAndSubtype.second == null || imiAndSubtype.second.equals(subtype))) {
    385                 subtypeRadioButton.setChecked(true);
    386             } else {
    387                 subtypeRadioButton.setChecked(false);
    388             }
    389         }
    390     }
    391 
    392     private TreeMap<InputMethodInfo, List<InputMethodSubtype>>
    393             getEnabledInputMethodAndSubtypeList() {
    394         String newEnabledIMIs = Settings.Secure.getString(
    395                 mContext.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
    396         String currentSystemLocaleString =
    397                 mContext.getResources().getConfiguration().locale.toString();
    398         if (!TextUtils.equals(mEnabledInputMethodAndSubtypesCacheStr, newEnabledIMIs)
    399                 || !TextUtils.equals(mLastSystemLocaleString, currentSystemLocaleString)
    400                 || mPackageChanged) {
    401             mEnabledInputMethodAndSubtypesCache.clear();
    402             final List<InputMethodInfo> imis = mImm.getEnabledInputMethodList();
    403             for (InputMethodInfo imi: imis) {
    404                 mEnabledInputMethodAndSubtypesCache.put(imi,
    405                         mImm.getEnabledInputMethodSubtypeList(imi, true));
    406             }
    407             mEnabledInputMethodAndSubtypesCacheStr = newEnabledIMIs;
    408             mPackageChanged = false;
    409             mLastSystemLocaleString = currentSystemLocaleString;
    410         }
    411         return mEnabledInputMethodAndSubtypesCache;
    412     }
    413 
    414     private InputMethodInfo getCurrentInputMethodInfo() {
    415         String curInputMethodId = Settings.Secure.getString(getContext()
    416                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
    417         Set<InputMethodInfo> cachedImiSet = mEnabledInputMethodAndSubtypesCache.keySet();
    418         // 1. Search IMI in cache
    419         for (InputMethodInfo imi: cachedImiSet) {
    420             if (imi.getId().equals(curInputMethodId)) {
    421                 return imi;
    422             }
    423         }
    424         // 2. Get current enabled IMEs and search IMI
    425         cachedImiSet = getEnabledInputMethodAndSubtypeList().keySet();
    426         for (InputMethodInfo imi: cachedImiSet) {
    427             if (imi.getId().equals(curInputMethodId)) {
    428                 return imi;
    429             }
    430         }
    431         return null;
    432     }
    433 
    434     private CharSequence getIMIName(InputMethodInfo imi) {
    435         if (imi == null) return null;
    436         return imi.loadLabel(mPackageManager);
    437     }
    438 
    439     private CharSequence getSubtypeName(InputMethodInfo imi, InputMethodSubtype subtype) {
    440         if (imi == null || subtype == null) return null;
    441         if (DEBUG) {
    442             Log.d(TAG, "Get text from: " + imi.getPackageName() + subtype.getNameResId()
    443                     + imi.getServiceInfo().applicationInfo);
    444         }
    445         return subtype.getDisplayName(
    446                 mContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo);
    447     }
    448 
    449     private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
    450         if (imi != null) {
    451             if (DEBUG) {
    452                 Log.d(TAG, "Update icons of IME: " + imi.getPackageName());
    453                 if (subtype != null) {
    454                     Log.d(TAG, "subtype =" + subtype.getLocale() + "," + subtype.getMode());
    455                 }
    456             }
    457             if (subtype != null) {
    458                 return mPackageManager.getDrawable(imi.getPackageName(), subtype.getIconResId(),
    459                         imi.getServiceInfo().applicationInfo);
    460             } else if (imi.getSubtypeCount() > 0) {
    461                 return mPackageManager.getDrawable(imi.getPackageName(),
    462                         imi.getSubtypeAt(0).getIconResId(),
    463                         imi.getServiceInfo().applicationInfo);
    464             } else {
    465                 try {
    466                     return mPackageManager.getApplicationInfo(
    467                             imi.getPackageName(), 0).loadIcon(mPackageManager);
    468                 } catch (PackageManager.NameNotFoundException e) {
    469                     Log.w(TAG, "IME can't be found: " + imi.getPackageName());
    470                 }
    471             }
    472         }
    473         return null;
    474     }
    475 
    476     private void onPackageChanged() {
    477         if (DEBUG) {
    478             Log.d(TAG, "onPackageChanged.");
    479         }
    480         mPackageChanged = true;
    481     }
    482 
    483     public interface OnHardKeyboardEnabledChangeListener {
    484         public void onHardKeyboardEnabledChange(boolean enabled);
    485     }
    486 
    487 }
    488