• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.AlertDialog;
22 import android.app.AppGlobals;
23 import android.app.Dialog;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnClickListener;
28 import android.content.Intent;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.ResolveInfo;
32 import android.graphics.drawable.Icon;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.graphics.drawable.Drawable;
36 import android.hardware.input.InputManager;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.os.RemoteException;
40 import android.util.Log;
41 import android.util.SparseArray;
42 import android.view.ContextThemeWrapper;
43 import android.view.InputDevice;
44 import android.view.KeyCharacterMap;
45 import android.view.KeyEvent;
46 import android.view.KeyboardShortcutGroup;
47 import android.view.KeyboardShortcutInfo;
48 import android.view.LayoutInflater;
49 import android.view.View;
50 import android.view.View.AccessibilityDelegate;
51 import android.view.ViewGroup;
52 import android.view.Window;
53 import android.view.WindowManager.KeyboardShortcutsReceiver;
54 import android.view.accessibility.AccessibilityNodeInfo;
55 import android.widget.ImageView;
56 import android.widget.LinearLayout;
57 import android.widget.RelativeLayout;
58 import android.widget.TextView;
59 
60 import com.android.internal.app.AssistUtils;
61 import com.android.settingslib.Utils;
62 import com.android.systemui.R;
63 import com.android.systemui.recents.Recents;
64 
65 import java.util.ArrayList;
66 import java.util.Collections;
67 import java.util.Comparator;
68 import java.util.List;
69 
70 import static android.content.Context.LAYOUT_INFLATER_SERVICE;
71 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
72 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
73 
74 /**
75  * Contains functionality for handling keyboard shortcuts.
76  */
77 public final class KeyboardShortcuts {
78     private static final String TAG = KeyboardShortcuts.class.getSimpleName();
79     private static final Object sLock = new Object();
80     private static KeyboardShortcuts sInstance;
81 
82     private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
83     private final SparseArray<String> mModifierNames = new SparseArray<>();
84     private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
85     private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
86 
87     private final Handler mHandler = new Handler(Looper.getMainLooper());
88     private final Context mContext;
89     private final IPackageManager mPackageManager;
90     private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
91         public void onClick(DialogInterface dialog, int id) {
92             dismissKeyboardShortcuts();
93         }
94     };
95     private final Comparator<KeyboardShortcutInfo> mApplicationItemsComparator =
96             new Comparator<KeyboardShortcutInfo>() {
97                 @Override
98                 public int compare(KeyboardShortcutInfo ksh1, KeyboardShortcutInfo ksh2) {
99                     boolean ksh1ShouldBeLast = ksh1.getLabel() == null
100                             || ksh1.getLabel().toString().isEmpty();
101                     boolean ksh2ShouldBeLast = ksh2.getLabel() == null
102                             || ksh2.getLabel().toString().isEmpty();
103                     if (ksh1ShouldBeLast && ksh2ShouldBeLast) {
104                         return 0;
105                     }
106                     if (ksh1ShouldBeLast) {
107                         return 1;
108                     }
109                     if (ksh2ShouldBeLast) {
110                         return -1;
111                     }
112                     return (ksh1.getLabel().toString()).compareToIgnoreCase(
113                             ksh2.getLabel().toString());
114                 }
115             };
116 
117     private Dialog mKeyboardShortcutsDialog;
118     private KeyCharacterMap mKeyCharacterMap;
119 
KeyboardShortcuts(Context context)120     private KeyboardShortcuts(Context context) {
121         this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light);
122         this.mPackageManager = AppGlobals.getPackageManager();
123         loadResources(context);
124     }
125 
getInstance(Context context)126     private static KeyboardShortcuts getInstance(Context context) {
127         if (sInstance == null) {
128             sInstance = new KeyboardShortcuts(context);
129         }
130         return sInstance;
131     }
132 
show(Context context, int deviceId)133     public static void show(Context context, int deviceId) {
134         synchronized (sLock) {
135             if (sInstance != null && !sInstance.mContext.equals(context)) {
136                 dismiss();
137             }
138             getInstance(context).showKeyboardShortcuts(deviceId);
139         }
140     }
141 
toggle(Context context, int deviceId)142     public static void toggle(Context context, int deviceId) {
143         synchronized (sLock) {
144             if (isShowing()) {
145                 dismiss();
146             } else {
147                 show(context, deviceId);
148             }
149         }
150     }
151 
dismiss()152     public static void dismiss() {
153         synchronized (sLock) {
154             if (sInstance != null) {
155                 sInstance.dismissKeyboardShortcuts();
156                 sInstance = null;
157             }
158         }
159     }
160 
isShowing()161     private static boolean isShowing() {
162         return sInstance != null && sInstance.mKeyboardShortcutsDialog != null
163                 && sInstance.mKeyboardShortcutsDialog.isShowing();
164     }
165 
loadResources(Context context)166     private void loadResources(Context context) {
167         mSpecialCharacterNames.put(
168                 KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
169         mSpecialCharacterNames.put(
170                 KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
171         mSpecialCharacterNames.put(
172                 KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
173         mSpecialCharacterNames.put(
174                 KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
175         mSpecialCharacterNames.put(
176                 KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
177         mSpecialCharacterNames.put(
178                 KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
179         mSpecialCharacterNames.put(
180                 KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
181         mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
182         mSpecialCharacterNames.put(
183                 KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
184         mSpecialCharacterNames.put(
185                 KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
186         mSpecialCharacterNames.put(
187                 KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
188         mSpecialCharacterNames.put(
189                 KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
190         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
191                 context.getString(R.string.keyboard_key_media_play_pause));
192         mSpecialCharacterNames.put(
193                 KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
194         mSpecialCharacterNames.put(
195                 KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
196         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
197                 context.getString(R.string.keyboard_key_media_previous));
198         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
199                 context.getString(R.string.keyboard_key_media_rewind));
200         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
201                 context.getString(R.string.keyboard_key_media_fast_forward));
202         mSpecialCharacterNames.put(
203                 KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
204         mSpecialCharacterNames.put(
205                 KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
206         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
207                 context.getString(R.string.keyboard_key_button_template, "A"));
208         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
209                 context.getString(R.string.keyboard_key_button_template, "B"));
210         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
211                 context.getString(R.string.keyboard_key_button_template, "C"));
212         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
213                 context.getString(R.string.keyboard_key_button_template, "X"));
214         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
215                 context.getString(R.string.keyboard_key_button_template, "Y"));
216         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
217                 context.getString(R.string.keyboard_key_button_template, "Z"));
218         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
219                 context.getString(R.string.keyboard_key_button_template, "L1"));
220         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
221                 context.getString(R.string.keyboard_key_button_template, "R1"));
222         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
223                 context.getString(R.string.keyboard_key_button_template, "L2"));
224         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
225                 context.getString(R.string.keyboard_key_button_template, "R2"));
226         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
227                 context.getString(R.string.keyboard_key_button_template, "Start"));
228         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
229                 context.getString(R.string.keyboard_key_button_template, "Select"));
230         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
231                 context.getString(R.string.keyboard_key_button_template, "Mode"));
232         mSpecialCharacterNames.put(
233                 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
234         mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
235         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
236         mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
237         mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
238         mSpecialCharacterNames.put(
239                 KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
240         mSpecialCharacterNames.put(
241                 KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
242         mSpecialCharacterNames.put(
243                 KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
244         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
245         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
246         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
247         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
248         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
249         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
250         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
251         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
252         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
253         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
254         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
255         mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
256         mSpecialCharacterNames.put(
257                 KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
258         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
259                 context.getString(R.string.keyboard_key_numpad_template, "0"));
260         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
261                 context.getString(R.string.keyboard_key_numpad_template, "1"));
262         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
263                 context.getString(R.string.keyboard_key_numpad_template, "2"));
264         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
265                 context.getString(R.string.keyboard_key_numpad_template, "3"));
266         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
267                 context.getString(R.string.keyboard_key_numpad_template, "4"));
268         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
269                 context.getString(R.string.keyboard_key_numpad_template, "5"));
270         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
271                 context.getString(R.string.keyboard_key_numpad_template, "6"));
272         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
273                 context.getString(R.string.keyboard_key_numpad_template, "7"));
274         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
275                 context.getString(R.string.keyboard_key_numpad_template, "8"));
276         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
277                 context.getString(R.string.keyboard_key_numpad_template, "9"));
278         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
279                 context.getString(R.string.keyboard_key_numpad_template, "/"));
280         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
281                 context.getString(R.string.keyboard_key_numpad_template, "*"));
282         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
283                 context.getString(R.string.keyboard_key_numpad_template, "-"));
284         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
285                 context.getString(R.string.keyboard_key_numpad_template, "+"));
286         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
287                 context.getString(R.string.keyboard_key_numpad_template, "."));
288         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
289                 context.getString(R.string.keyboard_key_numpad_template, ","));
290         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
291                 context.getString(R.string.keyboard_key_numpad_template,
292                         context.getString(R.string.keyboard_key_enter)));
293         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
294                 context.getString(R.string.keyboard_key_numpad_template, "="));
295         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
296                 context.getString(R.string.keyboard_key_numpad_template, "("));
297         mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
298                 context.getString(R.string.keyboard_key_numpad_template, ")"));
299         mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
300         mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
301         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
302         mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
303         mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
304 
305         mModifierNames.put(KeyEvent.META_META_ON, "Meta");
306         mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
307         mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
308         mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
309         mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
310         mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
311 
312         mSpecialCharacterDrawables.put(
313                 KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
314         mSpecialCharacterDrawables.put(
315                 KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
316         mSpecialCharacterDrawables.put(
317                 KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
318         mSpecialCharacterDrawables.put(
319                 KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
320         mSpecialCharacterDrawables.put(
321                 KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
322         mSpecialCharacterDrawables.put(
323                 KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
324 
325         mModifierDrawables.put(
326                 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
327     }
328 
329     /**
330      * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
331      * existing device, that device's map is used. Otherwise, it checks first all available devices
332      * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
333      * Keyboard with its default map.
334      */
retrieveKeyCharacterMap(int deviceId)335     private void retrieveKeyCharacterMap(int deviceId) {
336         final InputManager inputManager = InputManager.getInstance();
337         if (deviceId != -1) {
338             final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
339             if (inputDevice != null) {
340                 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
341                 return;
342             }
343         }
344         final int[] deviceIds = inputManager.getInputDeviceIds();
345         for (int i = 0; i < deviceIds.length; ++i) {
346             final InputDevice inputDevice = inputManager.getInputDevice(deviceIds[i]);
347             // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
348             // resort.
349             if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
350                 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
351                 return;
352             }
353         }
354         final InputDevice inputDevice = inputManager.getInputDevice(-1);
355         mKeyCharacterMap = inputDevice.getKeyCharacterMap();
356     }
357 
showKeyboardShortcuts(int deviceId)358     private void showKeyboardShortcuts(int deviceId) {
359         retrieveKeyCharacterMap(deviceId);
360         Recents.getSystemServices().requestKeyboardShortcuts(mContext,
361                 new KeyboardShortcutsReceiver() {
362                     @Override
363                     public void onKeyboardShortcutsReceived(
364                             final List<KeyboardShortcutGroup> result) {
365                         result.add(getSystemShortcuts());
366                         final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
367                         if (appShortcuts != null) {
368                             result.add(appShortcuts);
369                         }
370                         showKeyboardShortcutsDialog(result);
371                     }
372                 }, deviceId);
373     }
374 
dismissKeyboardShortcuts()375     private void dismissKeyboardShortcuts() {
376         if (mKeyboardShortcutsDialog != null) {
377             mKeyboardShortcutsDialog.dismiss();
378             mKeyboardShortcutsDialog = null;
379         }
380     }
381 
getSystemShortcuts()382     private KeyboardShortcutGroup getSystemShortcuts() {
383         final KeyboardShortcutGroup systemGroup = new KeyboardShortcutGroup(
384                 mContext.getString(R.string.keyboard_shortcut_group_system), true);
385         systemGroup.addItem(new KeyboardShortcutInfo(
386                 mContext.getString(R.string.keyboard_shortcut_group_system_home),
387                 KeyEvent.KEYCODE_ENTER,
388                 KeyEvent.META_META_ON));
389         systemGroup.addItem(new KeyboardShortcutInfo(
390                 mContext.getString(R.string.keyboard_shortcut_group_system_back),
391                 KeyEvent.KEYCODE_DEL,
392                 KeyEvent.META_META_ON));
393         systemGroup.addItem(new KeyboardShortcutInfo(
394                 mContext.getString(R.string.keyboard_shortcut_group_system_recents),
395                 KeyEvent.KEYCODE_TAB,
396                 KeyEvent.META_ALT_ON));
397         systemGroup.addItem(new KeyboardShortcutInfo(
398                 mContext.getString(
399                         R.string.keyboard_shortcut_group_system_notifications),
400                 KeyEvent.KEYCODE_N,
401                 KeyEvent.META_META_ON));
402         systemGroup.addItem(new KeyboardShortcutInfo(
403                 mContext.getString(
404                         R.string.keyboard_shortcut_group_system_shortcuts_helper),
405                 KeyEvent.KEYCODE_SLASH,
406                 KeyEvent.META_META_ON));
407         systemGroup.addItem(new KeyboardShortcutInfo(
408                 mContext.getString(
409                         R.string.keyboard_shortcut_group_system_switch_input),
410                 KeyEvent.KEYCODE_SPACE,
411                 KeyEvent.META_META_ON));
412         return systemGroup;
413     }
414 
getDefaultApplicationShortcuts()415     private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
416         final int userId = mContext.getUserId();
417         List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
418 
419         // Assist.
420         final AssistUtils assistUtils = new AssistUtils(mContext);
421         final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
422         PackageInfo assistPackageInfo = null;
423         try {
424             assistPackageInfo = mPackageManager.getPackageInfo(
425                     assistComponent.getPackageName(), 0, userId);
426         } catch (RemoteException e) {
427             Log.e(TAG, "PackageManagerService is dead");
428         }
429 
430         if (assistPackageInfo != null) {
431             final Icon assistIcon = Icon.createWithResource(
432                     assistPackageInfo.applicationInfo.packageName,
433                     assistPackageInfo.applicationInfo.icon);
434 
435             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
436                     mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
437                     assistIcon,
438                     KeyEvent.KEYCODE_UNKNOWN,
439                     KeyEvent.META_META_ON));
440         }
441 
442         // Browser.
443         final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
444         if (browserIcon != null) {
445             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
446                     mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
447                     browserIcon,
448                     KeyEvent.KEYCODE_B,
449                     KeyEvent.META_META_ON));
450         }
451 
452 
453         // Contacts.
454         final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
455         if (contactsIcon != null) {
456             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
457                     mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
458                     contactsIcon,
459                     KeyEvent.KEYCODE_C,
460                     KeyEvent.META_META_ON));
461         }
462 
463         // Email.
464         final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
465         if (emailIcon != null) {
466             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
467                     mContext.getString(R.string.keyboard_shortcut_group_applications_email),
468                     emailIcon,
469                     KeyEvent.KEYCODE_E,
470                     KeyEvent.META_META_ON));
471         }
472 
473         // Messaging.
474         final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
475         if (messagingIcon != null) {
476             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
477                     mContext.getString(R.string.keyboard_shortcut_group_applications_im),
478                     messagingIcon,
479                     KeyEvent.KEYCODE_T,
480                     KeyEvent.META_META_ON));
481         }
482 
483         // Music.
484         final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
485         if (musicIcon != null) {
486             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
487                     mContext.getString(R.string.keyboard_shortcut_group_applications_music),
488                     musicIcon,
489                     KeyEvent.KEYCODE_P,
490                     KeyEvent.META_META_ON));
491         }
492 
493         // Calendar.
494         final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
495         if (calendarIcon != null) {
496             keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
497                     mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
498                     calendarIcon,
499                     KeyEvent.KEYCODE_L,
500                     KeyEvent.META_META_ON));
501         }
502 
503         final int itemsSize = keyboardShortcutInfoAppItems.size();
504         if (itemsSize == 0) {
505             return null;
506         }
507 
508         // Sorts by label, case insensitive with nulls and/or empty labels last.
509         Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
510         return new KeyboardShortcutGroup(
511                 mContext.getString(R.string.keyboard_shortcut_group_applications),
512                 keyboardShortcutInfoAppItems,
513                 true);
514     }
515 
getIconForIntentCategory(String intentCategory, int userId)516     private Icon getIconForIntentCategory(String intentCategory, int userId) {
517         final Intent intent = new Intent(Intent.ACTION_MAIN);
518         intent.addCategory(intentCategory);
519 
520         final PackageInfo packageInfo = getPackageInfoForIntent(intent, userId);
521         if (packageInfo != null && packageInfo.applicationInfo.icon != 0) {
522             return Icon.createWithResource(
523                     packageInfo.applicationInfo.packageName,
524                     packageInfo.applicationInfo.icon);
525         }
526         return null;
527     }
528 
getPackageInfoForIntent(Intent intent, int userId)529     private PackageInfo getPackageInfoForIntent(Intent intent, int userId) {
530         try {
531             ResolveInfo handler;
532             handler = mPackageManager.resolveIntent(
533                     intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0, userId);
534             if (handler == null || handler.activityInfo == null) {
535                 return null;
536             }
537             return mPackageManager.getPackageInfo(handler.activityInfo.packageName, 0, userId);
538         } catch (RemoteException e) {
539             Log.e(TAG, "PackageManagerService is dead", e);
540             return null;
541         }
542     }
543 
showKeyboardShortcutsDialog( final List<KeyboardShortcutGroup> keyboardShortcutGroups)544     private void showKeyboardShortcutsDialog(
545             final List<KeyboardShortcutGroup> keyboardShortcutGroups) {
546         // Need to post on the main thread.
547         mHandler.post(new Runnable() {
548             @Override
549             public void run() {
550                 handleShowKeyboardShortcuts(keyboardShortcutGroups);
551             }
552         });
553     }
554 
handleShowKeyboardShortcuts(List<KeyboardShortcutGroup> keyboardShortcutGroups)555     private void handleShowKeyboardShortcuts(List<KeyboardShortcutGroup> keyboardShortcutGroups) {
556         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
557         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
558                 LAYOUT_INFLATER_SERVICE);
559         final View keyboardShortcutsView = inflater.inflate(
560                 R.layout.keyboard_shortcuts_view, null);
561         populateKeyboardShortcuts((LinearLayout) keyboardShortcutsView.findViewById(
562                 R.id.keyboard_shortcuts_container), keyboardShortcutGroups);
563         dialogBuilder.setView(keyboardShortcutsView);
564         dialogBuilder.setPositiveButton(R.string.quick_settings_done, mDialogCloseListener);
565         mKeyboardShortcutsDialog = dialogBuilder.create();
566         mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
567         Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
568         keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
569         mKeyboardShortcutsDialog.show();
570     }
571 
populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout, List<KeyboardShortcutGroup> keyboardShortcutGroups)572     private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
573             List<KeyboardShortcutGroup> keyboardShortcutGroups) {
574         LayoutInflater inflater = LayoutInflater.from(mContext);
575         final int keyboardShortcutGroupsSize = keyboardShortcutGroups.size();
576         TextView shortcutsKeyView = (TextView) inflater.inflate(
577                 R.layout.keyboard_shortcuts_key_view, null, false);
578         shortcutsKeyView.measure(
579                 View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
580         final int shortcutKeyTextItemMinWidth = shortcutsKeyView.getMeasuredHeight();
581         // Needed to be able to scale the image items to the same height as the text items.
582         final int shortcutKeyIconItemHeightWidth = shortcutsKeyView.getMeasuredHeight()
583                 - shortcutsKeyView.getPaddingTop()
584                 - shortcutsKeyView.getPaddingBottom();
585         for (int i = 0; i < keyboardShortcutGroupsSize; i++) {
586             KeyboardShortcutGroup group = keyboardShortcutGroups.get(i);
587             TextView categoryTitle = (TextView) inflater.inflate(
588                     R.layout.keyboard_shortcuts_category_title, keyboardShortcutsLayout, false);
589             categoryTitle.setText(group.getLabel());
590             categoryTitle.setTextColor(group.isSystemGroup()
591                     ? Utils.getColorAccent(mContext)
592                     : mContext.getColor(R.color.ksh_application_group_color));
593             keyboardShortcutsLayout.addView(categoryTitle);
594 
595             LinearLayout shortcutContainer = (LinearLayout) inflater.inflate(
596                     R.layout.keyboard_shortcuts_container, keyboardShortcutsLayout, false);
597             final int itemsSize = group.getItems().size();
598             for (int j = 0; j < itemsSize; j++) {
599                 KeyboardShortcutInfo info = group.getItems().get(j);
600                 List<StringDrawableContainer> shortcutKeys = getHumanReadableShortcutKeys(info);
601                 if (shortcutKeys == null) {
602                     // Ignore shortcuts we can't display keys for.
603                     Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
604                     continue;
605                 }
606                 View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
607                         shortcutContainer, false);
608 
609                 if (info.getIcon() != null) {
610                     ImageView shortcutIcon = (ImageView) shortcutView
611                             .findViewById(R.id.keyboard_shortcuts_icon);
612                     shortcutIcon.setImageIcon(info.getIcon());
613                     shortcutIcon.setVisibility(View.VISIBLE);
614                 }
615 
616                 TextView shortcutKeyword = (TextView) shortcutView
617                         .findViewById(R.id.keyboard_shortcuts_keyword);
618                 shortcutKeyword.setText(info.getLabel());
619                 if (info.getIcon() != null) {
620                     RelativeLayout.LayoutParams lp =
621                             (RelativeLayout.LayoutParams) shortcutKeyword.getLayoutParams();
622                     lp.removeRule(RelativeLayout.ALIGN_PARENT_START);
623                     shortcutKeyword.setLayoutParams(lp);
624                 }
625 
626                 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
627                         .findViewById(R.id.keyboard_shortcuts_item_container);
628                 final int shortcutKeysSize = shortcutKeys.size();
629                 for (int k = 0; k < shortcutKeysSize; k++) {
630                     StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k);
631                     if (shortcutRepresentation.mDrawable != null) {
632                         ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
633                                 R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
634                                 false);
635                         Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth,
636                                 shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888);
637                         Canvas canvas = new Canvas(bitmap);
638                         shortcutRepresentation.mDrawable.setBounds(0, 0, canvas.getWidth(),
639                                 canvas.getHeight());
640                         shortcutRepresentation.mDrawable.draw(canvas);
641                         shortcutKeyIconView.setImageBitmap(bitmap);
642                         shortcutKeyIconView.setImportantForAccessibility(
643                                 IMPORTANT_FOR_ACCESSIBILITY_YES);
644                         shortcutKeyIconView.setAccessibilityDelegate(
645                                 new ShortcutKeyAccessibilityDelegate(
646                                         shortcutRepresentation.mString));
647                         shortcutItemsContainer.addView(shortcutKeyIconView);
648                     } else if (shortcutRepresentation.mString != null) {
649                         TextView shortcutKeyTextView = (TextView) inflater.inflate(
650                                 R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer,
651                                 false);
652                         shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
653                         shortcutKeyTextView.setText(shortcutRepresentation.mString);
654                         shortcutKeyTextView.setAccessibilityDelegate(
655                                 new ShortcutKeyAccessibilityDelegate(
656                                         shortcutRepresentation.mString));
657                         shortcutItemsContainer.addView(shortcutKeyTextView);
658                     }
659                 }
660                 shortcutContainer.addView(shortcutView);
661             }
662             keyboardShortcutsLayout.addView(shortcutContainer);
663             if (i < keyboardShortcutGroupsSize - 1) {
664                 View separator = inflater.inflate(
665                         R.layout.keyboard_shortcuts_category_separator, keyboardShortcutsLayout,
666                         false);
667                 keyboardShortcutsLayout.addView(separator);
668             }
669         }
670     }
671 
getHumanReadableShortcutKeys(KeyboardShortcutInfo info)672     private List<StringDrawableContainer> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
673         List<StringDrawableContainer> shortcutKeys = getHumanReadableModifiers(info);
674         if (shortcutKeys == null) {
675             return null;
676         }
677         String shortcutKeyString = null;
678         Drawable shortcutKeyDrawable = null;
679         if (info.getBaseCharacter() > Character.MIN_VALUE) {
680             shortcutKeyString = String.valueOf(info.getBaseCharacter());
681         } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
682             shortcutKeyDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
683             shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
684         } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
685             shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
686         } else {
687             // Special case for shortcuts with no base key or keycode.
688             if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
689                 return shortcutKeys;
690             }
691             char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
692             if (displayLabel != 0) {
693                 shortcutKeyString = String.valueOf(displayLabel);
694             } else {
695                 return null;
696             }
697         }
698 
699         if (shortcutKeyString != null) {
700             shortcutKeys.add(new StringDrawableContainer(shortcutKeyString, shortcutKeyDrawable));
701         } else {
702             Log.w(TAG, "Keyboard Shortcut does not have a text representation, skipping.");
703         }
704 
705         return shortcutKeys;
706     }
707 
getHumanReadableModifiers(KeyboardShortcutInfo info)708     private List<StringDrawableContainer> getHumanReadableModifiers(KeyboardShortcutInfo info) {
709         final List<StringDrawableContainer> shortcutKeys = new ArrayList<>();
710         int modifiers = info.getModifiers();
711         if (modifiers == 0) {
712             return shortcutKeys;
713         }
714         for(int i = 0; i < mModifierNames.size(); ++i) {
715             final int supportedModifier = mModifierNames.keyAt(i);
716             if ((modifiers & supportedModifier) != 0) {
717                 shortcutKeys.add(new StringDrawableContainer(
718                         mModifierNames.get(supportedModifier),
719                         mModifierDrawables.get(supportedModifier)));
720                 modifiers &= ~supportedModifier;
721             }
722         }
723         if (modifiers != 0) {
724             // Remaining unsupported modifiers, don't show anything.
725             return null;
726         }
727         return shortcutKeys;
728     }
729 
730     private final class ShortcutKeyAccessibilityDelegate extends AccessibilityDelegate {
731         private String mContentDescription;
732 
ShortcutKeyAccessibilityDelegate(String contentDescription)733         ShortcutKeyAccessibilityDelegate(String contentDescription) {
734             mContentDescription = contentDescription;
735         }
736 
737         @Override
onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)738         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
739             super.onInitializeAccessibilityNodeInfo(host, info);
740             if (mContentDescription != null) {
741                 info.setContentDescription(mContentDescription.toLowerCase());
742             }
743         }
744     }
745 
746     private static final class StringDrawableContainer {
747         @NonNull
748         public String mString;
749         @Nullable
750         public Drawable mDrawable;
751 
StringDrawableContainer(String string, Drawable drawable)752         StringDrawableContainer(String string, Drawable drawable) {
753             mString = string;
754             mDrawable = drawable;
755         }
756     }
757 }
758