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