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