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.hardware.health.Translate.h2aTranslate; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.hardware.health.HealthInfo; 24 import android.hardware.health.V2_0.IHealth; 25 import android.hardware.health.V2_0.Result; 26 import android.hidl.manager.V1_0.IServiceManager; 27 import android.hidl.manager.V1_0.IServiceNotification; 28 import android.os.BatteryManager; 29 import android.os.BatteryProperty; 30 import android.os.HandlerThread; 31 import android.os.RemoteException; 32 import android.os.Trace; 33 import android.util.MutableInt; 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 HIDL HAL. 44 * 45 * @hide 46 */ 47 final class HealthServiceWrapperHidl extends HealthServiceWrapper { 48 private static final String TAG = "HealthServiceWrapperHidl"; 49 public static final String INSTANCE_VENDOR = "default"; 50 51 private final IServiceNotification mNotification = new Notification(); 52 private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder"); 53 // These variables are fixed after init. 54 private Callback mCallback; 55 private IHealthSupplier mHealthSupplier; 56 private String mInstanceName; 57 58 // Last IHealth service received. 59 private final AtomicReference<IHealth> mLastService = new AtomicReference<>(); 60 traceBegin(String name)61 private static void traceBegin(String name) { 62 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name); 63 } 64 traceEnd()65 private static void traceEnd() { 66 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 67 } 68 69 @Override getProperty(int id, final BatteryProperty prop)70 public int getProperty(int id, final BatteryProperty prop) throws RemoteException { 71 traceBegin("HealthGetProperty"); 72 try { 73 IHealth service = mLastService.get(); 74 if (service == null) throw new RemoteException("no health service"); 75 final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED); 76 switch (id) { 77 case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: 78 service.getChargeCounter( 79 (int result, int value) -> { 80 outResult.value = result; 81 if (result == Result.SUCCESS) prop.setLong(value); 82 }); 83 break; 84 case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW: 85 service.getCurrentNow( 86 (int result, int value) -> { 87 outResult.value = result; 88 if (result == Result.SUCCESS) prop.setLong(value); 89 }); 90 break; 91 case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE: 92 service.getCurrentAverage( 93 (int result, int value) -> { 94 outResult.value = result; 95 if (result == Result.SUCCESS) prop.setLong(value); 96 }); 97 break; 98 case BatteryManager.BATTERY_PROPERTY_CAPACITY: 99 service.getCapacity( 100 (int result, int value) -> { 101 outResult.value = result; 102 if (result == Result.SUCCESS) prop.setLong(value); 103 }); 104 break; 105 case BatteryManager.BATTERY_PROPERTY_STATUS: 106 service.getChargeStatus( 107 (int result, int value) -> { 108 outResult.value = result; 109 if (result == Result.SUCCESS) prop.setLong(value); 110 }); 111 break; 112 case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER: 113 service.getEnergyCounter( 114 (int result, long value) -> { 115 outResult.value = result; 116 if (result == Result.SUCCESS) prop.setLong(value); 117 }); 118 break; 119 } 120 return outResult.value; 121 } finally { 122 traceEnd(); 123 } 124 } 125 126 @Override scheduleUpdate()127 public void scheduleUpdate() throws RemoteException { 128 getHandlerThread() 129 .getThreadHandler() 130 .post( 131 () -> { 132 traceBegin("HealthScheduleUpdate"); 133 try { 134 IHealth service = mLastService.get(); 135 if (service == null) { 136 Slog.e(TAG, "no health service"); 137 return; 138 } 139 service.update(); 140 } catch (RemoteException ex) { 141 Slog.e(TAG, "Cannot call update on health HAL", ex); 142 } finally { 143 traceEnd(); 144 } 145 }); 146 } 147 148 private static class Mutable<T> { 149 public T value; 150 } 151 152 @Override getHealthInfo()153 public HealthInfo getHealthInfo() throws RemoteException { 154 IHealth service = mLastService.get(); 155 if (service == null) return null; 156 final Mutable<HealthInfo> ret = new Mutable<>(); 157 service.getHealthInfo( 158 (result, value) -> { 159 if (result == Result.SUCCESS) { 160 ret.value = h2aTranslate(value.legacy); 161 } 162 }); 163 return ret.value; 164 } 165 166 /** 167 * Start monitoring registration of new IHealth services. Only instance {@link #INSTANCE_VENDOR} 168 * and in device / framework manifest are used. This function should only be called once. 169 * 170 * <p>mCallback.onRegistration() is called synchronously (aka in init thread) before this method 171 * returns if callback is not null. 172 * 173 * @throws RemoteException transaction error when talking to IServiceManager 174 * @throws NoSuchElementException if one of the following cases: - No service manager; - {@link 175 * #INSTANCE_VENDOR} is not in manifests (i.e. not available on this device), or none of 176 * these instances are available to current process. 177 * @throws NullPointerException when supplier is null 178 */ 179 @VisibleForTesting HealthServiceWrapperHidl( @ullable Callback callback, @NonNull IServiceManagerSupplier managerSupplier, @NonNull IHealthSupplier healthSupplier)180 HealthServiceWrapperHidl( 181 @Nullable Callback callback, 182 @NonNull IServiceManagerSupplier managerSupplier, 183 @NonNull IHealthSupplier healthSupplier) 184 throws RemoteException, NoSuchElementException, NullPointerException { 185 if (managerSupplier == null || healthSupplier == null) { 186 throw new NullPointerException(); 187 } 188 mHealthSupplier = healthSupplier; 189 190 // Initialize mLastService and call callback for the first time (in init thread) 191 IHealth newService = null; 192 traceBegin("HealthInitGetService_" + INSTANCE_VENDOR); 193 try { 194 newService = healthSupplier.get(INSTANCE_VENDOR); 195 } catch (NoSuchElementException ex) { 196 /* ignored, handled below */ 197 } finally { 198 traceEnd(); 199 } 200 if (newService != null) { 201 mInstanceName = INSTANCE_VENDOR; 202 mLastService.set(newService); 203 } 204 205 if (mInstanceName == null || newService == null) { 206 throw new NoSuchElementException( 207 String.format( 208 "IHealth service instance %s isn't available. Perhaps no permission?", 209 INSTANCE_VENDOR)); 210 } 211 212 if (callback != null) { 213 mCallback = callback; 214 mCallback.onRegistration(null, newService, mInstanceName); 215 } 216 217 // Register for future service registrations 218 traceBegin("HealthInitRegisterNotification"); 219 mHandlerThread.start(); 220 try { 221 managerSupplier 222 .get() 223 .registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification); 224 } finally { 225 traceEnd(); 226 } 227 Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName); 228 } 229 230 @VisibleForTesting getHandlerThread()231 public HandlerThread getHandlerThread() { 232 return mHandlerThread; 233 } 234 235 /** Service registration callback. */ 236 interface Callback { 237 /** 238 * This function is invoked asynchronously when a new and related IServiceNotification is 239 * received. 240 * 241 * @param service the recently retrieved service from IServiceManager. Can be a dead service 242 * before service notification of a new service is delivered. Implementation must handle 243 * cases for {@link RemoteException}s when calling into service. 244 * @param instance instance name. 245 */ onRegistration(IHealth oldService, IHealth newService, String instance)246 void onRegistration(IHealth oldService, IHealth newService, String instance); 247 } 248 249 /** 250 * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service 251 * is not available. 252 */ 253 interface IServiceManagerSupplier { get()254 default IServiceManager get() throws NoSuchElementException, RemoteException { 255 return IServiceManager.getService(); 256 } 257 } 258 259 /** 260 * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service 261 * is not available. 262 */ 263 interface IHealthSupplier { get(String name)264 default IHealth get(String name) throws NoSuchElementException, RemoteException { 265 return IHealth.getService(name, true /* retry */); 266 } 267 } 268 269 private class Notification extends IServiceNotification.Stub { 270 @Override onRegistration( String interfaceName, String instanceName, boolean preexisting)271 public final void onRegistration( 272 String interfaceName, String instanceName, boolean preexisting) { 273 if (!IHealth.kInterfaceName.equals(interfaceName)) return; 274 if (!mInstanceName.equals(instanceName)) return; 275 276 // This runnable only runs on mHandlerThread and ordering is ensured, hence 277 // no locking is needed inside the runnable. 278 mHandlerThread 279 .getThreadHandler() 280 .post( 281 new Runnable() { 282 @Override 283 public void run() { 284 try { 285 IHealth newService = mHealthSupplier.get(mInstanceName); 286 IHealth oldService = mLastService.getAndSet(newService); 287 288 // preexisting may be inaccurate (race). Check for equality 289 // here. 290 if (Objects.equals(newService, oldService)) return; 291 292 Slog.i( 293 TAG, 294 "health: new instance registered " + mInstanceName); 295 // #init() may be called with null callback. Skip null 296 // callbacks. 297 if (mCallback == null) return; 298 mCallback.onRegistration( 299 oldService, newService, mInstanceName); 300 } catch (NoSuchElementException | RemoteException ex) { 301 Slog.e( 302 TAG, 303 "health: Cannot get instance '" 304 + mInstanceName 305 + "': " 306 + ex.getMessage() 307 + ". Perhaps no permission?"); 308 } 309 } 310 }); 311 } 312 } 313 } 314