1 /*
2  * Copyright (C) 2021 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.health;
18 
19 import static android.os.Flags.batteryPartStatusApi;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.hardware.health.BatteryHealthData;
24 import android.hardware.health.HealthInfo;
25 import android.hardware.health.IHealth;
26 import android.os.BatteryManager;
27 import android.os.BatteryProperty;
28 import android.os.Binder;
29 import android.os.HandlerThread;
30 import android.os.IBinder;
31 import android.os.IServiceCallback;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.ServiceSpecificException;
35 import android.os.Trace;
36 import android.util.Slog;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import java.util.NoSuchElementException;
41 import java.util.Objects;
42 import java.util.concurrent.atomic.AtomicReference;
43 
44 /**
45  * Implement {@link HealthServiceWrapper} backed by the AIDL HAL.
46  *
47  * @hide
48  */
49 class HealthServiceWrapperAidl extends HealthServiceWrapper {
50     private static final String TAG = "HealthServiceWrapperAidl";
51     @VisibleForTesting static final String SERVICE_NAME = IHealth.DESCRIPTOR + "/default";
52     private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceBinder");
53     private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
54     private final IServiceCallback mServiceCallback = new ServiceCallback();
55     private final HealthRegCallbackAidl mRegCallback;
56 
57     /** Stub interface into {@link ServiceManager} for testing. */
58     interface ServiceManagerStub {
waitForDeclaredService(@onNull String name)59         default @Nullable IHealth waitForDeclaredService(@NonNull String name) {
60             return IHealth.Stub.asInterface(ServiceManager.waitForDeclaredService(name));
61         }
62 
registerForNotifications( @onNull String name, @NonNull IServiceCallback callback)63         default void registerForNotifications(
64                 @NonNull String name, @NonNull IServiceCallback callback) throws RemoteException {
65             ServiceManager.registerForNotifications(name, callback);
66         }
67     }
68 
HealthServiceWrapperAidl( @ullable HealthRegCallbackAidl regCallback, @NonNull ServiceManagerStub serviceManager)69     HealthServiceWrapperAidl(
70             @Nullable HealthRegCallbackAidl regCallback, @NonNull ServiceManagerStub serviceManager)
71             throws RemoteException, NoSuchElementException {
72 
73         traceBegin("HealthInitGetServiceAidl");
74         IHealth newService;
75         try {
76             newService = serviceManager.waitForDeclaredService(SERVICE_NAME);
77         } finally {
78             traceEnd();
79         }
80         if (newService == null) {
81             throw new NoSuchElementException(
82                     "IHealth service instance isn't available. Perhaps no permission?");
83         }
84         mLastService.set(newService);
85         mRegCallback = regCallback;
86         if (mRegCallback != null) {
87             mRegCallback.onRegistration(null /* oldService */, newService);
88         }
89 
90         traceBegin("HealthInitRegisterNotificationAidl");
91         mHandlerThread.start();
92         try {
93             serviceManager.registerForNotifications(SERVICE_NAME, mServiceCallback);
94         } finally {
95             traceEnd();
96         }
97         Slog.i(TAG, "health: HealthServiceWrapper listening to AIDL HAL");
98     }
99 
100     @Override
101     @VisibleForTesting
getHandlerThread()102     public HandlerThread getHandlerThread() {
103         return mHandlerThread;
104     }
105 
106     @Override
getProperty(int id, BatteryProperty prop)107     public int getProperty(int id, BatteryProperty prop) throws RemoteException {
108         traceBegin("HealthGetPropertyAidl");
109         try {
110             return getPropertyInternal(id, prop);
111         } finally {
112             traceEnd();
113         }
114     }
115 
getPropertyInternal(int id, BatteryProperty prop)116     private int getPropertyInternal(int id, BatteryProperty prop) throws RemoteException {
117         IHealth service = mLastService.get();
118         if (service == null) throw new RemoteException("no health service");
119         BatteryHealthData healthData;
120         try {
121             switch (id) {
122                 case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
123                     prop.setLong(service.getChargeCounterUah());
124                     break;
125                 case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
126                     prop.setLong(service.getCurrentNowMicroamps());
127                     break;
128                 case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
129                     prop.setLong(service.getCurrentAverageMicroamps());
130                     break;
131                 case BatteryManager.BATTERY_PROPERTY_CAPACITY:
132                     prop.setLong(service.getCapacity());
133                     break;
134                 case BatteryManager.BATTERY_PROPERTY_STATUS:
135                     prop.setLong(service.getChargeStatus());
136                     break;
137                 case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
138                     prop.setLong(service.getEnergyCounterNwh());
139                     break;
140                 case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
141                     healthData = service.getBatteryHealthData();
142                     prop.setLong(healthData.batteryManufacturingDateSeconds);
143                     break;
144                 case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
145                     healthData = service.getBatteryHealthData();
146                     prop.setLong(healthData.batteryFirstUsageSeconds);
147                     break;
148                 case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
149                     prop.setLong(service.getChargingPolicy());
150                     break;
151                 case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
152                     healthData = service.getBatteryHealthData();
153                     prop.setLong(healthData.batteryStateOfHealth);
154                     break;
155                 case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
156                     if (batteryPartStatusApi()) {
157                         healthData = service.getBatteryHealthData();
158                         prop.setString(healthData.batterySerialNumber);
159                     }
160                     break;
161                 case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
162                     if (batteryPartStatusApi()) {
163                         healthData = service.getBatteryHealthData();
164                         prop.setLong(healthData.batteryPartStatus);
165                     }
166                     break;
167             }
168         } catch (UnsupportedOperationException e) {
169             // Leave prop untouched.
170             return -1;
171         } catch (ServiceSpecificException e) {
172             // Leave prop untouched.
173             return -2;
174         }
175         // throws RemoteException as-is. BatteryManager wraps it into a RuntimeException
176         // and throw it to apps.
177 
178         // If no error, return 0.
179         return 0;
180     }
181 
182     @Override
scheduleUpdate()183     public void scheduleUpdate() throws RemoteException {
184         getHandlerThread()
185                 .getThreadHandler()
186                 .post(
187                         () -> {
188                             traceBegin("HealthScheduleUpdate");
189                             try {
190                                 IHealth service = mLastService.get();
191                                 if (service == null) {
192                                     Slog.e(TAG, "no health service");
193                                     return;
194                                 }
195                                 service.update();
196                             } catch (RemoteException | ServiceSpecificException ex) {
197                                 Slog.e(TAG, "Cannot call update on health AIDL HAL", ex);
198                             } finally {
199                                 traceEnd();
200                             }
201                         });
202     }
203 
204     @Override
getHealthInfo()205     public HealthInfo getHealthInfo() throws RemoteException {
206         IHealth service = mLastService.get();
207         if (service == null) return null;
208         try {
209             return service.getHealthInfo();
210         } catch (UnsupportedOperationException | ServiceSpecificException ex) {
211             return null;
212         }
213     }
214 
setChargingPolicy(int policy)215     public void setChargingPolicy(int policy) throws RemoteException {
216         IHealth service = mLastService.get();
217         if (service == null) return;
218         try {
219             service.setChargingPolicy(policy);
220         } catch (UnsupportedOperationException | ServiceSpecificException ex) {
221             return;
222         }
223     }
224 
traceBegin(String name)225     private static void traceBegin(String name) {
226         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
227     }
228 
traceEnd()229     private static void traceEnd() {
230         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
231     }
232 
233     private class ServiceCallback extends IServiceCallback.Stub {
234         @Override
onRegistration(String name, @NonNull final IBinder newBinder)235         public void onRegistration(String name, @NonNull final IBinder newBinder)
236                 throws RemoteException {
237             if (!SERVICE_NAME.equals(name)) return;
238             // This runnable only runs on mHandlerThread and ordering is ensured, hence
239             // no locking is needed inside the runnable.
240             getHandlerThread()
241                     .getThreadHandler()
242                     .post(
243                             () -> {
244                                 IHealth newService =
245                                         IHealth.Stub.asInterface(Binder.allowBlocking(newBinder));
246                                 IHealth oldService = mLastService.getAndSet(newService);
247                                 IBinder oldBinder =
248                                         oldService != null ? oldService.asBinder() : null;
249                                 if (Objects.equals(newBinder, oldBinder)) return;
250 
251                                 Slog.i(TAG, "New health AIDL HAL service registered");
252                                 if (mRegCallback != null) {
253                                     mRegCallback.onRegistration(oldService, newService);
254                                 }
255                             });
256         }
257     }
258 }
259