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