blob: 3d36aa44733048570e23cd61abba9d5c8ebcfd8d [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;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010057import com.android.systemui.R;
Clara Bayarri75e09792015-07-29 16:20:40 +010058import com.android.systemui.recents.Recents;
59
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000060import java.util.ArrayList;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010061import java.util.Collections;
62import java.util.Comparator;
Clara Bayarri75e09792015-07-29 16:20:40 +010063import java.util.List;
64
65import static android.content.Context.LAYOUT_INFLATER_SERVICE;
Clara Bayarri75e09792015-07-29 16:20:40 +010066import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010067
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010068import com.google.android.collect.Lists;
69
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +010070/**
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();
Clara Bayarrib999af52016-04-06 16:02:35 +010075 private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
76 private final SparseArray<String> mModifierNames = new SparseArray<>();
77 private final SparseArray<Drawable> mSpecialCharacterDrawables = new SparseArray<>();
78 private final SparseArray<Drawable> mModifierDrawables = new SparseArray<>();
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000079
80 private final Handler mHandler = new Handler(Looper.getMainLooper());
81 private final Context mContext;
Andrei Stingaceanu12e98032016-04-05 12:22:21 +010082 private final IPackageManager mPackageManager;
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010083 private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +000084 public void onClick(DialogInterface dialog, int id) {
85 dismissKeyboardShortcutsDialog();
86 }
87 };
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +010088 private final Comparator<KeyboardShortcutInfo> mApplicationItemsComparator =
89 new Comparator<KeyboardShortcutInfo>() {
90 @Override
91 public int compare(KeyboardShortcutInfo ksh1, KeyboardShortcutInfo ksh2) {
92 boolean ksh1ShouldBeLast = ksh1.getLabel() == null
93 || ksh1.getLabel().toString().isEmpty();
94 boolean ksh2ShouldBeLast = ksh2.getLabel() == null
95 || ksh2.getLabel().toString().isEmpty();
96 if (ksh1ShouldBeLast && ksh2ShouldBeLast) {
97 return 0;
98 }
99 if (ksh1ShouldBeLast) {
100 return 1;
101 }
102 if (ksh2ShouldBeLast) {
103 return -1;
104 }
105 return (ksh1.getLabel().toString()).compareToIgnoreCase(
106 ksh2.getLabel().toString());
107 }
108 };
Clara Bayarri75e09792015-07-29 16:20:40 +0100109
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100110 private Dialog mKeyboardShortcutsDialog;
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800111 private KeyCharacterMap mKeyCharacterMap;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100112
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000113 public KeyboardShortcuts(Context context) {
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000114 this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_Material_Light);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100115 this.mPackageManager = AppGlobals.getPackageManager();
Clara Bayarrib999af52016-04-06 16:02:35 +0100116 loadResources(context);
117 }
118
119 private void loadResources(Context context) {
120 mSpecialCharacterNames.put(
121 KeyEvent.KEYCODE_HOME, context.getString(R.string.keyboard_key_home));
122 mSpecialCharacterNames.put(
123 KeyEvent.KEYCODE_BACK, context.getString(R.string.keyboard_key_back));
124 mSpecialCharacterNames.put(
125 KeyEvent.KEYCODE_DPAD_UP, context.getString(R.string.keyboard_key_dpad_up));
126 mSpecialCharacterNames.put(
127 KeyEvent.KEYCODE_DPAD_DOWN, context.getString(R.string.keyboard_key_dpad_down));
128 mSpecialCharacterNames.put(
129 KeyEvent.KEYCODE_DPAD_LEFT, context.getString(R.string.keyboard_key_dpad_left));
130 mSpecialCharacterNames.put(
131 KeyEvent.KEYCODE_DPAD_RIGHT, context.getString(R.string.keyboard_key_dpad_right));
132 mSpecialCharacterNames.put(
133 KeyEvent.KEYCODE_DPAD_CENTER, context.getString(R.string.keyboard_key_dpad_center));
134 mSpecialCharacterNames.put(KeyEvent.KEYCODE_PERIOD, ".");
135 mSpecialCharacterNames.put(
136 KeyEvent.KEYCODE_TAB, context.getString(R.string.keyboard_key_tab));
137 mSpecialCharacterNames.put(
138 KeyEvent.KEYCODE_SPACE, context.getString(R.string.keyboard_key_space));
139 mSpecialCharacterNames.put(
140 KeyEvent.KEYCODE_ENTER, context.getString(R.string.keyboard_key_enter));
141 mSpecialCharacterNames.put(
142 KeyEvent.KEYCODE_DEL, context.getString(R.string.keyboard_key_backspace));
143 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
144 context.getString(R.string.keyboard_key_media_play_pause));
145 mSpecialCharacterNames.put(
146 KeyEvent.KEYCODE_MEDIA_STOP, context.getString(R.string.keyboard_key_media_stop));
147 mSpecialCharacterNames.put(
148 KeyEvent.KEYCODE_MEDIA_NEXT, context.getString(R.string.keyboard_key_media_next));
149 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS,
150 context.getString(R.string.keyboard_key_media_previous));
151 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_REWIND,
152 context.getString(R.string.keyboard_key_media_rewind));
153 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
154 context.getString(R.string.keyboard_key_media_fast_forward));
155 mSpecialCharacterNames.put(
156 KeyEvent.KEYCODE_PAGE_UP, context.getString(R.string.keyboard_key_page_up));
157 mSpecialCharacterNames.put(
158 KeyEvent.KEYCODE_PAGE_DOWN, context.getString(R.string.keyboard_key_page_down));
159 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_A,
160 context.getString(R.string.keyboard_key_button_template, "A"));
161 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_B,
162 context.getString(R.string.keyboard_key_button_template, "B"));
163 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_C,
164 context.getString(R.string.keyboard_key_button_template, "C"));
165 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_X,
166 context.getString(R.string.keyboard_key_button_template, "X"));
167 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Y,
168 context.getString(R.string.keyboard_key_button_template, "Y"));
169 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_Z,
170 context.getString(R.string.keyboard_key_button_template, "Z"));
171 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L1,
172 context.getString(R.string.keyboard_key_button_template, "L1"));
173 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R1,
174 context.getString(R.string.keyboard_key_button_template, "R1"));
175 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_L2,
176 context.getString(R.string.keyboard_key_button_template, "L2"));
177 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_R2,
178 context.getString(R.string.keyboard_key_button_template, "R2"));
179 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_START,
180 context.getString(R.string.keyboard_key_button_template, "Start"));
181 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_SELECT,
182 context.getString(R.string.keyboard_key_button_template, "Select"));
183 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BUTTON_MODE,
184 context.getString(R.string.keyboard_key_button_template, "Mode"));
185 mSpecialCharacterNames.put(
186 KeyEvent.KEYCODE_FORWARD_DEL, context.getString(R.string.keyboard_key_forward_del));
187 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ESCAPE, "Esc");
188 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SYSRQ, "SysRq");
189 mSpecialCharacterNames.put(KeyEvent.KEYCODE_BREAK, "Break");
190 mSpecialCharacterNames.put(KeyEvent.KEYCODE_SCROLL_LOCK, "Scroll Lock");
191 mSpecialCharacterNames.put(
192 KeyEvent.KEYCODE_MOVE_HOME, context.getString(R.string.keyboard_key_move_home));
193 mSpecialCharacterNames.put(
194 KeyEvent.KEYCODE_MOVE_END, context.getString(R.string.keyboard_key_move_end));
195 mSpecialCharacterNames.put(
196 KeyEvent.KEYCODE_INSERT, context.getString(R.string.keyboard_key_insert));
197 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F1, "F1");
198 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F2, "F2");
199 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F3, "F3");
200 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F4, "F4");
201 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F5, "F5");
202 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F6, "F6");
203 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F7, "F7");
204 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F8, "F8");
205 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F9, "F9");
206 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F10, "F10");
207 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F11, "F11");
208 mSpecialCharacterNames.put(KeyEvent.KEYCODE_F12, "F12");
209 mSpecialCharacterNames.put(
210 KeyEvent.KEYCODE_NUM_LOCK, context.getString(R.string.keyboard_key_num_lock));
211 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_0,
212 context.getString(R.string.keyboard_key_numpad_template, "0"));
213 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_1,
214 context.getString(R.string.keyboard_key_numpad_template, "1"));
215 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_2,
216 context.getString(R.string.keyboard_key_numpad_template, "2"));
217 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_3,
218 context.getString(R.string.keyboard_key_numpad_template, "3"));
219 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_4,
220 context.getString(R.string.keyboard_key_numpad_template, "4"));
221 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_5,
222 context.getString(R.string.keyboard_key_numpad_template, "5"));
223 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_6,
224 context.getString(R.string.keyboard_key_numpad_template, "6"));
225 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_7,
226 context.getString(R.string.keyboard_key_numpad_template, "7"));
227 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_8,
228 context.getString(R.string.keyboard_key_numpad_template, "8"));
229 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_9,
230 context.getString(R.string.keyboard_key_numpad_template, "9"));
231 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE,
232 context.getString(R.string.keyboard_key_numpad_template, "/"));
233 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
234 context.getString(R.string.keyboard_key_numpad_template, "*"));
235 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
236 context.getString(R.string.keyboard_key_numpad_template, "-"));
237 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ADD,
238 context.getString(R.string.keyboard_key_numpad_template, "+"));
239 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_DOT,
240 context.getString(R.string.keyboard_key_numpad_template, "."));
241 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_COMMA,
242 context.getString(R.string.keyboard_key_numpad_template, ","));
243 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_ENTER,
244 context.getString(R.string.keyboard_key_numpad_template,
245 context.getString(R.string.keyboard_key_enter)));
246 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_EQUALS,
247 context.getString(R.string.keyboard_key_numpad_template, "="));
248 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
249 context.getString(R.string.keyboard_key_numpad_template, "("));
250 mSpecialCharacterNames.put(KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
251 context.getString(R.string.keyboard_key_numpad_template, ")"));
252 mSpecialCharacterNames.put(KeyEvent.KEYCODE_ZENKAKU_HANKAKU, "半角/全角");
253 mSpecialCharacterNames.put(KeyEvent.KEYCODE_EISU, "英数");
254 mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
255 mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
256 mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
257
258 mModifierNames.put(KeyEvent.META_META_ON, "Meta");
259 mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
260 mModifierNames.put(KeyEvent.META_ALT_ON, "Alt");
261 mModifierNames.put(KeyEvent.META_SHIFT_ON, "Shift");
262 mModifierNames.put(KeyEvent.META_SYM_ON, "Sym");
263 mModifierNames.put(KeyEvent.META_FUNCTION_ON, "Fn");
264
265 mSpecialCharacterDrawables.put(
266 KeyEvent.KEYCODE_DEL, context.getDrawable(R.drawable.ic_ksh_key_backspace));
267 mSpecialCharacterDrawables.put(
268 KeyEvent.KEYCODE_ENTER, context.getDrawable(R.drawable.ic_ksh_key_enter));
269 mSpecialCharacterDrawables.put(
270 KeyEvent.KEYCODE_DPAD_UP, context.getDrawable(R.drawable.ic_ksh_key_up));
271 mSpecialCharacterDrawables.put(
272 KeyEvent.KEYCODE_DPAD_RIGHT, context.getDrawable(R.drawable.ic_ksh_key_right));
273 mSpecialCharacterDrawables.put(
274 KeyEvent.KEYCODE_DPAD_DOWN, context.getDrawable(R.drawable.ic_ksh_key_down));
275 mSpecialCharacterDrawables.put(
276 KeyEvent.KEYCODE_DPAD_LEFT, context.getDrawable(R.drawable.ic_ksh_key_left));
277
278 mModifierDrawables.put(
279 KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000280 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100281
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800282 public void toggleKeyboardShortcuts(int deviceId) {
283 InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
284 if (inputDevice != null) {
285 mKeyCharacterMap = inputDevice.getKeyCharacterMap();
286 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100287 if (mKeyboardShortcutsDialog == null) {
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000288 Recents.getSystemServices().requestKeyboardShortcuts(mContext,
Clara Bayarri75e09792015-07-29 16:20:40 +0100289 new KeyboardShortcutsReceiver() {
290 @Override
291 public void onKeyboardShortcutsReceived(
292 final List<KeyboardShortcutGroup> result) {
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100293 result.add(getSystemShortcuts());
294 final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
295 if (appShortcuts != null) {
296 result.add(appShortcuts);
297 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000298 showKeyboardShortcutsDialog(result);
Clara Bayarri75e09792015-07-29 16:20:40 +0100299 }
Clara Bayarrifcd7e802016-03-10 12:58:18 +0000300 }, deviceId);
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100301 } else {
302 dismissKeyboardShortcutsDialog();
303 }
304 }
305
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100306 public void dismissKeyboardShortcutsDialog() {
307 if (mKeyboardShortcutsDialog != null) {
308 mKeyboardShortcutsDialog.dismiss();
309 mKeyboardShortcutsDialog = null;
310 }
311 }
312
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100313 private KeyboardShortcutGroup getSystemShortcuts() {
314 final KeyboardShortcutGroup systemGroup = new KeyboardShortcutGroup(
315 mContext.getString(R.string.keyboard_shortcut_group_system), true);
316 systemGroup.addItem(new KeyboardShortcutInfo(
317 mContext.getString(R.string.keyboard_shortcut_group_system_home),
318 KeyEvent.KEYCODE_ENTER,
319 KeyEvent.META_META_ON));
320 systemGroup.addItem(new KeyboardShortcutInfo(
321 mContext.getString(R.string.keyboard_shortcut_group_system_back),
322 KeyEvent.KEYCODE_DEL,
323 KeyEvent.META_META_ON));
324 systemGroup.addItem(new KeyboardShortcutInfo(
325 mContext.getString(R.string.keyboard_shortcut_group_system_recents),
326 KeyEvent.KEYCODE_TAB,
327 KeyEvent.META_ALT_ON));
328 systemGroup.addItem(new KeyboardShortcutInfo(
329 mContext.getString(
330 R.string.keyboard_shortcut_group_system_notifications),
331 KeyEvent.KEYCODE_N,
332 KeyEvent.META_META_ON));
333 systemGroup.addItem(new KeyboardShortcutInfo(
334 mContext.getString(
335 R.string.keyboard_shortcut_group_system_shortcuts_helper),
336 KeyEvent.KEYCODE_SLASH,
337 KeyEvent.META_META_ON));
338 systemGroup.addItem(new KeyboardShortcutInfo(
339 mContext.getString(
340 R.string.keyboard_shortcut_group_system_switch_input),
341 KeyEvent.KEYCODE_SPACE,
342 KeyEvent.META_META_ON));
343 return systemGroup;
344 }
345
346 private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
347 final int userId = mContext.getUserId();
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100348 List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = Lists.newArrayList();
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100349
350 // Assist.
351 final AssistUtils assistUtils = new AssistUtils(mContext);
352 final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
353 PackageInfo assistPackageInfo = null;
354 try {
355 assistPackageInfo = mPackageManager.getPackageInfo(
356 assistComponent.getPackageName(), 0, userId);
357 } catch (RemoteException e) {
358 Log.e(TAG, "PackageManagerService is dead");
359 }
360
361 if (assistPackageInfo != null) {
362 final Icon assistIcon = Icon.createWithResource(
363 assistPackageInfo.applicationInfo.packageName,
364 assistPackageInfo.applicationInfo.icon);
365
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100366 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100367 mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
368 assistIcon,
369 KeyEvent.KEYCODE_UNKNOWN,
370 KeyEvent.META_META_ON));
371 }
372
373 // Browser.
374 final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
375 if (browserIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100376 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100377 mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
378 browserIcon,
379 KeyEvent.KEYCODE_B,
380 KeyEvent.META_META_ON));
381 }
382
383
384 // Contacts.
385 final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
386 if (contactsIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100387 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100388 mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
389 contactsIcon,
390 KeyEvent.KEYCODE_C,
391 KeyEvent.META_META_ON));
392 }
393
394 // Email.
395 final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
396 if (emailIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100397 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100398 mContext.getString(R.string.keyboard_shortcut_group_applications_email),
399 emailIcon,
400 KeyEvent.KEYCODE_E,
401 KeyEvent.META_META_ON));
402 }
403
404 // Messaging.
405 final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
406 if (messagingIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100407 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100408 mContext.getString(R.string.keyboard_shortcut_group_applications_im),
409 messagingIcon,
410 KeyEvent.KEYCODE_T,
411 KeyEvent.META_META_ON));
412 }
413
414 // Music.
415 final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
416 if (musicIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100417 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100418 mContext.getString(R.string.keyboard_shortcut_group_applications_music),
419 musicIcon,
420 KeyEvent.KEYCODE_P,
421 KeyEvent.META_META_ON));
422 }
423
424 // Calendar.
425 final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
426 if (calendarIcon != null) {
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100427 keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100428 mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
429 calendarIcon,
430 KeyEvent.KEYCODE_L,
431 KeyEvent.META_META_ON));
432 }
433
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100434 final int itemsSize = keyboardShortcutInfoAppItems.size();
435 if (itemsSize == 0) {
436 return null;
437 }
438
439 // Sorts by label, case insensitive with nulls and/or empty labels last.
440 Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
441 return new KeyboardShortcutGroup(
442 mContext.getString(R.string.keyboard_shortcut_group_applications),
443 keyboardShortcutInfoAppItems,
444 true);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100445 }
446
447 private Icon getIconForIntentCategory(String intentCategory, int userId) {
448 final Intent intent = new Intent(Intent.ACTION_MAIN);
449 intent.addCategory(intentCategory);
450
451 final PackageInfo packageInfo = getPackageInfoForIntent(intent, userId);
452 if (packageInfo != null && packageInfo.applicationInfo.icon != 0) {
453 return Icon.createWithResource(
454 packageInfo.applicationInfo.packageName,
455 packageInfo.applicationInfo.icon);
456 }
457 return null;
458 }
459
460 private PackageInfo getPackageInfoForIntent(Intent intent, int userId) {
461 try {
462 ResolveInfo handler;
463 handler = mPackageManager.resolveIntent(
464 intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), 0, userId);
465 if (handler == null || handler.activityInfo == null) {
466 return null;
467 }
468 return mPackageManager.getPackageInfo(handler.activityInfo.packageName, 0, userId);
469 } catch (RemoteException e) {
470 Log.e(TAG, "PackageManagerService is dead", e);
471 return null;
472 }
473 }
474
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000475 private void showKeyboardShortcutsDialog(
476 final List<KeyboardShortcutGroup> keyboardShortcutGroups) {
477 // Need to post on the main thread.
478 mHandler.post(new Runnable() {
479 @Override
480 public void run() {
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000481 handleShowKeyboardShortcuts(keyboardShortcutGroups);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000482 }
483 });
484 }
485
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000486 private void handleShowKeyboardShortcuts(List<KeyboardShortcutGroup> keyboardShortcutGroups) {
487 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
488 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
489 LAYOUT_INFLATER_SERVICE);
490 final View keyboardShortcutsView = inflater.inflate(
491 R.layout.keyboard_shortcuts_view, null);
492 populateKeyboardShortcuts((LinearLayout) keyboardShortcutsView.findViewById(
493 R.id.keyboard_shortcuts_container), keyboardShortcutGroups);
494 dialogBuilder.setView(keyboardShortcutsView);
Andrei Stingaceanu56e44e42016-04-08 15:07:15 +0100495 dialogBuilder.setPositiveButton(R.string.quick_settings_done, mDialogCloseListener);
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000496 mKeyboardShortcutsDialog = dialogBuilder.create();
497 mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
498 Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
499 keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
500 mKeyboardShortcutsDialog.show();
501 }
502
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000503 private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
504 List<KeyboardShortcutGroup> keyboardShortcutGroups) {
505 LayoutInflater inflater = LayoutInflater.from(mContext);
506 final int keyboardShortcutGroupsSize = keyboardShortcutGroups.size();
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100507 // Needed to be able to scale the image items to the same height as the text items.
508 final int shortcutTextItemHeight = getShortcutTextItemHeight(inflater);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000509 for (int i = 0; i < keyboardShortcutGroupsSize; i++) {
510 KeyboardShortcutGroup group = keyboardShortcutGroups.get(i);
511 TextView categoryTitle = (TextView) inflater.inflate(
512 R.layout.keyboard_shortcuts_category_title, keyboardShortcutsLayout, false);
513 categoryTitle.setText(group.getLabel());
514 categoryTitle.setTextColor(group.isSystemGroup()
515 ? mContext.getColor(R.color.ksh_system_group_color)
516 : mContext.getColor(R.color.ksh_application_group_color));
517 keyboardShortcutsLayout.addView(categoryTitle);
518
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000519 LinearLayout shortcutContainer = (LinearLayout) inflater.inflate(
520 R.layout.keyboard_shortcuts_container, keyboardShortcutsLayout, false);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000521 final int itemsSize = group.getItems().size();
522 for (int j = 0; j < itemsSize; j++) {
523 KeyboardShortcutInfo info = group.getItems().get(j);
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800524 if (info.getKeycode() != KeyEvent.KEYCODE_UNKNOWN
525 && !KeyCharacterMap.deviceHasKey(info.getKeycode())) {
526 // The user can't achieve this shortcut, so skipping.
527 Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
528 continue;
529 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100530 List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800531 if (shortcutKeys == null) {
532 // Ignore shortcuts we can't display keys for.
533 Log.w(TAG, "Keyboard Shortcut contains unsupported keys, skipping.");
534 continue;
535 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000536 View shortcutView = inflater.inflate(R.layout.keyboard_shortcut_app_item,
537 shortcutContainer, false);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100538
539 if (info.getIcon() != null) {
540 ImageView shortcutIcon = (ImageView) shortcutView
541 .findViewById(R.id.keyboard_shortcuts_icon);
542 shortcutIcon.setImageIcon(info.getIcon());
543 shortcutIcon.setVisibility(View.VISIBLE);
544 }
545
546 TextView shortcutKeyword = (TextView) shortcutView
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000547 .findViewById(R.id.keyboard_shortcuts_keyword);
Andrei Stingaceanu12e98032016-04-05 12:22:21 +0100548 shortcutKeyword.setText(info.getLabel());
549 if (info.getIcon() != null) {
550 RelativeLayout.LayoutParams lp =
551 (RelativeLayout.LayoutParams) shortcutKeyword.getLayoutParams();
552 lp.removeRule(RelativeLayout.ALIGN_PARENT_START);
553 shortcutKeyword.setLayoutParams(lp);
554 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000555
Andrei Stingaceanu844927d2016-02-16 14:31:58 +0000556 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000557 .findViewById(R.id.keyboard_shortcuts_item_container);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000558 final int shortcutKeysSize = shortcutKeys.size();
559 for (int k = 0; k < shortcutKeysSize; k++) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100560 StringOrDrawable shortcutRepresentation = shortcutKeys.get(k);
561 if (shortcutRepresentation.drawable != null) {
562 ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
563 R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
564 false);
565 Bitmap bitmap = Bitmap.createBitmap(shortcutTextItemHeight,
566 shortcutTextItemHeight, Bitmap.Config.ARGB_8888);
567 Canvas canvas = new Canvas(bitmap);
568 shortcutRepresentation.drawable.setBounds(0, 0, canvas.getWidth(),
569 canvas.getHeight());
570 shortcutRepresentation.drawable.draw(canvas);
571 shortcutKeyIconView.setImageBitmap(bitmap);
572 shortcutItemsContainer.addView(shortcutKeyIconView);
573 } else if (shortcutRepresentation.string != null) {
574 TextView shortcutKeyTextView = (TextView) inflater.inflate(
575 R.layout.keyboard_shortcuts_key_view, shortcutItemsContainer,
576 false);
577 shortcutKeyTextView.setText(shortcutRepresentation.string);
578 shortcutItemsContainer.addView(shortcutKeyTextView);
579 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000580 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000581 shortcutContainer.addView(shortcutView);
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000582 }
Andrei Stingaceanu2b909e92016-02-03 17:52:00 +0000583 keyboardShortcutsLayout.addView(shortcutContainer);
584 if (i < keyboardShortcutGroupsSize - 1) {
585 View separator = inflater.inflate(
586 R.layout.keyboard_shortcuts_category_separator, keyboardShortcutsLayout,
587 false);
588 keyboardShortcutsLayout.addView(separator);
589 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000590 }
591 }
592
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100593 private int getShortcutTextItemHeight(LayoutInflater inflater) {
594 TextView shortcutKeyTextView = (TextView) inflater.inflate(
595 R.layout.keyboard_shortcuts_key_view, null, false);
596 shortcutKeyTextView.measure(
597 View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
598 return shortcutKeyTextView.getMeasuredHeight()
599 - shortcutKeyTextView.getPaddingTop()
600 - shortcutKeyTextView.getPaddingBottom();
601 }
602
603 private List<StringOrDrawable> getHumanReadableShortcutKeys(KeyboardShortcutInfo info) {
604 List<StringOrDrawable> shortcutKeys = getHumanReadableModifiers(info);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800605 if (shortcutKeys == null) {
606 return null;
607 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100608 String displayLabelString = null;
609 Drawable displayLabelDrawable = null;
Clara Bayarri1d648a12016-03-23 17:09:02 +0000610 if (info.getBaseCharacter() > Character.MIN_VALUE) {
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800611 displayLabelString = String.valueOf(info.getBaseCharacter());
Clara Bayarrib999af52016-04-06 16:02:35 +0100612 } else if (mSpecialCharacterDrawables.get(info.getKeycode()) != null) {
613 displayLabelDrawable = mSpecialCharacterDrawables.get(info.getKeycode());
614 } else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
615 displayLabelString = mSpecialCharacterNames.get(info.getKeycode());
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800616 } else {
Clara Bayarri1d648a12016-03-23 17:09:02 +0000617 // Special case for shortcuts with no base key or keycode.
618 if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
619 return shortcutKeys;
620 }
Clara Bayarri4e850ff2016-03-02 11:12:32 -0800621 // TODO: Have a generic map for when we don't have the device's.
622 char displayLabel = mKeyCharacterMap == null
623 ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode());
624 if (displayLabel != 0) {
625 displayLabelString = String.valueOf(displayLabel);
626 } else {
627 return null;
628 }
629 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100630
631 if (displayLabelDrawable != null) {
632 shortcutKeys.add(new StringOrDrawable(displayLabelDrawable));
633 } else if (displayLabelString != null) {
634 shortcutKeys.add(new StringOrDrawable(displayLabelString.toUpperCase()));
635 }
Andrei Stingaceanu8861cb02016-01-20 16:48:30 +0000636 return shortcutKeys;
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100637 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800638
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100639 private List<StringOrDrawable> getHumanReadableModifiers(KeyboardShortcutInfo info) {
640 final List<StringOrDrawable> shortcutKeys = new ArrayList<>();
Clara Bayarrib9057df2016-03-02 11:37:09 -0800641 int modifiers = info.getModifiers();
642 if (modifiers == 0) {
643 return shortcutKeys;
644 }
Clara Bayarrib999af52016-04-06 16:02:35 +0100645 for(int i = 0; i < mModifierNames.size(); ++i) {
646 final int supportedModifier = mModifierNames.keyAt(i);
Clara Bayarrib9057df2016-03-02 11:37:09 -0800647 if ((modifiers & supportedModifier) != 0) {
Clara Bayarrib999af52016-04-06 16:02:35 +0100648 if (mModifierDrawables.get(supportedModifier) != null) {
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100649 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100650 mModifierDrawables.get(supportedModifier)));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100651 } else {
652 shortcutKeys.add(new StringOrDrawable(
Clara Bayarrib999af52016-04-06 16:02:35 +0100653 mModifierNames.get(supportedModifier).toUpperCase()));
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100654 }
Clara Bayarrib9057df2016-03-02 11:37:09 -0800655 modifiers &= ~supportedModifier;
656 }
657 }
658 if (modifiers != 0) {
659 // Remaining unsupported modifiers, don't show anything.
660 return null;
661 }
662 return shortcutKeys;
663 }
Andrei Stingaceanud1519102016-03-31 15:53:33 +0100664
665 private static final class StringOrDrawable {
666 public String string;
667 public Drawable drawable;
668
669 public StringOrDrawable(String string) {
670 this.string = string;
671 }
672
673 public StringOrDrawable(Drawable drawable) {
674 this.drawable = drawable;
675 }
676 }
Andrei Stingaceanu9d9294c2015-08-24 17:19:06 +0100677}