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