• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
compare(InputMethodInfo imi1, InputMethodInfo imi2)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 
InputMethodsPanel(Context context, AttributeSet attrs)102     public InputMethodsPanel(Context context, AttributeSet attrs) {
103         this(context, attrs, 0);
104     }
105 
InputMethodsPanel(Context context, AttributeSet attrs, int defStyle)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 
setHardKeyboardEnabledChangeListener( OnHardKeyboardEnabledChangeListener listener)116     public void setHardKeyboardEnabledChangeListener(
117             OnHardKeyboardEnabledChangeListener listener) {
118         mHardKeyboardEnabledChangeListener = listener;
119     }
120 
121     @Override
onDetachedFromWindow()122     protected void onDetachedFromWindow() {
123         super.onDetachedFromWindow();
124         if (mAttached) {
125             getContext().unregisterReceiver(mBroadcastReceiver);
126             mAttached = false;
127         }
128     }
129 
130     @Override
onAttachedToWindow()131     protected void onAttachedToWindow() {
132         super.onAttachedToWindow();
133         if (!mAttached) {
134             getContext().registerReceiver(mBroadcastReceiver, mIntentFilter);
135             mAttached = true;
136         }
137     }
138 
139     @Override
onFinishInflate()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
isInContentArea(int x, int y)152     public boolean isInContentArea(int x, int y) {
153         return false;
154     }
155 
156     @Override
onClick(View view)157     public void onClick(View view) {
158         if (view == mConfigureImeShortcut) {
159             showConfigureInputMethods();
160             closePanel(true);
161         }
162     }
163 
164     @Override
dispatchHoverEvent(MotionEvent event)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 
updateHardKeyboardEnabled()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 
openPanel()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 
closePanel(boolean closeKeyboard)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         updateHardKeyboardEnabled();
205     }
206 
startActivity(Intent intent)207     private void startActivity(Intent intent) {
208         mContext.startActivity(intent);
209     }
210 
showConfigureInputMethods()211     private void showConfigureInputMethods() {
212         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
213         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
214                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
215                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
216         startActivity(intent);
217     }
218 
createInputMethodItem( final InputMethodInfo imi, final InputMethodSubtype subtype)219     private View createInputMethodItem(
220             final InputMethodInfo imi, final InputMethodSubtype subtype) {
221         final CharSequence subtypeName;
222         if (subtype == null || subtype.overridesImplicitlyEnabledSubtype()) {
223             subtypeName = null;
224         } else {
225             subtypeName = getSubtypeName(imi, subtype);
226         }
227         final CharSequence imiName = getIMIName(imi);
228         final Drawable icon = getSubtypeIcon(imi, subtype);
229         final View view = View.inflate(mContext, R.layout.status_bar_input_methods_item, null);
230         final ImageView subtypeIcon = (ImageView)view.findViewById(R.id.item_icon);
231         final TextView itemTitle = (TextView)view.findViewById(R.id.item_title);
232         final TextView itemSubtitle = (TextView)view.findViewById(R.id.item_subtitle);
233         final ImageView settingsIcon = (ImageView)view.findViewById(R.id.item_settings_icon);
234         final View subtypeView = view.findViewById(R.id.item_subtype);
235         if (subtypeName == null) {
236             itemTitle.setText(imiName);
237             itemSubtitle.setVisibility(View.GONE);
238         } else {
239             itemTitle.setText(subtypeName);
240             itemSubtitle.setVisibility(View.VISIBLE);
241             itemSubtitle.setText(imiName);
242         }
243         subtypeIcon.setImageDrawable(icon);
244         subtypeIcon.setContentDescription(itemTitle.getText());
245         final String settingsActivity = imi.getSettingsActivity();
246         if (!TextUtils.isEmpty(settingsActivity)) {
247             settingsIcon.setOnClickListener(new View.OnClickListener() {
248                 @Override
249                 public void onClick(View arg0) {
250                     Intent intent = new Intent(Intent.ACTION_MAIN);
251                     intent.setClassName(imi.getPackageName(), settingsActivity);
252                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
253                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
254                             | Intent.FLAG_ACTIVITY_CLEAR_TOP);
255                     startActivity(intent);
256                     closePanel(true);
257                 }
258             });
259         } else {
260             // Do not show the settings icon if the IME does not have a settings preference
261             view.findViewById(R.id.item_vertical_separator).setVisibility(View.GONE);
262             settingsIcon.setVisibility(View.GONE);
263         }
264         mRadioViewAndImiMap.put(
265                 subtypeView, new Pair<InputMethodInfo, InputMethodSubtype> (imi, subtype));
266         subtypeView.setOnClickListener(new View.OnClickListener() {
267             @Override
268             public void onClick(View v) {
269                 Pair<InputMethodInfo, InputMethodSubtype> imiAndSubtype =
270                         updateRadioButtonsByView(v);
271                 closePanel(false);
272                 setInputMethodAndSubtype(imiAndSubtype.first, imiAndSubtype.second);
273             }
274         });
275         return view;
276     }
277 
updateUiElements()278     private void updateUiElements() {
279         updateHardKeyboardSection();
280 
281         // TODO: Reuse subtype views.
282         mInputMethodMenuList.removeAllViews();
283         mRadioViewAndImiMap.clear();
284         mPackageManager = mContext.getPackageManager();
285 
286         Map<InputMethodInfo, List<InputMethodSubtype>> enabledIMIs =
287                 getEnabledInputMethodAndSubtypeList();
288         Set<InputMethodInfo> cachedImiSet = enabledIMIs.keySet();
289         for (InputMethodInfo imi: cachedImiSet) {
290             List<InputMethodSubtype> subtypes = enabledIMIs.get(imi);
291             if (subtypes == null || subtypes.size() == 0) {
292                 mInputMethodMenuList.addView(
293                         createInputMethodItem(imi, null));
294                 continue;
295             }
296             for (InputMethodSubtype subtype: subtypes) {
297                 mInputMethodMenuList.addView(createInputMethodItem(imi, subtype));
298             }
299         }
300         updateRadioButtons();
301     }
302 
setImeToken(IBinder token)303     public void setImeToken(IBinder token) {
304         mToken = token;
305     }
306 
setImeSwitchButton(InputMethodButton imb)307     public void setImeSwitchButton(InputMethodButton imb) {
308         mInputMethodSwitchButton = imb;
309     }
310 
setInputMethodAndSubtype(InputMethodInfo imi, InputMethodSubtype subtype)311     private void setInputMethodAndSubtype(InputMethodInfo imi, InputMethodSubtype subtype) {
312         if (mToken != null) {
313             mImm.setInputMethodAndSubtype(mToken, imi.getId(), subtype);
314         } else {
315             Log.w(TAG, "IME Token is not set yet.");
316         }
317     }
318 
setHardKeyboardStatus(boolean available, boolean enabled)319     public void setHardKeyboardStatus(boolean available, boolean enabled) {
320         if (mHardKeyboardAvailable != available || mHardKeyboardEnabled != enabled) {
321             mHardKeyboardAvailable = available;
322             mHardKeyboardEnabled = enabled;
323             updateHardKeyboardSection();
324         }
325     }
326 
updateHardKeyboardSection()327     private void updateHardKeyboardSection() {
328         if (mHardKeyboardAvailable) {
329             mHardKeyboardSection.setVisibility(View.VISIBLE);
330             if (mHardKeyboardSwitch.isChecked() != mHardKeyboardEnabled) {
331                 mHardKeyboardSwitch.setChecked(mHardKeyboardEnabled);
332             }
333         } else {
334             mHardKeyboardSection.setVisibility(View.GONE);
335         }
336     }
337 
338     // Turn on the selected radio button when the user chooses the item
updateRadioButtonsByView(View selectedView)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 
updateRadioButtons()362     private void updateRadioButtons() {
363         updateRadioButtonsByImiAndSubtype(
364                 getCurrentInputMethodInfo(), mImm.getCurrentInputMethodSubtype());
365     }
366 
367     // Turn on the selected radio button at startup
updateRadioButtonsByImiAndSubtype( InputMethodInfo imi, InputMethodSubtype subtype)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>>
getEnabledInputMethodAndSubtypeList()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 
getCurrentInputMethodInfo()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 
getIMIName(InputMethodInfo imi)434     private CharSequence getIMIName(InputMethodInfo imi) {
435         if (imi == null) return null;
436         return imi.loadLabel(mPackageManager);
437     }
438 
getSubtypeName(InputMethodInfo imi, InputMethodSubtype subtype)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 
getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype)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 
onPackageChanged()476     private void onPackageChanged() {
477         if (DEBUG) {
478             Log.d(TAG, "onPackageChanged.");
479         }
480         mPackageChanged = true;
481     }
482 
483     public interface OnHardKeyboardEnabledChangeListener {
onHardKeyboardEnabledChange(boolean enabled)484         public void onHardKeyboardEnabledChange(boolean enabled);
485     }
486 
487 }
488