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