1 /*
2  * Copyright 2023 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 
17 package com.android.server.input;
18 
19 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
20 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
21 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
22 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
23 import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.role.RoleManager;
28 import android.content.Intent;
29 import android.hardware.input.KeyboardLayout;
30 import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
31 import android.icu.util.ULocale;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.util.Slog;
35 import android.util.SparseArray;
36 import android.util.proto.ProtoOutputStream;
37 import android.view.InputDevice;
38 import android.view.KeyEvent;
39 import android.view.inputmethod.InputMethodSubtype;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
43 import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
44 import com.android.internal.util.FrameworkStatsLog;
45 import com.android.server.policy.ModifierShortcutManager;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.Set;
52 
53 /**
54  * Collect Keyboard metrics
55  */
56 public final class KeyboardMetricsCollector {
57     private static final String TAG = "KeyboardMetricCollector";
58 
59     // To enable these logs, run: 'adb shell setprop log.tag.KeyboardMetricCollector DEBUG'
60     // (requires restart)
61     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
62 
63     @VisibleForTesting
64     static final String DEFAULT_LAYOUT_NAME = "Default";
65 
66     @VisibleForTesting
67     public static final String DEFAULT_LANGUAGE_TAG = "None";
68 
69     public enum KeyboardLogEvent {
70         UNSPECIFIED(
71                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
72                 "INVALID_KEYBOARD_EVENT"),
73         HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
74                 "HOME"),
75         RECENT_APPS(
76                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
77                 "RECENT_APPS"),
78         BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
79                 "BACK"),
80         APP_SWITCH(
81                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
82                 "APP_SWITCH"),
83         LAUNCH_ASSISTANT(
84                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
85                 "LAUNCH_ASSISTANT"),
86         LAUNCH_VOICE_ASSISTANT(
87                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
88                 "LAUNCH_VOICE_ASSISTANT"),
89         LAUNCH_SYSTEM_SETTINGS(
90                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
91                 "LAUNCH_SYSTEM_SETTINGS"),
92         TOGGLE_NOTIFICATION_PANEL(
93                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
94                 "TOGGLE_NOTIFICATION_PANEL"),
95         TOGGLE_TASKBAR(
96                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
97                 "TOGGLE_TASKBAR"),
98         TAKE_SCREENSHOT(
99                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
100                 "TAKE_SCREENSHOT"),
101         OPEN_SHORTCUT_HELPER(
102                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
103                 "OPEN_SHORTCUT_HELPER"),
104         BRIGHTNESS_UP(
105                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
106                 "BRIGHTNESS_UP"),
107         BRIGHTNESS_DOWN(
108                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
109                 "BRIGHTNESS_DOWN"),
110         KEYBOARD_BACKLIGHT_UP(
111                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
112                 "KEYBOARD_BACKLIGHT_UP"),
113         KEYBOARD_BACKLIGHT_DOWN(
114                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
115                 "KEYBOARD_BACKLIGHT_DOWN"),
116         KEYBOARD_BACKLIGHT_TOGGLE(
117                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
118                 "KEYBOARD_BACKLIGHT_TOGGLE"),
119         VOLUME_UP(
120                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
121                 "VOLUME_UP"),
122         VOLUME_DOWN(
123                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
124                 "VOLUME_DOWN"),
125         VOLUME_MUTE(
126                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
127                 "VOLUME_MUTE"),
128         ALL_APPS(
129                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
130                 "ALL_APPS"),
131         LAUNCH_SEARCH(
132                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
133                 "LAUNCH_SEARCH"),
134         LANGUAGE_SWITCH(
135                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
136                 "LANGUAGE_SWITCH"),
137         ACCESSIBILITY_ALL_APPS(
138                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
139                 "ACCESSIBILITY_ALL_APPS"),
140         TOGGLE_CAPS_LOCK(
141                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
142                 "TOGGLE_CAPS_LOCK"),
143         SYSTEM_MUTE(
144                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
145                 "SYSTEM_MUTE"),
146         SPLIT_SCREEN_NAVIGATION(
147                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
148                 "SPLIT_SCREEN_NAVIGATION"),
149 
150         CHANGE_SPLITSCREEN_FOCUS(
151                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
152                 "CHANGE_SPLITSCREEN_FOCUS"),
153         TRIGGER_BUG_REPORT(
154                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
155                 "TRIGGER_BUG_REPORT"),
156         LOCK_SCREEN(
157                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
158                 "LOCK_SCREEN"),
159         OPEN_NOTES(
160                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
161                 "OPEN_NOTES"),
162         TOGGLE_POWER(
163                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
164                 "TOGGLE_POWER"),
165         SYSTEM_NAVIGATION(
166                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
167                 "SYSTEM_NAVIGATION"),
168         SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
169                 "SLEEP"),
170         WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
171                 "WAKEUP"),
172         MEDIA_KEY(
173                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
174                 "MEDIA_KEY"),
175         LAUNCH_DEFAULT_BROWSER(
176                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
177                 "LAUNCH_DEFAULT_BROWSER"),
178         LAUNCH_DEFAULT_EMAIL(
179                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
180                 "LAUNCH_DEFAULT_EMAIL"),
181         LAUNCH_DEFAULT_CONTACTS(
182                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
183                 "LAUNCH_DEFAULT_CONTACTS"),
184         LAUNCH_DEFAULT_CALENDAR(
185                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
186                 "LAUNCH_DEFAULT_CALENDAR"),
187         LAUNCH_DEFAULT_CALCULATOR(
188                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
189                 "LAUNCH_DEFAULT_CALCULATOR"),
190         LAUNCH_DEFAULT_MUSIC(
191                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
192                 "LAUNCH_DEFAULT_MUSIC"),
193         LAUNCH_DEFAULT_MAPS(
194                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
195                 "LAUNCH_DEFAULT_MAPS"),
196         LAUNCH_DEFAULT_MESSAGING(
197                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
198                 "LAUNCH_DEFAULT_MESSAGING"),
199         LAUNCH_DEFAULT_GALLERY(
200                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
201                 "LAUNCH_DEFAULT_GALLERY"),
202         LAUNCH_DEFAULT_FILES(
203                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
204                 "LAUNCH_DEFAULT_FILES"),
205         LAUNCH_DEFAULT_WEATHER(
206                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
207                 "LAUNCH_DEFAULT_WEATHER"),
208         LAUNCH_DEFAULT_FITNESS(
209                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
210                 "LAUNCH_DEFAULT_FITNESS"),
211         LAUNCH_APPLICATION_BY_PACKAGE_NAME(
212                 FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
213                 "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
214         DESKTOP_MODE(
215                 FrameworkStatsLog
216                         .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
217                 "DESKTOP_MODE"),
218         MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
219                 .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
220                 "MULTIWINDOW_NAVIGATION");
221 
222 
223         private final int mValue;
224         private final String mName;
225 
226         private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
227 
228         static {
229             for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
VALUE_TO_ENUM_MAP.put(type.mValue, type)230                 VALUE_TO_ENUM_MAP.put(type.mValue, type);
231             }
232         }
233 
KeyboardLogEvent(int enumValue, String enumName)234         KeyboardLogEvent(int enumValue, String enumName) {
235             mValue = enumValue;
236             mName = enumName;
237         }
238 
getIntValue()239         public int getIntValue() {
240             return mValue;
241         }
242 
243         /**
244          * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
245          * value will return {@code null}
246          */
247         @Nullable
from(int value)248         public static KeyboardLogEvent from(int value) {
249             return VALUE_TO_ENUM_MAP.get(value);
250         }
251 
252         /**
253          * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
254          */
255         @Nullable
getVolumeEvent(int keycode)256         public static KeyboardLogEvent getVolumeEvent(int keycode) {
257             switch (keycode) {
258                 case KeyEvent.KEYCODE_VOLUME_DOWN:
259                     return VOLUME_DOWN;
260                 case KeyEvent.KEYCODE_VOLUME_UP:
261                     return VOLUME_UP;
262                 case KeyEvent.KEYCODE_VOLUME_MUTE:
263                     return VOLUME_MUTE;
264                 default:
265                     return null;
266             }
267         }
268 
269         /**
270          * Find KeyboardLogEvent corresponding to brightness up/down key events.
271          */
272         @Nullable
getBrightnessEvent(int keycode)273         public static KeyboardLogEvent getBrightnessEvent(int keycode) {
274             switch (keycode) {
275                 case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
276                     return BRIGHTNESS_DOWN;
277                 case KeyEvent.KEYCODE_BRIGHTNESS_UP:
278                     return BRIGHTNESS_UP;
279                 default:
280                     return null;
281             }
282         }
283 
284         /**
285          * Find KeyboardLogEvent corresponding to intent filter category. Returns
286          * {@code null if no matching event found}
287          */
288         @Nullable
getLogEventFromIntent(Intent intent)289         public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
290             Intent selectorIntent = intent.getSelector();
291             if (selectorIntent != null) {
292                 Set<String> selectorCategories = selectorIntent.getCategories();
293                 if (selectorCategories != null && !selectorCategories.isEmpty()) {
294                     for (String intentCategory : selectorCategories) {
295                         KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
296                         if (logEvent == null) {
297                             continue;
298                         }
299                         return logEvent;
300                     }
301                 }
302             }
303 
304             // The shortcut may be targeting a system role rather than using an intent selector,
305             // so check for that.
306             String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
307             if (!TextUtils.isEmpty(role)) {
308                 return getLogEventFromRole(role);
309             }
310 
311             Set<String> intentCategories = intent.getCategories();
312             if (intentCategories == null || intentCategories.isEmpty()
313                     || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
314                 return null;
315             }
316             if (intent.getComponent() == null) {
317                 return null;
318             }
319 
320             // TODO(b/280423320): Add new field package name associated in the
321             //  KeyboardShortcutEvent atom and log it accordingly.
322             return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
323         }
324 
325         @Nullable
getEventFromSelectorCategory(String category)326         private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
327             switch (category) {
328                 case Intent.CATEGORY_APP_BROWSER:
329                     return LAUNCH_DEFAULT_BROWSER;
330                 case Intent.CATEGORY_APP_EMAIL:
331                     return LAUNCH_DEFAULT_EMAIL;
332                 case Intent.CATEGORY_APP_CONTACTS:
333                     return LAUNCH_DEFAULT_CONTACTS;
334                 case Intent.CATEGORY_APP_CALENDAR:
335                     return LAUNCH_DEFAULT_CALENDAR;
336                 case Intent.CATEGORY_APP_CALCULATOR:
337                     return LAUNCH_DEFAULT_CALCULATOR;
338                 case Intent.CATEGORY_APP_MUSIC:
339                     return LAUNCH_DEFAULT_MUSIC;
340                 case Intent.CATEGORY_APP_MAPS:
341                     return LAUNCH_DEFAULT_MAPS;
342                 case Intent.CATEGORY_APP_MESSAGING:
343                     return LAUNCH_DEFAULT_MESSAGING;
344                 case Intent.CATEGORY_APP_GALLERY:
345                     return LAUNCH_DEFAULT_GALLERY;
346                 case Intent.CATEGORY_APP_FILES:
347                     return LAUNCH_DEFAULT_FILES;
348                 case Intent.CATEGORY_APP_WEATHER:
349                     return LAUNCH_DEFAULT_WEATHER;
350                 case Intent.CATEGORY_APP_FITNESS:
351                     return LAUNCH_DEFAULT_FITNESS;
352                 default:
353                     return null;
354             }
355         }
356 
357         /**
358          * Find KeyboardLogEvent corresponding to the provide system role name.
359          * Returns {@code null} if no matching event found.
360          */
361         @Nullable
getLogEventFromRole(String role)362         private static KeyboardLogEvent getLogEventFromRole(String role) {
363             if (RoleManager.ROLE_BROWSER.equals(role)) {
364                 return LAUNCH_DEFAULT_BROWSER;
365             } else if (RoleManager.ROLE_SMS.equals(role)) {
366                 return LAUNCH_DEFAULT_MESSAGING;
367             } else {
368                 Log.w(TAG, "Keyboard shortcut to launch "
369                         + role + " not supported for logging");
370                 return null;
371             }
372         }
373     }
374 
375     /**
376      * Log keyboard system shortcuts for the proto
377      * {@link com.android.os.input.KeyboardSystemsEventReported}
378      * defined in "stats/atoms/input/input_extension_atoms.proto"
379      */
logKeyboardSystemsEventReportedAtom(@ullable InputDevice inputDevice, @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes)380     public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
381             @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
382         // Logging Keyboard system event only for an external HW keyboard. We should not log events
383         // for virtual keyboards or internal Key events.
384         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
385             return;
386         }
387         if (keyboardSystemEvent == null) {
388             Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
389                     + ", modifier state = " + modifierState);
390             return;
391         }
392         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
393                 inputDevice.getVendorId(), inputDevice.getProductId(),
394                 keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
395                 inputDevice.getDeviceBus());
396 
397         if (DEBUG) {
398             Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
399         }
400     }
401 
402     /**
403      * Function to log the KeyboardConfigured
404      * {@link com.android.os.input.KeyboardConfigured} atom
405      *
406      * @param event {@link KeyboardConfigurationEvent} contains information about keyboard
407      *              configuration. Use {@link KeyboardConfigurationEvent.Builder} to create the
408      *              configuration event to log.
409      */
logKeyboardConfiguredAtom(KeyboardConfigurationEvent event)410     public static void logKeyboardConfiguredAtom(KeyboardConfigurationEvent event) {
411         // Creating proto to log nested field KeyboardLayoutConfig in atom
412         ProtoOutputStream proto = new ProtoOutputStream();
413 
414         for (LayoutConfiguration layoutConfiguration : event.getLayoutConfigurations()) {
415             addKeyboardLayoutConfigurationToProto(proto, layoutConfiguration);
416         }
417         // Push the atom to Statsd
418         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_CONFIGURED,
419                 event.isFirstConfiguration(), event.getVendorId(), event.getProductId(),
420                 proto.getBytes(), event.getDeviceBus());
421 
422         if (DEBUG) {
423             Slog.d(TAG, "Logging Keyboard configuration event: " + event);
424         }
425     }
426 
427     /**
428      * Populate the KeyboardLayoutConfig proto which is a repeated proto
429      * in the RepeatedKeyboardLayoutConfig proto with values from the
430      * {@link LayoutConfiguration} class
431      * The proto definitions can be found at:
432      * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto"
433      *
434      * @param proto Representing the nested proto RepeatedKeyboardLayoutConfig
435      * @param layoutConfiguration Class containing the fields for populating the
436      * KeyboardLayoutConfig proto
437      */
addKeyboardLayoutConfigurationToProto(ProtoOutputStream proto, LayoutConfiguration layoutConfiguration)438     private static void addKeyboardLayoutConfigurationToProto(ProtoOutputStream proto,
439             LayoutConfiguration layoutConfiguration) {
440         // Start a new KeyboardLayoutConfig proto.
441         long keyboardLayoutConfigToken = proto.start(
442                 RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG);
443         proto.write(KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
444                 layoutConfiguration.keyboardLanguageTag);
445         proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
446                 layoutConfiguration.keyboardLayoutType);
447         proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
448                 layoutConfiguration.keyboardLayoutName);
449         proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
450                 layoutConfiguration.layoutSelectionCriteria);
451         proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
452                 layoutConfiguration.imeLanguageTag);
453         proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
454                 layoutConfiguration.imeLayoutType);
455         proto.end(keyboardLayoutConfigToken);
456     }
457 
458     /**
459      * Class representing the proto KeyboardLayoutConfig defined in
460      * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto
461      *
462      * @see com.android.os.input.KeyboardConfigured
463      */
464     public static class KeyboardConfigurationEvent {
465 
466         private final InputDevice mInputDevice;
467         private final boolean mIsFirstConfiguration;
468         private final List<LayoutConfiguration> mLayoutConfigurations;
469 
KeyboardConfigurationEvent(InputDevice inputDevice, boolean isFirstConfiguration, List<LayoutConfiguration> layoutConfigurations)470         private KeyboardConfigurationEvent(InputDevice inputDevice, boolean isFirstConfiguration,
471                 List<LayoutConfiguration> layoutConfigurations) {
472             mInputDevice = inputDevice;
473             mIsFirstConfiguration = isFirstConfiguration;
474             mLayoutConfigurations = layoutConfigurations;
475         }
476 
getVendorId()477         public int getVendorId() {
478             return mInputDevice.getVendorId();
479         }
480 
getProductId()481         public int getProductId() {
482             return mInputDevice.getProductId();
483         }
484 
getDeviceBus()485         public int getDeviceBus() {
486             return mInputDevice.getDeviceBus();
487         }
488 
isFirstConfiguration()489         public boolean isFirstConfiguration() {
490             return mIsFirstConfiguration;
491         }
492 
getLayoutConfigurations()493         public List<LayoutConfiguration> getLayoutConfigurations() {
494             return mLayoutConfigurations;
495         }
496 
497         @Override
toString()498         public String toString() {
499             return "InputDevice = {VendorId = " + Integer.toHexString(getVendorId())
500                     + ", ProductId = " + Integer.toHexString(getProductId())
501                     + ", Device Bus = " + Integer.toHexString(getDeviceBus())
502                     + "}, isFirstConfiguration = " + mIsFirstConfiguration
503                     + ", LayoutConfigurations = " + mLayoutConfigurations;
504         }
505 
506         /**
507          * Builder class to help create {@link KeyboardConfigurationEvent}.
508          */
509         public static class Builder {
510             @NonNull
511             private final InputDevice mInputDevice;
512             private boolean mIsFirstConfiguration;
513             private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>();
514             private final List<String> mSelectedLayoutList = new ArrayList<>();
515             private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>();
516 
Builder(@onNull InputDevice inputDevice)517             public Builder(@NonNull InputDevice inputDevice) {
518                 Objects.requireNonNull(inputDevice, "InputDevice provided should not be null");
519                 mInputDevice = inputDevice;
520             }
521 
522             /**
523              * Set whether this is the first time this keyboard is configured.
524              */
setIsFirstTimeConfiguration(boolean isFirstTimeConfiguration)525             public Builder setIsFirstTimeConfiguration(boolean isFirstTimeConfiguration) {
526                 mIsFirstConfiguration = isFirstTimeConfiguration;
527                 return this;
528             }
529 
530             /**
531              * Adds keyboard layout configuration info for a particular IME subtype language
532              */
addLayoutSelection(@onNull InputMethodSubtype imeSubtype, @Nullable String selectedLayout, @LayoutSelectionCriteria int layoutSelectionCriteria)533             public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype,
534                     @Nullable String selectedLayout,
535                     @LayoutSelectionCriteria int layoutSelectionCriteria) {
536                 Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null");
537                 if (!isValidSelectionCriteria(layoutSelectionCriteria)) {
538                     throw new IllegalStateException("Invalid layout selection criteria");
539                 }
540                 mImeSubtypeList.add(imeSubtype);
541                 mSelectedLayoutList.add(selectedLayout);
542                 mLayoutSelectionCriteriaList.add(layoutSelectionCriteria);
543                 return this;
544             }
545 
546             /**
547              * Creates {@link KeyboardConfigurationEvent} from the provided information
548              */
build()549             public KeyboardConfigurationEvent build() {
550                 int size = mImeSubtypeList.size();
551                 if (size == 0) {
552                     throw new IllegalStateException("Should have at least one configuration");
553                 }
554                 List<LayoutConfiguration> configurationList = new ArrayList<>();
555                 for (int i = 0; i < size; i++) {
556                     @LayoutSelectionCriteria int layoutSelectionCriteria =
557                             mLayoutSelectionCriteriaList.get(i);
558                     InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
559                     String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
560                     keyboardLanguageTag = TextUtils.isEmpty(keyboardLanguageTag)
561                             ? DEFAULT_LANGUAGE_TAG : keyboardLanguageTag;
562                     int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
563                             mInputDevice.getKeyboardLayoutType());
564 
565                     ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
566                     String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
567                             : imeSubtype.getCanonicalizedLanguageTag();
568                     imeLanguageTag = TextUtils.isEmpty(imeLanguageTag) ? DEFAULT_LANGUAGE_TAG
569                             : imeLanguageTag;
570                     int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
571                             imeSubtype.getPhysicalKeyboardHintLayoutType());
572 
573                     // Sanitize null values
574                     String keyboardLayoutName = mSelectedLayoutList.get(i) == null
575                             ? DEFAULT_LAYOUT_NAME
576                             : mSelectedLayoutList.get(i);
577 
578                     configurationList.add(
579                             new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
580                                     keyboardLayoutName, layoutSelectionCriteria,
581                                     imeLayoutType, imeLanguageTag));
582                 }
583                 return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
584                         configurationList);
585             }
586         }
587     }
588 
589     @VisibleForTesting
590     static class LayoutConfiguration {
591         // This should match enum values defined in "frameworks/base/core/res/res/values/attrs.xml"
592         public final int keyboardLayoutType;
593         public final String keyboardLanguageTag;
594         public final String keyboardLayoutName;
595         @LayoutSelectionCriteria
596         public final int layoutSelectionCriteria;
597         public final int imeLayoutType;
598         public final String imeLanguageTag;
599 
LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag, String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria, int imeLayoutType, String imeLanguageTag)600         private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
601                 String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
602                 int imeLayoutType, String imeLanguageTag) {
603             this.keyboardLayoutType = keyboardLayoutType;
604             this.keyboardLanguageTag = keyboardLanguageTag;
605             this.keyboardLayoutName = keyboardLayoutName;
606             this.layoutSelectionCriteria = layoutSelectionCriteria;
607             this.imeLayoutType = imeLayoutType;
608             this.imeLanguageTag = imeLanguageTag;
609         }
610 
611         @Override
toString()612         public String toString() {
613             return "{keyboardLanguageTag = " + keyboardLanguageTag
614                     + " keyboardLayoutType = "
615                     + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
616                     + " keyboardLayoutName = " + keyboardLayoutName
617                     + " layoutSelectionCriteria = "
618                     + layoutSelectionCriteriaToString(layoutSelectionCriteria)
619                     + " imeLanguageTag = " + imeLanguageTag
620                     + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(
621                     imeLayoutType)
622                     + "}";
623         }
624     }
625 
isValidSelectionCriteria(int layoutSelectionCriteria)626     private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
627         return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
628                 || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
629                 || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
630                 || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
631     }
632 }
633