blob: 3df12cd6453cdf0f688430896a68be84a3df95bd [file] [log] [blame]
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +01001/*
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;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010020import android.app.AppGlobals;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010021import android.app.Dialog;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010022import android.content.ComponentName;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010023import android.content.Context;
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000024import android.content.DialogInterface;
25import android.content.DialogInterface.OnClickListener;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010026import android.content.Intent;
27import android.content.pm.IPackageManager;
28import android.content.pm.PackageInfo;
29import android.content.pm.ResolveInfo;
30import android.graphics.drawable.Icon;
Andrei Stingaceanud1519102016-03-31 15:53:33 +010031import android.graphics.Bitmap;
32import android.graphics.Canvas;
33import android.graphics.drawable.Drawable;
Clara Bayarri4e850ff2016-03-02 11:12:32 -080034import android.hardware.input.InputManager;
Clara Bayarri75e09792015-07-29 16:20:40 +010035import android.os.Handler;
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000036import android.os.Looper;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010037import android.os.RemoteException;
Clara Bayarri4e850ff2016-03-02 11:12:32 -080038import android.util.Log;
39import android.util.SparseArray;
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +000040import android.view.ContextThemeWrapper;
Clara Bayarri4e850ff2016-03-02 11:12:32 -080041import android.view.InputDevice;
42import android.view.KeyCharacterMap;
Clara Bayarri75e09792015-07-29 16:20:40 +010043import android.view.KeyEvent;
44import android.view.KeyboardShortcutGroup;
45import android.view.KeyboardShortcutInfo;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010046import android.view.LayoutInflater;
47import android.view.View;
Andrei Stingaceanu844927d2016-02-16 14:31:58 +000048import android.view.ViewGroup;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010049import android.view.Window;
Clara Bayarri75e09792015-07-29 16:20:40 +010050import android.view.WindowManager.KeyboardShortcutsReceiver;
Andrei Stingaceanud1519102016-03-31 15:53:33 +010051import android.widget.ImageView;
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000052import android.widget.LinearLayout;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010053import android.widget.RelativeLayout;
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000054import android.widget.TextView;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010055
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010056import com.android.internal.app.AssistUtils;
Clara Bayarric17a5982016-04-15 12:26:47 +010057import com.android.internal.logging.MetricsLogger;
58import com.android.internal.logging.MetricsProto;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010059import com.android.systemui.R;
Clara Bayarri75e09792015-07-29 16:20:40 +010060import com.android.systemui.recents.Recents;
61
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000062import java.util.ArrayList;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010063import java.util.Collections;
64import java.util.Comparator;
Clara Bayarri75e09792015-07-29 16:20:40 +010065import java.util.List;
66
67import static android.content.Context.LAYOUT_INFLATER_SERVICE;
Clara Bayarri75e09792015-07-29 16:20:40 +010068import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010069
70/**
71 * Contains functionality for handling keyboard shortcuts.
72 */
Andrei Stingaceanud1519102016-03-31 15:53:33 +010073public final class KeyboardShortcuts {
Clara Bayarri4e850ff2016-03-02 11:12:32 -080074 private static final String TAG = KeyboardShortcuts.class.getSimpleName();
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +010075 private static final Object sLock = new Object();
76 private static KeyboardShortcuts sInstance;
77 private static boolean sIsShowing;
78
Clara Bayarrib999af52016-04-06 16:02:35 +010079 private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
80 private final SparseArray<String> mModifierNames = new SparseArray<>();
81 private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
82 private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000083
84 private final Handler mHandler = new Handler(Looper.getMainLooper());
85 private final Context mContext;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010086 private final IPackageManager mPackageManager;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010087 private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000088 public void onClick(DialogInterface dialog, int id) {
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +010089 dismissKeyboardShortcuts();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000090 }
91 };
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010092 private final Comparator<KeyboardShortcutInfo> mApplicationItemsComparator =
93 new Comparator<KeyboardShortcutInfo>() {
94 @Override
95 public int compare(KeyboardShortcutInfo ksh1, KeyboardShortcutInfo ksh2) {
96 boolean ksh1ShouldBeLast = ksh1.getLabel() == null
97 || ksh1.getLabel().toString().isEmpty();
98 boolean ksh2ShouldBeLast = ksh2.getLabel() == null
99 || ksh2.getLabel().toString().isEmpty();
100 if (ksh1ShouldBeLast && ksh2ShouldBeLast) {
101 return 0;
102 }
103 if (ksh1ShouldBeLast) {
104 return 1;
105 }
106 if (ksh2ShouldBeLast) {
107 return -1;
108 }
109 return (ksh1.getLabel().toString()).compareToIgnoreCase(
110 ksh2.getLabel().toString());
111 }
112 };
Clara Bayarri75e09792015-07-29 16:20:40 +0100113
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100114 private Dialog mKeyboardShortcutsDialog;
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800115 private KeyCharacterMap mKeyCharacterMap;
Clara Bayarri382c59e2016-05-18 12:19:17 +0100116 private KeyCharacterMap mBackupKeyCharacterMap;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100117
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100118 private KeyboardShortcuts(Context context) {
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000119 this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_Material_Light);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100120 this.mPackageManager = AppGlobals.getPackageManager();
Clara Bayarrib999af52016-04-06 16:02:35 +0100121 loadResources(context);
122 }
123
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100124 private static KeyboardShortcuts getInstance(Context context) {
125 if (sInstance == null) {
126 sInstance = new KeyboardShortcuts(context);
127 }
128 return sInstance;
129 }
130
131 public static void show(Context context, int deviceId) {
Clara Bayarric17a5982016-04-15 12:26:47 +0100132 MetricsLogger.visible(context,
133 MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100134 synchronized (sLock) {
135 if (sInstance != null && !sInstance.mContext.equals(context)) {
136 dismiss();
137 }
138 getInstance(context).showKeyboardShortcuts(deviceId);
139 sIsShowing = true;
140 }
141 }
142
143 public static void toggle(Context context, int deviceId) {
144 synchronized (sLock) {
145 if (sIsShowing) {
146 dismiss();
147 } else {
148 show(context, deviceId);
149 }
150 }
151 }
152
153 public static void dismiss() {
154 synchronized (sLock) {
155 if (sInstance != null) {
Clara Bayarri318ef062016-05-26 11:16:12 +0100156 MetricsLogger.hidden(sInstance.mContext,
157 MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100158 sInstance.dismissKeyboardShortcuts();
159 sInstance = null;
160 }
161 sIsShowing = false;
162 }
163 }
164
Clara Bayarrib999af52016-04-06 16:02:35 +0100165 private void loadResources(Context context) {
166 mSpecialCharacterNames.put(
167 KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
168 mSpecialCharacterNames.put(
169 KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
170 mSpecialCharacterNames.put(
171 KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
172 mSpecialCharacterNames.put(
173 KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
174 mSpecialCharacterNames.put(
175 KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
176 mSpecialCharacterNames.put(
177 KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
178 mSpecialCharacterNames.put(
179 KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
180 mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
181 mSpecialCharacterNames.put(
182 KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
183 mSpecialCharacterNames.put(
184 KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
185 mSpecialCharacterNames.put(
186 KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
187 mSpecialCharacterNames.put(
188 KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
189 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
190 context.getString(R.string.keyboard_key_media_play_pause));
191 mSpecialCharacterNames.put(
192 KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
193 mSpecialCharacterNames.put(
194 KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
195 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
196 context.getString(R.string.keyboard_key_media_previous));
197 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
198 context.getString(R.string.keyboard_key_media_rewind));
199 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
200 context.getString(R.string.keyboard_key_media_fast_forward));
201 mSpecialCharacterNames.put(
202 KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
203 mSpecialCharacterNames.put(
204 KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
205 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
206 context.getString(R.string.keyboard_key_button_template, "A"));
207 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
208 context.getString(R.string.keyboard_key_button_template, "B"));
209 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
210 context.getString(R.string.keyboard_key_button_template, "C"));
211 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
212 context.getString(R.string.keyboard_key_button_template, "X"));
213 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
214 context.getString(R.string.keyboard_key_button_template, "Y"));
215 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
216 context.getString(R.string.keyboard_key_button_template, "Z"));
217 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
218 context.getString(R.string.keyboard_key_button_template, "L1"));
219 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
220 context.getString(R.string.keyboard_key_button_template, "R1"));
221 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
222 context.getString(R.string.keyboard_key_button_template, "L2"));
223 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
224 context.getString(R.string.keyboard_key_button_template, "R2"));
225 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
226 context.getString(R.string.keyboard_key_button_template, "Start"));
227 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
228 context.getString(R.string.keyboard_key_button_template, "Select"));
229 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
230 context.getString(R.string.keyboard_key_button_template, "Mode"));
231 mSpecialCharacterNames.put(
232 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
233 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
234 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
235 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
236 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
237 mSpecialCharacterNames.put(
238 KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
239 mSpecialCharacterNames.put(
240 KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
241 mSpecialCharacterNames.put(
242 KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
243 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
244 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
245 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
246 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
247 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
248 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
249 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
250 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
251 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
252 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
253 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
254 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
255 mSpecialCharacterNames.put(
256 KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
257 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
258 context.getString(R.string.keyboard_key_numpad_template, "0"));
259 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
260 context.getString(R.string.keyboard_key_numpad_template, "1"));
261 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
262 context.getString(R.string.keyboard_key_numpad_template, "2"));
263 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
264 context.getString(R.string.keyboard_key_numpad_template, "3"));
265 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
266 context.getString(R.string.keyboard_key_numpad_template, "4"));
267 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
268 context.getString(R.string.keyboard_key_numpad_template, "5"));
269 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
270 context.getString(R.string.keyboard_key_numpad_template, "6"));
271 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
272 context.getString(R.string.keyboard_key_numpad_template, "7"));
273 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
274 context.getString(R.string.keyboard_key_numpad_template, "8"));
275 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
276 context.getString(R.string.keyboard_key_numpad_template, "9"));
277 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
278 context.getString(R.string.keyboard_key_numpad_template, "/"));
279 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
280 context.getString(R.string.keyboard_key_numpad_template, "*"));
281 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
282 context.getString(R.string.keyboard_key_numpad_template, "-"));
283 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
284 context.getString(R.string.keyboard_key_numpad_template, "+"));
285 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
286 context.getString(R.string.keyboard_key_numpad_template, "."));
287 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
288 context.getString(R.string.keyboard_key_numpad_template, ","));
289 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
290 context.getString(R.string.keyboard_key_numpad_template,
291 context.getString(R.string.keyboard_key_enter)));
292 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
293 context.getString(R.string.keyboard_key_numpad_template, "="));
294 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
295 context.getString(R.string.keyboard_key_numpad_template, "("));
296 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
297 context.getString(R.string.keyboard_key_numpad_template, ")"));
298 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
299 mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
300 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
301 mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
302 mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
303
304 mModifierNames.put(KeyEvent.META_META_ON, "Meta");
305 mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
306 mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
307 mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
308 mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
309 mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
310
311 mSpecialCharacterDrawables.put(
312 KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
313 mSpecialCharacterDrawables.put(
314 KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
315 mSpecialCharacterDrawables.put(
316 KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
317 mSpecialCharacterDrawables.put(
318 KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
319 mSpecialCharacterDrawables.put(
320 KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
321 mSpecialCharacterDrawables.put(
322 KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
323
324 mModifierDrawables.put(
325 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000326 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100327
Clara Bayarri03f19552016-04-06 10:59:52 +0100328 /**
329 * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
330 * existing device, that device's map is used. Otherwise, it checks first all available devices
331 * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
332 * Keyboard with its default map.
333 */
334 private void retrieveKeyCharacterMap(int deviceId) {
335 final InputManager inputManager = InputManager.getInstance();
Clara Bayarri382c59e2016-05-18 12:19:17 +0100336 mBackupKeyCharacterMap = inputManager.getInputDevice(-1).getKeyCharacterMap();
Clara Bayarri03f19552016-04-06 10:59:52 +0100337 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 }
Clara Bayarri382c59e2016-05-18 12:19:17 +0100354 // Fall back to -1, the virtual keyboard.
355 mKeyCharacterMap = mBackupKeyCharacterMap;
Clara Bayarri03f19552016-04-06 10:59:52 +0100356 }
357
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100358 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() {
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100376 if (mKeyboardShortcutsDialog != null) {
377 mKeyboardShortcutsDialog.dismiss();
378 mKeyboardShortcutsDialog = null;
379 }
380 }
381
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100382 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();
Andrei Stingaceanu9cfcafc2016-04-12 11:07:39 +0100417 List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100418
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
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100435 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100436 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100445 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100446 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100456 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100457 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100466 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100467 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100476 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100477 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100486 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100487 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) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100496 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100497 mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
498 calendarIcon,
499 KeyEvent.KEYCODE_L,
500 KeyEvent.META_META_ON));
501 }
502
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100503 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);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100514 }
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
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000544 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() {
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000550 handleShowKeyboardShortcuts(keyboardShortcutGroups);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000551 }
552 });
553 }
554
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000555 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);
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100564 dialogBuilder.setPositiveButton(R.string.quick_settings_done, mDialogCloseListener);
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000565 mKeyboardShortcutsDialog = dialogBuilder.create();
566 mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
567 Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
568 keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
569 mKeyboardShortcutsDialog.show();
570 }
571
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000572 private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
573 List<KeyboardShortcutGroup> keyboardShortcutGroups) {
574 LayoutInflater inflater = LayoutInflater.from(mContext);
575 final int keyboardShortcutGroupsSize = keyboardShortcutGroups.size();
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100576 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();
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100581 // Needed to be able to scale the image items to the same height as the text items.
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100582 final int shortcutKeyIconItemHeightWidth = shortcutsKeyView.getMeasuredHeight()
583 - shortcutsKeyView.getPaddingTop()
584 - shortcutsKeyView.getPaddingBottom();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000585 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 ? mContext.getColor(R.color.ksh_system_group_color)
592 : mContext.getColor(R.color.ksh_application_group_color));
593 keyboardShortcutsLayout.addView(categoryTitle);
594
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000595 LinearLayout shortcutContainer = (LinearLayout) inflater.inflate(
596 R.layout.keyboard_shortcuts_container, keyboardShortcutsLayout, false);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000597 final int itemsSize = group.getItems().size();
598 for (int j = 0; j < itemsSize; j++) {
599 KeyboardShortcutInfo info = group.getItems().get(j);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100600 List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800601 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 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000606 View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
607 shortcutContainer, false);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100608
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
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000617 .findViewById(R.id.keyboard_shortcuts_keyword);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100618 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 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000625
Andrei Stingaceanu844927d2016-02-16 14:31:58 +0000626 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000627 .findViewById(R.id.keyboard_shortcuts_item_container);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000628 final int shortcutKeysSize = shortcutKeys.size();
629 for (int k = 0; k < shortcutKeysSize; k++) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100630 StringOrDrawable shortcutRepresentation = shortcutKeys.get(k);
631 if (shortcutRepresentation.drawable != null) {
632 ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
633 R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
634 false);
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100635 Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth,
636 shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100637 Canvas canvas = new Canvas(bitmap);
638 shortcutRepresentation.drawable.setBounds(0, 0, canvas.getWidth(),
639 canvas.getHeight());
640 shortcutRepresentation.drawable.draw(canvas);
641 shortcutKeyIconView.setImageBitmap(bitmap);
642 shortcutItemsContainer.addView(shortcutKeyIconView);
643 } else if (shortcutRepresentation.string != null) {
644 TextView shortcutKeyTextView = (TextView) inflater.inflate(
645 R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer,
646 false);
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100647 shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100648 shortcutKeyTextView.setText(shortcutRepresentation.string);
649 shortcutItemsContainer.addView(shortcutKeyTextView);
650 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000651 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000652 shortcutContainer.addView(shortcutView);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000653 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000654 keyboardShortcutsLayout.addView(shortcutContainer);
655 if (i < keyboardShortcutGroupsSize - 1) {
656 View separator = inflater.inflate(
657 R.layout.keyboard_shortcuts_category_separator, keyboardShortcutsLayout,
658 false);
659 keyboardShortcutsLayout.addView(separator);
660 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000661 }
662 }
663
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100664 private List<StringOrDrawable> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
665 List<StringOrDrawable> shortcutKeys = getHumanReadableModifiers(info);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800666 if (shortcutKeys == null) {
667 return null;
668 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100669 String displayLabelString = null;
670 Drawable displayLabelDrawable = null;
Clara Bayarri1d648a12016-03-23 17:09:02 +0000671 if (info.getBaseCharacter() > Character.MIN_VALUE) {
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800672 displayLabelString = String.valueOf(info.getBaseCharacter());
Clara Bayarrib999af52016-04-06 16:02:35 +0100673 } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
674 displayLabelDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
675 } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
676 displayLabelString = mSpecialCharacterNames.get(info.getKeycode());
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800677 } else {
Clara Bayarri1d648a12016-03-23 17:09:02 +0000678 // Special case for shortcuts with no base key or keycode.
679 if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
680 return shortcutKeys;
681 }
Clara Bayarri03f19552016-04-06 10:59:52 +0100682 char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800683 if (displayLabel != 0) {
684 displayLabelString = String.valueOf(displayLabel);
685 } else {
Clara Bayarri382c59e2016-05-18 12:19:17 +0100686 displayLabel = mBackupKeyCharacterMap.getDisplayLabel(info.getKeycode());
687 if (displayLabel != 0) {
688 displayLabelString = String.valueOf(displayLabel);
689 } else {
690 return null;
691 }
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800692 }
693 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100694
695 if (displayLabelDrawable != null) {
696 shortcutKeys.add(new StringOrDrawable(displayLabelDrawable));
697 } else if (displayLabelString != null) {
698 shortcutKeys.add(new StringOrDrawable(displayLabelString.toUpperCase()));
699 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000700 return shortcutKeys;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100701 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800702
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100703 private List<StringOrDrawable> getHumanReadableModifiers(KeyboardShortcutInfo info) {
704 final List<StringOrDrawable> shortcutKeys = new ArrayList<>();
Clara Bayarrib9057df2016-03-02 11:37:09 -0800705 int modifiers = info.getModifiers();
706 if (modifiers == 0) {
707 return shortcutKeys;
708 }
Clara Bayarrib999af52016-04-06 16:02:35 +0100709 for(int i = 0; i < mModifierNames.size(); ++i) {
710 final int supportedModifier = mModifierNames.keyAt(i);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800711 if ((modifiers & supportedModifier) != 0) {
Clara Bayarrib999af52016-04-06 16:02:35 +0100712 if (mModifierDrawables.get(supportedModifier) != null) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100713 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100714 mModifierDrawables.get(supportedModifier)));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100715 } else {
716 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100717 mModifierNames.get(supportedModifier).toUpperCase()));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100718 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800719 modifiers &= ~supportedModifier;
720 }
721 }
722 if (modifiers != 0) {
723 // Remaining unsupported modifiers, don't show anything.
724 return null;
725 }
726 return shortcutKeys;
727 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100728
729 private static final class StringOrDrawable {
730 public String string;
731 public Drawable drawable;
732
733 public StringOrDrawable(String string) {
734 this.string = string;
735 }
736
737 public StringOrDrawable(Drawable drawable) {
738 this.drawable = drawable;
739 }
740 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100741}