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