Home | History | Annotate | Download | only in statusbar
      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 
    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 
    126     private static KeyboardShortcuts getInstance(Context context) {
    127         if (sInstance == null) {
    128             sInstance = new KeyboardShortcuts(context);
    129         }
    130         return sInstance;
    131     }
    132 
    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 
    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 
    152     public static void dismiss() {
    153         synchronized (sLock) {
    154             if (sInstance != null) {
    155                 sInstance.dismissKeyboardShortcuts();
    156                 sInstance = null;
    157             }
    158         }
    159     }
    160 
    161     private static boolean isShowing() {
    162         return sInstance != null && sInstance.mKeyboardShortcutsDialog != null
    163                 && sInstance.mKeyboardShortcutsDialog.isShowing();
    164     }
    165 
    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      */
    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 
    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 
    375     private void dismissKeyboardShortcuts() {
    376         if (mKeyboardShortcutsDialog != null) {
    377             mKeyboardShortcutsDialog.dismiss();
    378             mKeyboardShortcutsDialog = null;
    379         }
    380     }
    381 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    733         ShortcutKeyAccessibilityDelegate(String contentDescription) {
    734             mContentDescription = contentDescription;
    735         }
    736 
    737         @Override
    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 
    752         StringDrawableContainer(String string, Drawable drawable) {
    753             mString = string;
    754             mDrawable = drawable;
    755         }
    756     }
    757 }
    758