1 /* 2 * Copyright (C) 2022 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 android.annotation.BinderThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothManager; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.hardware.BatteryState; 30 import android.hardware.input.IInputDeviceBatteryListener; 31 import android.hardware.input.IInputDeviceBatteryState; 32 import android.hardware.input.InputManager; 33 import android.os.Handler; 34 import android.os.HandlerExecutor; 35 import android.os.IBinder; 36 import android.os.Looper; 37 import android.os.RemoteException; 38 import android.os.SystemClock; 39 import android.os.UEventObserver; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.IndentingPrintWriter; 43 import android.util.Log; 44 import android.util.Slog; 45 import android.view.InputDevice; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.io.PrintWriter; 51 import java.util.Arrays; 52 import java.util.Objects; 53 import java.util.Set; 54 import java.util.concurrent.Executor; 55 import java.util.function.Consumer; 56 import java.util.function.Function; 57 import java.util.function.Predicate; 58 59 /** 60 * A thread-safe component of {@link InputManagerService} responsible for managing the battery state 61 * of input devices. 62 * 63 * Interactions with BatteryController can happen on several threads, including Binder threads, the 64 * {@link UEventObserver}'s thread, or its own Handler thread, among others. All public methods, and 65 * private methods prefixed with "handle-" (e.g. {@link #handleListeningProcessDied(int)}), 66 * serve as entry points for these threads. 67 */ 68 final class BatteryController { 69 private static final String TAG = BatteryController.class.getSimpleName(); 70 71 // To enable these logs, run: 72 // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart) 73 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 74 75 @VisibleForTesting 76 static final long POLLING_PERIOD_MILLIS = 10_000; // 10 seconds 77 @VisibleForTesting 78 static final long USI_BATTERY_VALIDITY_DURATION_MILLIS = 60 * 60_000; // 1 hour 79 80 private final Object mLock = new Object(); 81 private final Context mContext; 82 private final NativeInputManagerService mNative; 83 private final Handler mHandler; 84 private final UEventManager mUEventManager; 85 private final BluetoothBatteryManager mBluetoothBatteryManager; 86 87 // Maps a pid to the registered listener record for that process. There can only be one battery 88 // listener per process. 89 @GuardedBy("mLock") 90 private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>(); 91 92 // Maps a deviceId that is being monitored to the monitor for the battery state of the device. 93 @GuardedBy("mLock") 94 private final ArrayMap<Integer, DeviceMonitor> mDeviceMonitors = new ArrayMap<>(); 95 96 @GuardedBy("mLock") 97 private boolean mIsPolling = false; 98 @GuardedBy("mLock") 99 private boolean mIsInteractive = true; 100 @Nullable 101 @GuardedBy("mLock") 102 private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener; 103 BatteryController(Context context, NativeInputManagerService nativeService, Looper looper, UEventManager uEventManager)104 BatteryController(Context context, NativeInputManagerService nativeService, Looper looper, 105 UEventManager uEventManager) { 106 this(context, nativeService, looper, uEventManager, 107 new LocalBluetoothBatteryManager(context, looper)); 108 } 109 110 @VisibleForTesting BatteryController(Context context, NativeInputManagerService nativeService, Looper looper, UEventManager uEventManager, BluetoothBatteryManager bbm)111 BatteryController(Context context, NativeInputManagerService nativeService, Looper looper, 112 UEventManager uEventManager, BluetoothBatteryManager bbm) { 113 mContext = context; 114 mNative = nativeService; 115 mHandler = new Handler(looper); 116 mUEventManager = uEventManager; 117 mBluetoothBatteryManager = bbm; 118 } 119 systemRunning()120 public void systemRunning() { 121 final InputManager inputManager = 122 Objects.requireNonNull(mContext.getSystemService(InputManager.class)); 123 inputManager.registerInputDeviceListener(mInputDeviceListener, mHandler); 124 for (int deviceId : inputManager.getInputDeviceIds()) { 125 mInputDeviceListener.onInputDeviceAdded(deviceId); 126 } 127 } 128 129 /** 130 * Register the battery listener for the given input device and start monitoring its battery 131 * state. 132 */ 133 @BinderThread registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener, int pid)134 public void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener, 135 int pid) { 136 synchronized (mLock) { 137 ListenerRecord listenerRecord = mListenerRecords.get(pid); 138 139 if (listenerRecord == null) { 140 listenerRecord = new ListenerRecord(pid, listener); 141 try { 142 listener.asBinder().linkToDeath(listenerRecord.mDeathRecipient, 0); 143 } catch (RemoteException e) { 144 Slog.i(TAG, "Client died before battery listener could be registered."); 145 return; 146 } 147 mListenerRecords.put(pid, listenerRecord); 148 if (DEBUG) Slog.d(TAG, "Battery listener added for pid " + pid); 149 } 150 151 if (listenerRecord.mListener.asBinder() != listener.asBinder()) { 152 throw new SecurityException( 153 "Cannot register a new battery listener when there is already another " 154 + "registered listener for pid " 155 + pid); 156 } 157 if (!listenerRecord.mMonitoredDevices.add(deviceId)) { 158 throw new IllegalArgumentException( 159 "The battery listener for pid " + pid 160 + " is already monitoring deviceId " + deviceId); 161 } 162 163 DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 164 if (monitor == null) { 165 // This is the first listener that is monitoring this device. 166 monitor = new DeviceMonitor(deviceId); 167 mDeviceMonitors.put(deviceId, monitor); 168 updateBluetoothBatteryMonitoring(); 169 } 170 171 if (DEBUG) { 172 Slog.d(TAG, "Battery listener for pid " + pid 173 + " is monitoring deviceId " + deviceId); 174 } 175 176 updatePollingLocked(true /*delayStart*/); 177 notifyBatteryListener(listenerRecord, monitor.getBatteryStateForReporting()); 178 } 179 } 180 notifyBatteryListener(ListenerRecord listenerRecord, State state)181 private static void notifyBatteryListener(ListenerRecord listenerRecord, State state) { 182 try { 183 listenerRecord.mListener.onBatteryStateChanged(state); 184 } catch (RemoteException e) { 185 Slog.e(TAG, "Failed to notify listener", e); 186 } 187 if (DEBUG) { 188 Slog.d(TAG, "Notified battery listener from pid " + listenerRecord.mPid 189 + " of state of deviceId " + state.deviceId); 190 } 191 } 192 notifyAllListenersForDevice(State state)193 private void notifyAllListenersForDevice(State state) { 194 synchronized (mLock) { 195 if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state); 196 mListenerRecords.forEach((pid, listenerRecord) -> { 197 if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) { 198 notifyBatteryListener(listenerRecord, state); 199 } 200 }); 201 } 202 } 203 204 @GuardedBy("mLock") updatePollingLocked(boolean delayStart)205 private void updatePollingLocked(boolean delayStart) { 206 if (!mIsInteractive || !anyOf(mDeviceMonitors, DeviceMonitor::requiresPolling)) { 207 // Stop polling. 208 mIsPolling = false; 209 mHandler.removeCallbacks(this::handlePollEvent); 210 return; 211 } 212 213 if (mIsPolling) { 214 return; 215 } 216 // Start polling. 217 mIsPolling = true; 218 mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0); 219 } 220 processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func)221 private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) { 222 final InputDevice device = 223 Objects.requireNonNull(mContext.getSystemService(InputManager.class)) 224 .getInputDevice(deviceId); 225 return device == null ? defaultValue : func.apply(device); 226 } 227 getInputDeviceName(int deviceId)228 private String getInputDeviceName(int deviceId) { 229 return processInputDevice(deviceId, "<none>" /*defaultValue*/, InputDevice::getName); 230 } 231 hasBattery(int deviceId)232 private boolean hasBattery(int deviceId) { 233 return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::hasBattery); 234 } 235 isUsiDevice(int deviceId)236 private boolean isUsiDevice(int deviceId) { 237 return processInputDevice(deviceId, false /*defaultValue*/, 238 (device) -> device.getHostUsiVersion() != null); 239 } 240 241 @Nullable getBluetoothDevice(int inputDeviceId)242 private BluetoothDevice getBluetoothDevice(int inputDeviceId) { 243 return getBluetoothDevice(mContext, 244 processInputDevice(inputDeviceId, null /*defaultValue*/, 245 InputDevice::getBluetoothAddress)); 246 } 247 248 @Nullable getBluetoothDevice(Context context, String address)249 private static BluetoothDevice getBluetoothDevice(Context context, String address) { 250 if (address == null) return null; 251 final BluetoothAdapter adapter = 252 Objects.requireNonNull(context.getSystemService(BluetoothManager.class)) 253 .getAdapter(); 254 return adapter.getRemoteDevice(address); 255 } 256 257 @GuardedBy("mLock") getDeviceMonitorOrThrowLocked(int deviceId)258 private DeviceMonitor getDeviceMonitorOrThrowLocked(int deviceId) { 259 return Objects.requireNonNull(mDeviceMonitors.get(deviceId), 260 "Maps are out of sync: Cannot find device state for deviceId " + deviceId); 261 } 262 263 /** 264 * Unregister the battery listener for the given input device and stop monitoring its battery 265 * state. If there are no other input devices that this listener is monitoring, the listener is 266 * removed. 267 */ 268 @BinderThread unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener, int pid)269 public void unregisterBatteryListener(int deviceId, 270 @NonNull IInputDeviceBatteryListener listener, int pid) { 271 synchronized (mLock) { 272 final ListenerRecord listenerRecord = mListenerRecords.get(pid); 273 if (listenerRecord == null) { 274 throw new IllegalArgumentException( 275 "Cannot unregister battery callback: No listener registered for pid " 276 + pid); 277 } 278 279 if (listenerRecord.mListener.asBinder() != listener.asBinder()) { 280 throw new IllegalArgumentException( 281 "Cannot unregister battery callback: The listener is not the one that " 282 + "is registered for pid " 283 + pid); 284 } 285 286 if (!listenerRecord.mMonitoredDevices.contains(deviceId)) { 287 throw new IllegalArgumentException( 288 "Cannot unregister battery callback: The device is not being " 289 + "monitored for deviceId " + deviceId); 290 } 291 292 unregisterRecordLocked(listenerRecord, deviceId); 293 } 294 } 295 296 @GuardedBy("mLock") unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId)297 private void unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId) { 298 final int pid = listenerRecord.mPid; 299 300 if (!listenerRecord.mMonitoredDevices.remove(deviceId)) { 301 throw new IllegalStateException("Cannot unregister battery callback: The deviceId " 302 + deviceId 303 + " is not being monitored by pid " 304 + pid); 305 } 306 307 if (!hasRegisteredListenerForDeviceLocked(deviceId)) { 308 // There are no more listeners monitoring this device. 309 final DeviceMonitor monitor = getDeviceMonitorOrThrowLocked(deviceId); 310 if (!monitor.isPersistent()) { 311 monitor.onMonitorDestroy(); 312 mDeviceMonitors.remove(deviceId); 313 } 314 } 315 316 if (listenerRecord.mMonitoredDevices.isEmpty()) { 317 // There are no more devices being monitored by this listener. 318 listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0); 319 mListenerRecords.remove(pid); 320 if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid); 321 } 322 323 updatePollingLocked(false /*delayStart*/); 324 } 325 326 @GuardedBy("mLock") hasRegisteredListenerForDeviceLocked(int deviceId)327 private boolean hasRegisteredListenerForDeviceLocked(int deviceId) { 328 for (int i = 0; i < mListenerRecords.size(); i++) { 329 if (mListenerRecords.valueAt(i).mMonitoredDevices.contains(deviceId)) { 330 return true; 331 } 332 } 333 return false; 334 } 335 handleListeningProcessDied(int pid)336 private void handleListeningProcessDied(int pid) { 337 synchronized (mLock) { 338 final ListenerRecord listenerRecord = mListenerRecords.get(pid); 339 if (listenerRecord == null) { 340 return; 341 } 342 if (DEBUG) { 343 Slog.d(TAG, 344 "Removing battery listener for pid " + pid + " because the process died"); 345 } 346 for (final int deviceId : listenerRecord.mMonitoredDevices) { 347 unregisterRecordLocked(listenerRecord, deviceId); 348 } 349 } 350 } 351 handleUEventNotification(int deviceId, long eventTime)352 private void handleUEventNotification(int deviceId, long eventTime) { 353 synchronized (mLock) { 354 final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 355 if (monitor == null) { 356 return; 357 } 358 monitor.onUEvent(eventTime); 359 } 360 } 361 handlePollEvent()362 private void handlePollEvent() { 363 synchronized (mLock) { 364 if (!mIsPolling) { 365 return; 366 } 367 final long eventTime = SystemClock.uptimeMillis(); 368 mDeviceMonitors.forEach((deviceId, monitor) -> monitor.onPoll(eventTime)); 369 mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS); 370 } 371 } 372 handleMonitorTimeout(int deviceId)373 private void handleMonitorTimeout(int deviceId) { 374 synchronized (mLock) { 375 final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 376 if (monitor == null) { 377 return; 378 } 379 final long updateTime = SystemClock.uptimeMillis(); 380 monitor.onTimeout(updateTime); 381 } 382 } 383 handleBluetoothBatteryLevelChange(long eventTime, String address, int batteryLevel)384 private void handleBluetoothBatteryLevelChange(long eventTime, String address, 385 int batteryLevel) { 386 synchronized (mLock) { 387 final DeviceMonitor monitor = findIf(mDeviceMonitors, (m) -> 388 (m.mBluetoothDevice != null 389 && address.equals(m.mBluetoothDevice.getAddress()))); 390 if (monitor != null) { 391 monitor.onBluetoothBatteryChanged(eventTime, batteryLevel); 392 } 393 } 394 } 395 handleBluetoothMetadataChange(@onNull BluetoothDevice device, int key, @Nullable byte[] value)396 private void handleBluetoothMetadataChange(@NonNull BluetoothDevice device, int key, 397 @Nullable byte[] value) { 398 synchronized (mLock) { 399 final DeviceMonitor monitor = 400 findIf(mDeviceMonitors, (m) -> device.equals(m.mBluetoothDevice)); 401 if (monitor != null) { 402 final long eventTime = SystemClock.uptimeMillis(); 403 monitor.onBluetoothMetadataChanged(eventTime, key, value); 404 } 405 } 406 } 407 408 /** Gets the current battery state of an input device. */ getBatteryState(int deviceId)409 public IInputDeviceBatteryState getBatteryState(int deviceId) { 410 synchronized (mLock) { 411 final long updateTime = SystemClock.uptimeMillis(); 412 final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 413 if (monitor == null) { 414 // The input device's battery is not being monitored by any listener. 415 return queryBatteryStateFromNative(deviceId, updateTime, hasBattery(deviceId)); 416 } 417 // Force the battery state to update, and notify listeners if necessary. 418 monitor.onPoll(updateTime); 419 return monitor.getBatteryStateForReporting(); 420 } 421 } 422 onInteractiveChanged(boolean interactive)423 public void onInteractiveChanged(boolean interactive) { 424 synchronized (mLock) { 425 mIsInteractive = interactive; 426 updatePollingLocked(false /*delayStart*/); 427 } 428 } 429 notifyStylusGestureStarted(int deviceId, long eventTime)430 public void notifyStylusGestureStarted(int deviceId, long eventTime) { 431 synchronized (mLock) { 432 final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 433 if (monitor == null) { 434 return; 435 } 436 437 monitor.onStylusGestureStarted(eventTime); 438 } 439 } 440 dump(PrintWriter pw)441 public void dump(PrintWriter pw) { 442 IndentingPrintWriter ipw = new IndentingPrintWriter(pw); 443 synchronized (mLock) { 444 ipw.println(TAG + ":"); 445 ipw.increaseIndent(); 446 ipw.println("State: Polling = " + mIsPolling 447 + ", Interactive = " + mIsInteractive); 448 449 ipw.println("Listeners: " + mListenerRecords.size() + " battery listeners"); 450 ipw.increaseIndent(); 451 for (int i = 0; i < mListenerRecords.size(); i++) { 452 ipw.println(i + ": " + mListenerRecords.valueAt(i)); 453 } 454 ipw.decreaseIndent(); 455 456 ipw.println("Device Monitors: " + mDeviceMonitors.size() + " monitors"); 457 ipw.increaseIndent(); 458 for (int i = 0; i < mDeviceMonitors.size(); i++) { 459 ipw.println(i + ": " + mDeviceMonitors.valueAt(i)); 460 } 461 ipw.decreaseIndent(); 462 ipw.decreaseIndent(); 463 } 464 } 465 466 @SuppressWarnings("all") monitor()467 public void monitor() { 468 synchronized (mLock) { 469 return; 470 } 471 } 472 473 private final InputManager.InputDeviceListener mInputDeviceListener = 474 new InputManager.InputDeviceListener() { 475 @Override 476 public void onInputDeviceAdded(int deviceId) { 477 synchronized (mLock) { 478 if (isUsiDevice(deviceId) && !mDeviceMonitors.containsKey(deviceId)) { 479 // Start monitoring USI device immediately. 480 mDeviceMonitors.put(deviceId, new UsiDeviceMonitor(deviceId)); 481 } 482 } 483 } 484 485 @Override 486 public void onInputDeviceRemoved(int deviceId) {} 487 488 @Override 489 public void onInputDeviceChanged(int deviceId) { 490 synchronized (mLock) { 491 final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); 492 if (monitor == null) { 493 return; 494 } 495 final long eventTime = SystemClock.uptimeMillis(); 496 monitor.onConfiguration(eventTime); 497 } 498 } 499 }; 500 501 // A record of a registered battery listener from one process. 502 private class ListenerRecord { 503 public final int mPid; 504 public final IInputDeviceBatteryListener mListener; 505 public final IBinder.DeathRecipient mDeathRecipient; 506 // The set of deviceIds that are currently being monitored by this listener. 507 public final Set<Integer> mMonitoredDevices; 508 ListenerRecord(int pid, IInputDeviceBatteryListener listener)509 ListenerRecord(int pid, IInputDeviceBatteryListener listener) { 510 mPid = pid; 511 mListener = listener; 512 mMonitoredDevices = new ArraySet<>(); 513 mDeathRecipient = () -> handleListeningProcessDied(pid); 514 } 515 516 @Override toString()517 public String toString() { 518 return "pid=" + mPid 519 + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray()); 520 } 521 } 522 523 // Queries the battery state of an input device from native code. queryBatteryStateFromNative(int deviceId, long updateTime, boolean isPresent)524 private State queryBatteryStateFromNative(int deviceId, long updateTime, boolean isPresent) { 525 return new State( 526 deviceId, 527 updateTime, 528 isPresent, 529 isPresent ? mNative.getBatteryStatus(deviceId) : BatteryState.STATUS_UNKNOWN, 530 isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN); 531 } 532 updateBluetoothBatteryMonitoring()533 private void updateBluetoothBatteryMonitoring() { 534 synchronized (mLock) { 535 if (anyOf(mDeviceMonitors, (m) -> m.mBluetoothDevice != null)) { 536 // At least one input device being monitored is connected over Bluetooth. 537 if (mBluetoothBatteryListener == null) { 538 if (DEBUG) Slog.d(TAG, "Registering bluetooth battery listener"); 539 mBluetoothBatteryListener = this::handleBluetoothBatteryLevelChange; 540 mBluetoothBatteryManager.addBatteryListener(mBluetoothBatteryListener); 541 } 542 } else if (mBluetoothBatteryListener != null) { 543 // No Bluetooth input devices are monitored, so remove the registered listener. 544 if (DEBUG) Slog.d(TAG, "Unregistering bluetooth battery listener"); 545 mBluetoothBatteryManager.removeBatteryListener(mBluetoothBatteryListener); 546 mBluetoothBatteryListener = null; 547 } 548 } 549 } 550 551 // Holds the state of an InputDevice for which battery changes are currently being monitored. 552 private class DeviceMonitor { 553 protected final State mState; 554 // Represents whether the input device has a sysfs battery node. 555 protected boolean mHasBattery = false; 556 557 @Nullable 558 private BluetoothDevice mBluetoothDevice; 559 long mBluetoothEventTime = 0; 560 // The battery level reported by the Bluetooth Hands-Free Profile (HPF) obtained through 561 // BluetoothDevice#getBatteryLevel(). 562 int mBluetoothBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 563 // The battery level and status reported through the Bluetooth device's metadata. 564 int mBluetoothMetadataBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 565 int mBluetoothMetadataBatteryStatus = BatteryState.STATUS_UNKNOWN; 566 @Nullable 567 private BluetoothAdapter.OnMetadataChangedListener mBluetoothMetadataListener; 568 569 @Nullable 570 private BatteryController.UEventBatteryListener mUEventBatteryListener; 571 DeviceMonitor(int deviceId)572 DeviceMonitor(int deviceId) { 573 mState = new State(deviceId); 574 575 // Load the initial battery state and start monitoring. 576 final long eventTime = SystemClock.uptimeMillis(); 577 configureDeviceMonitor(eventTime); 578 } 579 processChangesAndNotify(long eventTime, Consumer<Long> changes)580 protected void processChangesAndNotify(long eventTime, Consumer<Long> changes) { 581 final State oldState = getBatteryStateForReporting(); 582 changes.accept(eventTime); 583 final State newState = getBatteryStateForReporting(); 584 if (!oldState.equalsIgnoringUpdateTime(newState)) { 585 notifyAllListenersForDevice(newState); 586 } 587 } 588 onConfiguration(long eventTime)589 public void onConfiguration(long eventTime) { 590 processChangesAndNotify(eventTime, this::configureDeviceMonitor); 591 } 592 configureDeviceMonitor(long eventTime)593 private void configureDeviceMonitor(long eventTime) { 594 final int deviceId = mState.deviceId; 595 if (mHasBattery != hasBattery(mState.deviceId)) { 596 mHasBattery = !mHasBattery; 597 if (mHasBattery) { 598 startNativeMonitoring(); 599 } else { 600 stopNativeMonitoring(); 601 } 602 updateBatteryStateFromNative(eventTime); 603 } 604 605 final BluetoothDevice bluetoothDevice = getBluetoothDevice(deviceId); 606 if (!Objects.equals(mBluetoothDevice, bluetoothDevice)) { 607 if (DEBUG) { 608 Slog.d(TAG, "Bluetooth device is now " 609 + ((bluetoothDevice != null) ? "" : "not") 610 + " present for deviceId " + deviceId); 611 } 612 613 mBluetoothBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 614 stopBluetoothMetadataMonitoring(); 615 616 mBluetoothDevice = bluetoothDevice; 617 updateBluetoothBatteryMonitoring(); 618 619 if (mBluetoothDevice != null) { 620 mBluetoothBatteryLevel = mBluetoothBatteryManager.getBatteryLevel( 621 mBluetoothDevice.getAddress()); 622 startBluetoothMetadataMonitoring(eventTime); 623 } 624 } 625 } 626 startNativeMonitoring()627 private void startNativeMonitoring() { 628 final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId); 629 if (batteryPath == null) { 630 return; 631 } 632 final int deviceId = mState.deviceId; 633 mUEventBatteryListener = new BatteryController.UEventBatteryListener() { 634 @Override 635 public void onBatteryUEvent(long eventTime) { 636 handleUEventNotification(deviceId, eventTime); 637 } 638 }; 639 mUEventManager.addListener( 640 mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath)); 641 } 642 formatDevPath(@onNull String path)643 private String formatDevPath(@NonNull String path) { 644 // Remove the "/sys" prefix if it has one. 645 return path.startsWith("/sys") ? path.substring(4) : path; 646 } 647 stopNativeMonitoring()648 private void stopNativeMonitoring() { 649 if (mUEventBatteryListener != null) { 650 mUEventManager.removeListener(mUEventBatteryListener); 651 mUEventBatteryListener = null; 652 } 653 } 654 startBluetoothMetadataMonitoring(long eventTime)655 private void startBluetoothMetadataMonitoring(long eventTime) { 656 Objects.requireNonNull(mBluetoothDevice); 657 658 mBluetoothMetadataListener = BatteryController.this::handleBluetoothMetadataChange; 659 mBluetoothBatteryManager.addMetadataListener(mBluetoothDevice.getAddress(), 660 mBluetoothMetadataListener); 661 updateBluetoothMetadataState(eventTime, BluetoothDevice.METADATA_MAIN_BATTERY, 662 mBluetoothBatteryManager.getMetadata(mBluetoothDevice.getAddress(), 663 BluetoothDevice.METADATA_MAIN_BATTERY)); 664 updateBluetoothMetadataState(eventTime, BluetoothDevice.METADATA_MAIN_CHARGING, 665 mBluetoothBatteryManager.getMetadata(mBluetoothDevice.getAddress(), 666 BluetoothDevice.METADATA_MAIN_CHARGING)); 667 } 668 stopBluetoothMetadataMonitoring()669 private void stopBluetoothMetadataMonitoring() { 670 if (mBluetoothMetadataListener == null) { 671 return; 672 } 673 Objects.requireNonNull(mBluetoothDevice); 674 675 mBluetoothBatteryManager.removeMetadataListener( 676 mBluetoothDevice.getAddress(), mBluetoothMetadataListener); 677 mBluetoothMetadataListener = null; 678 mBluetoothMetadataBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 679 mBluetoothMetadataBatteryStatus = BatteryState.STATUS_UNKNOWN; 680 } 681 682 // This must be called when the device is no longer being monitored. onMonitorDestroy()683 public void onMonitorDestroy() { 684 stopNativeMonitoring(); 685 stopBluetoothMetadataMonitoring(); 686 mBluetoothDevice = null; 687 updateBluetoothBatteryMonitoring(); 688 } 689 updateBatteryStateFromNative(long eventTime)690 protected void updateBatteryStateFromNative(long eventTime) { 691 mState.updateIfChanged( 692 queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery)); 693 } 694 onPoll(long eventTime)695 public void onPoll(long eventTime) { 696 processChangesAndNotify(eventTime, this::updateBatteryStateFromNative); 697 } 698 onUEvent(long eventTime)699 public void onUEvent(long eventTime) { 700 processChangesAndNotify(eventTime, this::updateBatteryStateFromNative); 701 } 702 onBluetoothBatteryChanged(long eventTime, int bluetoothBatteryLevel)703 public void onBluetoothBatteryChanged(long eventTime, int bluetoothBatteryLevel) { 704 processChangesAndNotify(eventTime, (time) -> { 705 mBluetoothBatteryLevel = bluetoothBatteryLevel; 706 mBluetoothEventTime = time; 707 }); 708 } 709 onBluetoothMetadataChanged(long eventTime, int key, @Nullable byte[] value)710 public void onBluetoothMetadataChanged(long eventTime, int key, @Nullable byte[] value) { 711 processChangesAndNotify(eventTime, 712 (time) -> updateBluetoothMetadataState(time, key, value)); 713 } 714 updateBluetoothMetadataState(long eventTime, int key, @Nullable byte[] value)715 private void updateBluetoothMetadataState(long eventTime, int key, 716 @Nullable byte[] value) { 717 switch (key) { 718 case BluetoothDevice.METADATA_MAIN_BATTERY: 719 mBluetoothEventTime = eventTime; 720 mBluetoothMetadataBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 721 if (value != null) { 722 try { 723 mBluetoothMetadataBatteryLevel = Integer.parseInt( 724 new String(value)); 725 } catch (NumberFormatException e) { 726 Slog.wtf(TAG, 727 "Failed to parse bluetooth METADATA_MAIN_BATTERY with " 728 + "value '" 729 + new String(value) + "' for device " 730 + mBluetoothDevice); 731 } 732 } 733 break; 734 case BluetoothDevice.METADATA_MAIN_CHARGING: 735 mBluetoothEventTime = eventTime; 736 if (value != null) { 737 mBluetoothMetadataBatteryStatus = Boolean.parseBoolean( 738 new String(value)) 739 ? BatteryState.STATUS_CHARGING 740 : BatteryState.STATUS_DISCHARGING; 741 } else { 742 mBluetoothMetadataBatteryStatus = BatteryState.STATUS_UNKNOWN; 743 } 744 break; 745 default: 746 break; 747 } 748 } 749 requiresPolling()750 public boolean requiresPolling() { 751 return true; 752 } 753 isPersistent()754 public boolean isPersistent() { 755 return false; 756 } 757 onTimeout(long eventTime)758 public void onTimeout(long eventTime) {} 759 onStylusGestureStarted(long eventTime)760 public void onStylusGestureStarted(long eventTime) {} 761 762 // Returns the current battery state that can be used to notify listeners BatteryController. getBatteryStateForReporting()763 public State getBatteryStateForReporting() { 764 // Give precedence to the Bluetooth battery state, and fall back to the native state. 765 return Objects.requireNonNullElseGet(resolveBluetoothBatteryState(), 766 () -> new State(mState)); 767 } 768 769 @Nullable resolveBluetoothBatteryState()770 protected State resolveBluetoothBatteryState() { 771 final int level; 772 // Prefer battery level obtained from the metadata over the Bluetooth Hands-Free 773 // Profile (HFP). 774 if (mBluetoothMetadataBatteryLevel >= 0 && mBluetoothMetadataBatteryLevel <= 100) { 775 level = mBluetoothMetadataBatteryLevel; 776 } else if (mBluetoothBatteryLevel >= 0 && mBluetoothBatteryLevel <= 100) { 777 level = mBluetoothBatteryLevel; 778 } else { 779 return null; 780 } 781 return new State(mState.deviceId, mBluetoothEventTime, true, 782 mBluetoothMetadataBatteryStatus, level / 100.f); 783 } 784 785 @Override toString()786 public String toString() { 787 return "DeviceId=" + mState.deviceId 788 + ", Name='" + getInputDeviceName(mState.deviceId) + "'" 789 + ", NativeBattery=" + mState 790 + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none") 791 + ", BluetoothState=" + resolveBluetoothBatteryState(); 792 } 793 } 794 795 // Battery monitoring logic that is specific to stylus devices that support the 796 // Universal Stylus Initiative (USI) protocol. 797 private class UsiDeviceMonitor extends DeviceMonitor { 798 799 // For USI devices, we only treat the battery state as valid for a fixed amount of time 800 // after receiving a battery update. Once the timeout has passed, we signal to all listeners 801 // that there is no longer a battery present for the device. The battery state is valid 802 // as long as this callback is non-null. 803 @Nullable 804 private Runnable mValidityTimeoutCallback; 805 UsiDeviceMonitor(int deviceId)806 UsiDeviceMonitor(int deviceId) { 807 super(deviceId); 808 } 809 810 @Override onPoll(long eventTime)811 public void onPoll(long eventTime) { 812 // Disregard polling for USI devices. 813 } 814 815 @Override onUEvent(long eventTime)816 public void onUEvent(long eventTime) { 817 processChangesAndNotify(eventTime, (time) -> { 818 updateBatteryStateFromNative(time); 819 markUsiBatteryValid(); 820 }); 821 } 822 823 @Override onStylusGestureStarted(long eventTime)824 public void onStylusGestureStarted(long eventTime) { 825 processChangesAndNotify(eventTime, (time) -> { 826 final boolean wasValid = mValidityTimeoutCallback != null; 827 if (!wasValid && mState.capacity == 0.f) { 828 // Handle a special case where the USI device reports a battery capacity of 0 829 // at boot until the first battery update. To avoid wrongly sending out a 830 // battery capacity of 0 if we detect stylus presence before the capacity 831 // is first updated, do not validate the battery state when the state is not 832 // valid and the capacity is 0. 833 return; 834 } 835 markUsiBatteryValid(); 836 }); 837 } 838 839 @Override onTimeout(long eventTime)840 public void onTimeout(long eventTime) { 841 processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid()); 842 } 843 844 @Override onConfiguration(long eventTime)845 public void onConfiguration(long eventTime) { 846 super.onConfiguration(eventTime); 847 848 if (!mHasBattery) { 849 throw new IllegalStateException( 850 "UsiDeviceMonitor: USI devices are always expected to " 851 + "report a valid battery, but no battery was detected!"); 852 } 853 } 854 markUsiBatteryValid()855 private void markUsiBatteryValid() { 856 if (mValidityTimeoutCallback != null) { 857 mHandler.removeCallbacks(mValidityTimeoutCallback); 858 } else { 859 final int deviceId = mState.deviceId; 860 mValidityTimeoutCallback = 861 () -> BatteryController.this.handleMonitorTimeout(deviceId); 862 } 863 mHandler.postDelayed(mValidityTimeoutCallback, USI_BATTERY_VALIDITY_DURATION_MILLIS); 864 } 865 markUsiBatteryInvalid()866 private void markUsiBatteryInvalid() { 867 if (mValidityTimeoutCallback == null) { 868 return; 869 } 870 mHandler.removeCallbacks(mValidityTimeoutCallback); 871 mValidityTimeoutCallback = null; 872 } 873 874 @Override getBatteryStateForReporting()875 public State getBatteryStateForReporting() { 876 // Give precedence to the Bluetooth battery state, and fall back to the native state. 877 return Objects.requireNonNullElseGet(resolveBluetoothBatteryState(), 878 () -> mValidityTimeoutCallback != null 879 ? new State(mState) : new State(mState.deviceId)); 880 } 881 882 @Override requiresPolling()883 public boolean requiresPolling() { 884 // Do not poll the battery state for USI devices. 885 return false; 886 } 887 888 @Override isPersistent()889 public boolean isPersistent() { 890 // Do not remove the battery monitor for USI devices. 891 return true; 892 } 893 894 @Override toString()895 public String toString() { 896 return super.toString() 897 + ", UsiStateIsValid=" + (mValidityTimeoutCallback != null); 898 } 899 } 900 901 @VisibleForTesting 902 abstract static class UEventBatteryListener extends UEventManager.UEventListener { 903 @Override onUEvent(UEventObserver.UEvent event)904 public void onUEvent(UEventObserver.UEvent event) { 905 final long eventTime = SystemClock.uptimeMillis(); 906 if (DEBUG) { 907 Slog.d(TAG, 908 "UEventListener: Received UEvent: " 909 + event + " eventTime: " + eventTime); 910 } 911 if (!"CHANGE".equalsIgnoreCase(event.get("ACTION")) 912 || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) { 913 // Disregard any UEvents that do not correspond to battery changes. 914 return; 915 } 916 UEventBatteryListener.this.onBatteryUEvent(eventTime); 917 } 918 onBatteryUEvent(long eventTime)919 public abstract void onBatteryUEvent(long eventTime); 920 } 921 922 // An interface used to change the API of adding a bluetooth battery listener to a more 923 // test-friendly format. 924 @VisibleForTesting 925 interface BluetoothBatteryManager { 926 @VisibleForTesting 927 interface BluetoothBatteryListener { onBluetoothBatteryChanged(long eventTime, String address, int batteryLevel)928 void onBluetoothBatteryChanged(long eventTime, String address, int batteryLevel); 929 } 930 // Methods used for obtaining the Bluetooth battery level through Bluetooth HFP. addBatteryListener(BluetoothBatteryListener listener)931 void addBatteryListener(BluetoothBatteryListener listener); removeBatteryListener(BluetoothBatteryListener listener)932 void removeBatteryListener(BluetoothBatteryListener listener); getBatteryLevel(String address)933 int getBatteryLevel(String address); 934 935 // Methods used for obtaining the battery level through Bluetooth metadata. addMetadataListener(String address, BluetoothAdapter.OnMetadataChangedListener listener)936 void addMetadataListener(String address, 937 BluetoothAdapter.OnMetadataChangedListener listener); removeMetadataListener(String address, BluetoothAdapter.OnMetadataChangedListener listener)938 void removeMetadataListener(String address, 939 BluetoothAdapter.OnMetadataChangedListener listener); getMetadata(String address, int key)940 byte[] getMetadata(String address, int key); 941 } 942 943 private static class LocalBluetoothBatteryManager implements BluetoothBatteryManager { 944 private final Context mContext; 945 private final Executor mExecutor; 946 @Nullable 947 @GuardedBy("mBroadcastReceiver") 948 private BluetoothBatteryListener mRegisteredListener; 949 @GuardedBy("mBroadcastReceiver") 950 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 951 @Override 952 public void onReceive(Context context, Intent intent) { 953 if (!BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED.equals(intent.getAction())) { 954 return; 955 } 956 final BluetoothDevice bluetoothDevice = intent.getParcelableExtra( 957 BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); 958 if (bluetoothDevice == null) { 959 return; 960 } 961 final int batteryLevel = intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, 962 BluetoothDevice.BATTERY_LEVEL_UNKNOWN); 963 synchronized (mBroadcastReceiver) { 964 if (mRegisteredListener != null) { 965 final long eventTime = SystemClock.uptimeMillis(); 966 mRegisteredListener.onBluetoothBatteryChanged( 967 eventTime, bluetoothDevice.getAddress(), batteryLevel); 968 } 969 } 970 } 971 }; 972 LocalBluetoothBatteryManager(Context context, Looper looper)973 LocalBluetoothBatteryManager(Context context, Looper looper) { 974 mContext = context; 975 mExecutor = new HandlerExecutor(new Handler(looper)); 976 } 977 978 @Override addBatteryListener(BluetoothBatteryListener listener)979 public void addBatteryListener(BluetoothBatteryListener listener) { 980 synchronized (mBroadcastReceiver) { 981 if (mRegisteredListener != null) { 982 throw new IllegalStateException( 983 "Only one bluetooth battery listener can be registered at once."); 984 } 985 mRegisteredListener = listener; 986 mContext.registerReceiver(mBroadcastReceiver, 987 new IntentFilter(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED)); 988 } 989 } 990 991 @Override removeBatteryListener(BluetoothBatteryListener listener)992 public void removeBatteryListener(BluetoothBatteryListener listener) { 993 synchronized (mBroadcastReceiver) { 994 if (!listener.equals(mRegisteredListener)) { 995 throw new IllegalStateException("Listener is not registered."); 996 } 997 mRegisteredListener = null; 998 mContext.unregisterReceiver(mBroadcastReceiver); 999 } 1000 } 1001 1002 @Override getBatteryLevel(String address)1003 public int getBatteryLevel(String address) { 1004 return getBluetoothDevice(mContext, address).getBatteryLevel(); 1005 } 1006 1007 @Override addMetadataListener(String address, BluetoothAdapter.OnMetadataChangedListener listener)1008 public void addMetadataListener(String address, 1009 BluetoothAdapter.OnMetadataChangedListener listener) { 1010 Objects.requireNonNull(mContext.getSystemService(BluetoothManager.class)) 1011 .getAdapter().addOnMetadataChangedListener( 1012 getBluetoothDevice(mContext, address), mExecutor, 1013 listener); 1014 } 1015 1016 @Override removeMetadataListener(String address, BluetoothAdapter.OnMetadataChangedListener listener)1017 public void removeMetadataListener(String address, 1018 BluetoothAdapter.OnMetadataChangedListener listener) { 1019 Objects.requireNonNull(mContext.getSystemService(BluetoothManager.class)) 1020 .getAdapter().removeOnMetadataChangedListener( 1021 getBluetoothDevice(mContext, address), listener); 1022 } 1023 1024 @Override getMetadata(String address, int key)1025 public byte[] getMetadata(String address, int key) { 1026 return getBluetoothDevice(mContext, address).getMetadata(key); 1027 } 1028 } 1029 1030 // Helper class that adds copying and printing functionality to IInputDeviceBatteryState. 1031 private static class State extends IInputDeviceBatteryState { 1032 State(int deviceId)1033 State(int deviceId) { 1034 reset(deviceId); 1035 } 1036 State(IInputDeviceBatteryState s)1037 State(IInputDeviceBatteryState s) { 1038 copyFrom(s); 1039 } 1040 State(int deviceId, long updateTime, boolean isPresent, int status, float capacity)1041 State(int deviceId, long updateTime, boolean isPresent, int status, float capacity) { 1042 initialize(deviceId, updateTime, isPresent, status, capacity); 1043 } 1044 1045 // Updates this from other if there is a difference between them, ignoring the updateTime. updateIfChanged(IInputDeviceBatteryState other)1046 public void updateIfChanged(IInputDeviceBatteryState other) { 1047 if (!equalsIgnoringUpdateTime(other)) { 1048 copyFrom(other); 1049 } 1050 } 1051 reset(int deviceId)1052 public void reset(int deviceId) { 1053 initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN, 1054 Float.NaN /*capacity*/); 1055 } 1056 copyFrom(IInputDeviceBatteryState s)1057 private void copyFrom(IInputDeviceBatteryState s) { 1058 initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity); 1059 } 1060 initialize(int deviceId, long updateTime, boolean isPresent, int status, float capacity)1061 private void initialize(int deviceId, long updateTime, boolean isPresent, int status, 1062 float capacity) { 1063 this.deviceId = deviceId; 1064 this.updateTime = updateTime; 1065 this.isPresent = isPresent; 1066 this.status = status; 1067 this.capacity = capacity; 1068 } 1069 equalsIgnoringUpdateTime(IInputDeviceBatteryState other)1070 public boolean equalsIgnoringUpdateTime(IInputDeviceBatteryState other) { 1071 long updateTime = this.updateTime; 1072 this.updateTime = other.updateTime; 1073 boolean eq = this.equals(other); 1074 this.updateTime = updateTime; 1075 return eq; 1076 } 1077 1078 @Override toString()1079 public String toString() { 1080 if (!isPresent) { 1081 return "State{<not present>}"; 1082 } 1083 return "State{time=" + updateTime 1084 + ", isPresent=" + isPresent 1085 + ", status=" + status 1086 + ", capacity=" + capacity 1087 + "}"; 1088 } 1089 } 1090 1091 // Check if any value in an ArrayMap matches the predicate in an optimized way. anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test)1092 private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) { 1093 return findIf(arrayMap, test) != null; 1094 } 1095 1096 // Find the first value in an ArrayMap that matches the predicate in an optimized way. findIf(ArrayMap<K, V> arrayMap, Predicate<V> test)1097 private static <K, V> V findIf(ArrayMap<K, V> arrayMap, Predicate<V> test) { 1098 for (int i = 0; i < arrayMap.size(); i++) { 1099 final V value = arrayMap.valueAt(i); 1100 if (test.test(value)) { 1101 return value; 1102 } 1103 } 1104 return null; 1105 } 1106 } 1107