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