OSDN Git Service

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