blob: adb0e9f734bc8a74d2af00a0662559b5cf2f339e [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;
Andrew Sapperstein5c373442016-06-12 13:17:16 -070057import com.android.settingslib.Utils;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010058import com.android.systemui.R;
Clara Bayarri75e09792015-07-29 16:20:40 +010059import com.android.systemui.recents.Recents;
60
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000061import java.util.ArrayList;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010062import java.util.Collections;
63import java.util.Comparator;
Clara Bayarri75e09792015-07-29 16:20:40 +010064import java.util.List;
65
66import static android.content.Context.LAYOUT_INFLATER_SERVICE;
Clara Bayarri75e09792015-07-29 16:20:40 +010067import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010068
69/**
70 * Contains functionality for handling keyboard shortcuts.
71 */
Andrei Stingaceanud1519102016-03-31 15:53:33 +010072public final class KeyboardShortcuts {
Clara Bayarri4e850ff2016-03-02 11:12:32 -080073 private static final String TAG = KeyboardShortcuts.class.getSimpleName();
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +010074 private static final Object sLock = new Object();
75 private static KeyboardShortcuts sInstance;
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +010076
Clara Bayarrib999af52016-04-06 16:02:35 +010077 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<>();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000081
82 private final Handler mHandler = new Handler(Looper.getMainLooper());
83 private final Context mContext;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010084 private final IPackageManager mPackageManager;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010085 private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000086 public void onClick(DialogInterface dialog, int id) {
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +010087 dismissKeyboardShortcuts();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000088 }
89 };
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010090 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 };
Clara Bayarri75e09792015-07-29 16:20:40 +0100111
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100112 private Dialog mKeyboardShortcutsDialog;
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800113 private KeyCharacterMap mKeyCharacterMap;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100114
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100115 private KeyboardShortcuts(Context context) {
Andrew Sapperstein5c373442016-06-12 13:17:16 -0700116 this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100117 this.mPackageManager = AppGlobals.getPackageManager();
Clara Bayarrib999af52016-04-06 16:02:35 +0100118 loadResources(context);
119 }
120
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100121 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);
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100134 }
135 }
136
137 public static void toggle(Context context, int deviceId) {
138 synchronized (sLock) {
Clara Bayarri9fe10772016-06-01 17:28:14 +0100139 if (isShowing()) {
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100140 dismiss();
141 } else {
142 show(context, deviceId);
143 }
144 }
145 }
146
147 public static void dismiss() {
148 synchronized (sLock) {
149 if (sInstance != null) {
150 sInstance.dismissKeyboardShortcuts();
151 sInstance = null;
152 }
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100153 }
154 }
155
Clara Bayarri9fe10772016-06-01 17:28:14 +0100156 private static boolean isShowing() {
157 return sInstance != null && sInstance.mKeyboardShortcutsDialog != null
158 && sInstance.mKeyboardShortcutsDialog.isShowing();
159 }
160
Clara Bayarrib999af52016-04-06 16:02:35 +0100161 private void loadResources(Context context) {
162 mSpecialCharacterNames.put(
163 KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
164 mSpecialCharacterNames.put(
165 KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
166 mSpecialCharacterNames.put(
167 KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
168 mSpecialCharacterNames.put(
169 KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
170 mSpecialCharacterNames.put(
171 KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
172 mSpecialCharacterNames.put(
173 KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
174 mSpecialCharacterNames.put(
175 KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
176 mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
177 mSpecialCharacterNames.put(
178 KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
179 mSpecialCharacterNames.put(
180 KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
181 mSpecialCharacterNames.put(
182 KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
183 mSpecialCharacterNames.put(
184 KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
185 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
186 context.getString(R.string.keyboard_key_media_play_pause));
187 mSpecialCharacterNames.put(
188 KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
189 mSpecialCharacterNames.put(
190 KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
191 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
192 context.getString(R.string.keyboard_key_media_previous));
193 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
194 context.getString(R.string.keyboard_key_media_rewind));
195 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
196 context.getString(R.string.keyboard_key_media_fast_forward));
197 mSpecialCharacterNames.put(
198 KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
199 mSpecialCharacterNames.put(
200 KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
201 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
202 context.getString(R.string.keyboard_key_button_template, "A"));
203 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
204 context.getString(R.string.keyboard_key_button_template, "B"));
205 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
206 context.getString(R.string.keyboard_key_button_template, "C"));
207 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
208 context.getString(R.string.keyboard_key_button_template, "X"));
209 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
210 context.getString(R.string.keyboard_key_button_template, "Y"));
211 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
212 context.getString(R.string.keyboard_key_button_template, "Z"));
213 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
214 context.getString(R.string.keyboard_key_button_template, "L1"));
215 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
216 context.getString(R.string.keyboard_key_button_template, "R1"));
217 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
218 context.getString(R.string.keyboard_key_button_template, "L2"));
219 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
220 context.getString(R.string.keyboard_key_button_template, "R2"));
221 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
222 context.getString(R.string.keyboard_key_button_template, "Start"));
223 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
224 context.getString(R.string.keyboard_key_button_template, "Select"));
225 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
226 context.getString(R.string.keyboard_key_button_template, "Mode"));
227 mSpecialCharacterNames.put(
228 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
229 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
230 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
231 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
232 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
233 mSpecialCharacterNames.put(
234 KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
235 mSpecialCharacterNames.put(
236 KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
237 mSpecialCharacterNames.put(
238 KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
239 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
240 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
241 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
242 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
243 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
244 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
245 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
246 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
247 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
248 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
249 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
250 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
251 mSpecialCharacterNames.put(
252 KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
253 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
254 context.getString(R.string.keyboard_key_numpad_template, "0"));
255 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
256 context.getString(R.string.keyboard_key_numpad_template, "1"));
257 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
258 context.getString(R.string.keyboard_key_numpad_template, "2"));
259 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
260 context.getString(R.string.keyboard_key_numpad_template, "3"));
261 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
262 context.getString(R.string.keyboard_key_numpad_template, "4"));
263 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
264 context.getString(R.string.keyboard_key_numpad_template, "5"));
265 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
266 context.getString(R.string.keyboard_key_numpad_template, "6"));
267 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
268 context.getString(R.string.keyboard_key_numpad_template, "7"));
269 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
270 context.getString(R.string.keyboard_key_numpad_template, "8"));
271 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
272 context.getString(R.string.keyboard_key_numpad_template, "9"));
273 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
274 context.getString(R.string.keyboard_key_numpad_template, "/"));
275 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
276 context.getString(R.string.keyboard_key_numpad_template, "*"));
277 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
278 context.getString(R.string.keyboard_key_numpad_template, "-"));
279 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
280 context.getString(R.string.keyboard_key_numpad_template, "+"));
281 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
282 context.getString(R.string.keyboard_key_numpad_template, "."));
283 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
284 context.getString(R.string.keyboard_key_numpad_template, ","));
285 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
286 context.getString(R.string.keyboard_key_numpad_template,
287 context.getString(R.string.keyboard_key_enter)));
288 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
289 context.getString(R.string.keyboard_key_numpad_template, "="));
290 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
291 context.getString(R.string.keyboard_key_numpad_template, "("));
292 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
293 context.getString(R.string.keyboard_key_numpad_template, ")"));
294 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
295 mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
296 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
297 mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
298 mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
299
300 mModifierNames.put(KeyEvent.META_META_ON, "Meta");
301 mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
302 mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
303 mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
304 mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
305 mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
306
307 mSpecialCharacterDrawables.put(
308 KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
309 mSpecialCharacterDrawables.put(
310 KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
311 mSpecialCharacterDrawables.put(
312 KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
313 mSpecialCharacterDrawables.put(
314 KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
315 mSpecialCharacterDrawables.put(
316 KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
317 mSpecialCharacterDrawables.put(
318 KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
319
320 mModifierDrawables.put(
321 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000322 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100323
Clara Bayarri03f19552016-04-06 10:59:52 +0100324 /**
325 * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
326 * existing device, that device's map is used. Otherwise, it checks first all available devices
327 * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
328 * Keyboard with its default map.
329 */
330 private void retrieveKeyCharacterMap(int deviceId) {
331 final InputManager inputManager = InputManager.getInstance();
332 if (deviceId != -1) {
333 final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
334 if (inputDevice != null) {
335 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
336 return;
337 }
338 }
339 final int[] deviceIds = inputManager.getInputDeviceIds();
340 for (int i = 0; i < deviceIds.length; ++i) {
341 final InputDevice inputDevice = inputManager.getInputDevice(deviceIds[i]);
342 // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
343 // resort.
344 if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
345 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
346 return;
347 }
348 }
349 final InputDevice inputDevice = inputManager.getInputDevice(-1);
350 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
351 }
352
Andrei Stingaceanuf86bc972016-04-12 15:29:25 +0100353 private void showKeyboardShortcuts(int deviceId) {
354 retrieveKeyCharacterMap(deviceId);
355 Recents.getSystemServices().requestKeyboardShortcuts(mContext,
356 new KeyboardShortcutsReceiver() {
357 @Override
358 public void onKeyboardShortcutsReceived(
359 final List<KeyboardShortcutGroup> result) {
360 result.add(getSystemShortcuts());
361 final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
362 if (appShortcuts != null) {
363 result.add(appShortcuts);
364 }
365 showKeyboardShortcutsDialog(result);
366 }
367 }, deviceId);
368 }
369
370 private void dismissKeyboardShortcuts() {
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100371 if (mKeyboardShortcutsDialog != null) {
372 mKeyboardShortcutsDialog.dismiss();
373 mKeyboardShortcutsDialog = null;
374 }
375 }
376
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100377 private KeyboardShortcutGroup getSystemShortcuts() {
378 final KeyboardShortcutGroup systemGroup = new KeyboardShortcutGroup(
379 mContext.getString(R.string.keyboard_shortcut_group_system), true);
380 systemGroup.addItem(new KeyboardShortcutInfo(
381 mContext.getString(R.string.keyboard_shortcut_group_system_home),
382 KeyEvent.KEYCODE_ENTER,
383 KeyEvent.META_META_ON));
384 systemGroup.addItem(new KeyboardShortcutInfo(
385 mContext.getString(R.string.keyboard_shortcut_group_system_back),
386 KeyEvent.KEYCODE_DEL,
387 KeyEvent.META_META_ON));
388 systemGroup.addItem(new KeyboardShortcutInfo(
389 mContext.getString(R.string.keyboard_shortcut_group_system_recents),
390 KeyEvent.KEYCODE_TAB,
391 KeyEvent.META_ALT_ON));
392 systemGroup.addItem(new KeyboardShortcutInfo(
393 mContext.getString(
394 R.string.keyboard_shortcut_group_system_notifications),
395 KeyEvent.KEYCODE_N,
396 KeyEvent.META_META_ON));
397 systemGroup.addItem(new KeyboardShortcutInfo(
398 mContext.getString(
399 R.string.keyboard_shortcut_group_system_shortcuts_helper),
400 KeyEvent.KEYCODE_SLASH,
401 KeyEvent.META_META_ON));
402 systemGroup.addItem(new KeyboardShortcutInfo(
403 mContext.getString(
404 R.string.keyboard_shortcut_group_system_switch_input),
405 KeyEvent.KEYCODE_SPACE,
406 KeyEvent.META_META_ON));
407 return systemGroup;
408 }
409
410 private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
411 final int userId = mContext.getUserId();
Andrei Stingaceanu9cfcafc2016-04-12 11:07:39 +0100412 List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100413
414 // Assist.
415 final AssistUtils assistUtils = new AssistUtils(mContext);
416 final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
417 PackageInfo assistPackageInfo = null;
418 try {
419 assistPackageInfo = mPackageManager.getPackageInfo(
420 assistComponent.getPackageName(), 0, userId);
421 } catch (RemoteException e) {
422 Log.e(TAG, "PackageManagerService is dead");
423 }
424
425 if (assistPackageInfo != null) {
426 final Icon assistIcon = Icon.createWithResource(
427 assistPackageInfo.applicationInfo.packageName,
428 assistPackageInfo.applicationInfo.icon);
429
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100430 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100431 mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
432 assistIcon,
433 KeyEvent.KEYCODE_UNKNOWN,
434 KeyEvent.META_META_ON));
435 }
436
437 // Browser.
438 final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
439 if (browserIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100440 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100441 mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
442 browserIcon,
443 KeyEvent.KEYCODE_B,
444 KeyEvent.META_META_ON));
445 }
446
447
448 // Contacts.
449 final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
450 if (contactsIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100451 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100452 mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
453 contactsIcon,
454 KeyEvent.KEYCODE_C,
455 KeyEvent.META_META_ON));
456 }
457
458 // Email.
459 final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
460 if (emailIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100461 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100462 mContext.getString(R.string.keyboard_shortcut_group_applications_email),
463 emailIcon,
464 KeyEvent.KEYCODE_E,
465 KeyEvent.META_META_ON));
466 }
467
468 // Messaging.
469 final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
470 if (messagingIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100471 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100472 mContext.getString(R.string.keyboard_shortcut_group_applications_im),
473 messagingIcon,
474 KeyEvent.KEYCODE_T,
475 KeyEvent.META_META_ON));
476 }
477
478 // Music.
479 final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
480 if (musicIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100481 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100482 mContext.getString(R.string.keyboard_shortcut_group_applications_music),
483 musicIcon,
484 KeyEvent.KEYCODE_P,
485 KeyEvent.META_META_ON));
486 }
487
488 // Calendar.
489 final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
490 if (calendarIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100491 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100492 mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
493 calendarIcon,
494 KeyEvent.KEYCODE_L,
495 KeyEvent.META_META_ON));
496 }
497
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100498 final int itemsSize = keyboardShortcutInfoAppItems.size();
499 if (itemsSize == 0) {
500 return null;
501 }
502
503 // Sorts by label, case insensitive with nulls and/or empty labels last.
504 Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
505 return new KeyboardShortcutGroup(
506 mContext.getString(R.string.keyboard_shortcut_group_applications),
507 keyboardShortcutInfoAppItems,
508 true);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100509 }
510
511 private Icon getIconForIntentCategory(String intentCategory, int userId) {
512 final Intent intent = new Intent(Intent.ACTION_MAIN);
513 intent.addCategory(intentCategory);
514
515 final PackageInfo packageInfo = getPackageInfoForIntent(intent, userId);
516 if (packageInfo != null && packageInfo.applicationInfo.icon != 0) {
517 return Icon.createWithResource(
518 packageInfo.applicationInfo.packageName,
519 packageInfo.applicationInfo.icon);
520 }
521 return null;
522 }
523
524 private PackageInfo getPackageInfoForIntent(Intent intent, int userId) {
525 try {
526 ResolveInfo handler;
527 handler = mPackageManager.resolveIntent(
528 intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0, userId);
529 if (handler == null || handler.activityInfo == null) {
530 return null;
531 }
532 return mPackageManager.getPackageInfo(handler.activityInfo.packageName, 0, userId);
533 } catch (RemoteException e) {
534 Log.e(TAG, "PackageManagerService is dead", e);
535 return null;
536 }
537 }
538
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000539 private void showKeyboardShortcutsDialog(
540 final List<KeyboardShortcutGroup> keyboardShortcutGroups) {
541 // Need to post on the main thread.
542 mHandler.post(new Runnable() {
543 @Override
544 public void run() {
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000545 handleShowKeyboardShortcuts(keyboardShortcutGroups);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000546 }
547 });
548 }
549
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000550 private void handleShowKeyboardShortcuts(List<KeyboardShortcutGroup> keyboardShortcutGroups) {
551 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
552 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
553 LAYOUT_INFLATER_SERVICE);
554 final View keyboardShortcutsView = inflater.inflate(
555 R.layout.keyboard_shortcuts_view, null);
556 populateKeyboardShortcuts((LinearLayout) keyboardShortcutsView.findViewById(
557 R.id.keyboard_shortcuts_container), keyboardShortcutGroups);
558 dialogBuilder.setView(keyboardShortcutsView);
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100559 dialogBuilder.setPositiveButton(R.string.quick_settings_done, mDialogCloseListener);
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000560 mKeyboardShortcutsDialog = dialogBuilder.create();
561 mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
562 Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
563 keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
564 mKeyboardShortcutsDialog.show();
565 }
566
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000567 private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
568 List<KeyboardShortcutGroup> keyboardShortcutGroups) {
569 LayoutInflater inflater = LayoutInflater.from(mContext);
570 final int keyboardShortcutGroupsSize = keyboardShortcutGroups.size();
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100571 TextView shortcutsKeyView = (TextView) inflater.inflate(
572 R.layout.keyboard_shortcuts_key_view, null, false);
573 shortcutsKeyView.measure(
574 View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
575 final int shortcutKeyTextItemMinWidth = shortcutsKeyView.getMeasuredHeight();
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100576 // Needed to be able to scale the image items to the same height as the text items.
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100577 final int shortcutKeyIconItemHeightWidth = shortcutsKeyView.getMeasuredHeight()
578 - shortcutsKeyView.getPaddingTop()
579 - shortcutsKeyView.getPaddingBottom();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000580 for (int i = 0; i < keyboardShortcutGroupsSize; i++) {
581 KeyboardShortcutGroup group = keyboardShortcutGroups.get(i);
582 TextView categoryTitle = (TextView) inflater.inflate(
583 R.layout.keyboard_shortcuts_category_title, keyboardShortcutsLayout, false);
584 categoryTitle.setText(group.getLabel());
585 categoryTitle.setTextColor(group.isSystemGroup()
Andrew Sapperstein5c373442016-06-12 13:17:16 -0700586 ? Utils.getColorAccent(mContext)
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000587 : mContext.getColor(R.color.ksh_application_group_color));
588 keyboardShortcutsLayout.addView(categoryTitle);
589
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000590 LinearLayout shortcutContainer = (LinearLayout) inflater.inflate(
591 R.layout.keyboard_shortcuts_container, keyboardShortcutsLayout, false);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000592 final int itemsSize = group.getItems().size();
593 for (int j = 0; j < itemsSize; j++) {
594 KeyboardShortcutInfo info = group.getItems().get(j);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100595 List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800596 if (shortcutKeys == null) {
597 // Ignore shortcuts we can't display keys for.
598 Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
599 continue;
600 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000601 View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
602 shortcutContainer, false);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100603
604 if (info.getIcon() != null) {
605 ImageView shortcutIcon = (ImageView) shortcutView
606 .findViewById(R.id.keyboard_shortcuts_icon);
607 shortcutIcon.setImageIcon(info.getIcon());
608 shortcutIcon.setVisibility(View.VISIBLE);
609 }
610
611 TextView shortcutKeyword = (TextView) shortcutView
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000612 .findViewById(R.id.keyboard_shortcuts_keyword);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100613 shortcutKeyword.setText(info.getLabel());
614 if (info.getIcon() != null) {
615 RelativeLayout.LayoutParams lp =
616 (RelativeLayout.LayoutParams) shortcutKeyword.getLayoutParams();
617 lp.removeRule(RelativeLayout.ALIGN_PARENT_START);
618 shortcutKeyword.setLayoutParams(lp);
619 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000620
Andrei Stingaceanu844927d2016-02-16 14:31:58 +0000621 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000622 .findViewById(R.id.keyboard_shortcuts_item_container);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000623 final int shortcutKeysSize = shortcutKeys.size();
624 for (int k = 0; k < shortcutKeysSize; k++) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100625 StringOrDrawable shortcutRepresentation = shortcutKeys.get(k);
626 if (shortcutRepresentation.drawable != null) {
627 ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
628 R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
629 false);
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100630 Bitmap bitmap = Bitmap.createBitmap(shortcutKeyIconItemHeightWidth,
631 shortcutKeyIconItemHeightWidth, Bitmap.Config.ARGB_8888);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100632 Canvas canvas = new Canvas(bitmap);
633 shortcutRepresentation.drawable.setBounds(0, 0, canvas.getWidth(),
634 canvas.getHeight());
635 shortcutRepresentation.drawable.draw(canvas);
636 shortcutKeyIconView.setImageBitmap(bitmap);
637 shortcutItemsContainer.addView(shortcutKeyIconView);
638 } else if (shortcutRepresentation.string != null) {
639 TextView shortcutKeyTextView = (TextView) inflater.inflate(
640 R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer,
641 false);
Andrei Stingaceanua02965e2016-04-08 16:42:02 +0100642 shortcutKeyTextView.setMinimumWidth(shortcutKeyTextItemMinWidth);
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100643 shortcutKeyTextView.setText(shortcutRepresentation.string);
644 shortcutItemsContainer.addView(shortcutKeyTextView);
645 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000646 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000647 shortcutContainer.addView(shortcutView);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000648 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000649 keyboardShortcutsLayout.addView(shortcutContainer);
650 if (i < keyboardShortcutGroupsSize - 1) {
651 View separator = inflater.inflate(
652 R.layout.keyboard_shortcuts_category_separator, keyboardShortcutsLayout,
653 false);
654 keyboardShortcutsLayout.addView(separator);
655 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000656 }
657 }
658
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100659 private List<StringOrDrawable> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
660 List<StringOrDrawable> shortcutKeys = getHumanReadableModifiers(info);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800661 if (shortcutKeys == null) {
662 return null;
663 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100664 String displayLabelString = null;
665 Drawable displayLabelDrawable = null;
Clara Bayarri1d648a12016-03-23 17:09:02 +0000666 if (info.getBaseCharacter() > Character.MIN_VALUE) {
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800667 displayLabelString = String.valueOf(info.getBaseCharacter());
Clara Bayarrib999af52016-04-06 16:02:35 +0100668 } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
669 displayLabelDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
670 } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
671 displayLabelString = mSpecialCharacterNames.get(info.getKeycode());
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800672 } else {
Clara Bayarri1d648a12016-03-23 17:09:02 +0000673 // Special case for shortcuts with no base key or keycode.
674 if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
675 return shortcutKeys;
676 }
Clara Bayarri03f19552016-04-06 10:59:52 +0100677 char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800678 if (displayLabel != 0) {
679 displayLabelString = String.valueOf(displayLabel);
680 } else {
681 return null;
682 }
683 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100684
685 if (displayLabelDrawable != null) {
686 shortcutKeys.add(new StringOrDrawable(displayLabelDrawable));
687 } else if (displayLabelString != null) {
688 shortcutKeys.add(new StringOrDrawable(displayLabelString.toUpperCase()));
689 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000690 return shortcutKeys;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100691 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800692
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100693 private List<StringOrDrawable> getHumanReadableModifiers(KeyboardShortcutInfo info) {
694 final List<StringOrDrawable> shortcutKeys = new ArrayList<>();
Clara Bayarrib9057df2016-03-02 11:37:09 -0800695 int modifiers = info.getModifiers();
696 if (modifiers == 0) {
697 return shortcutKeys;
698 }
Clara Bayarrib999af52016-04-06 16:02:35 +0100699 for(int i = 0; i < mModifierNames.size(); ++i) {
700 final int supportedModifier = mModifierNames.keyAt(i);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800701 if ((modifiers & supportedModifier) != 0) {
Clara Bayarrib999af52016-04-06 16:02:35 +0100702 if (mModifierDrawables.get(supportedModifier) != null) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100703 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100704 mModifierDrawables.get(supportedModifier)));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100705 } else {
706 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100707 mModifierNames.get(supportedModifier).toUpperCase()));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100708 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800709 modifiers &= ~supportedModifier;
710 }
711 }
712 if (modifiers != 0) {
713 // Remaining unsupported modifiers, don't show anything.
714 return null;
715 }
716 return shortcutKeys;
717 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100718
719 private static final class StringOrDrawable {
720 public String string;
721 public Drawable drawable;
722
723 public StringOrDrawable(String string) {
724 this.string = string;
725 }
726
727 public StringOrDrawable(Drawable drawable) {
728 this.drawable = drawable;
729 }
730 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100731}