blob: 1153cc37e3da4145cc5f176ac36dc88103870aae [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.health;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.health.BatteryHealthData;
import android.hardware.health.HealthInfo;
import android.hardware.health.IHealth;
import android.os.BatteryManager;
import android.os.BatteryProperty;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IServiceCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.Trace;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
* Implement {@link HealthServiceWrapper} backed by the AIDL HAL.
*
* @hide
*/
class HealthServiceWrapperAidl extends HealthServiceWrapper {
private static final String TAG = "HealthServiceWrapperAidl";
@VisibleForTesting static final String SERVICE_NAME = IHealth.DESCRIPTOR + "/default";
private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceBinder");
private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
private final IServiceCallback mServiceCallback = new ServiceCallback();
private final HealthRegCallbackAidl mRegCallback;
/** Stub interface into {@link ServiceManager} for testing. */
interface ServiceManagerStub {
default @Nullable IHealth waitForDeclaredService(@NonNull String name) {
return IHealth.Stub.asInterface(ServiceManager.waitForDeclaredService(name));
}
default void registerForNotifications(
@NonNull String name, @NonNull IServiceCallback callback) throws RemoteException {
ServiceManager.registerForNotifications(name, callback);
}
}
HealthServiceWrapperAidl(
@Nullable HealthRegCallbackAidl regCallback, @NonNull ServiceManagerStub serviceManager)
throws RemoteException, NoSuchElementException {
traceBegin("HealthInitGetServiceAidl");
IHealth newService;
try {
newService = serviceManager.waitForDeclaredService(SERVICE_NAME);
} finally {
traceEnd();
}
if (newService == null) {
throw new NoSuchElementException(
"IHealth service instance isn't available. Perhaps no permission?");
}
mLastService.set(newService);
mRegCallback = regCallback;
if (mRegCallback != null) {
mRegCallback.onRegistration(null /* oldService */, newService);
}
traceBegin("HealthInitRegisterNotificationAidl");
mHandlerThread.start();
try {
serviceManager.registerForNotifications(SERVICE_NAME, mServiceCallback);
} finally {
traceEnd();
}
Slog.i(TAG, "health: HealthServiceWrapper listening to AIDL HAL");
}
@Override
@VisibleForTesting
public HandlerThread getHandlerThread() {
return mHandlerThread;
}
@Override
public int getProperty(int id, BatteryProperty prop) throws RemoteException {
traceBegin("HealthGetPropertyAidl");
try {
return getPropertyInternal(id, prop);
} finally {
traceEnd();
}
}
private int getPropertyInternal(int id, BatteryProperty prop) throws RemoteException {
IHealth service = mLastService.get();
if (service == null) throw new RemoteException("no health service");
BatteryHealthData healthData;
try {
switch (id) {
case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
prop.setLong(service.getChargeCounterUah());
break;
case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
prop.setLong(service.getCurrentNowMicroamps());
break;
case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
prop.setLong(service.getCurrentAverageMicroamps());
break;
case BatteryManager.BATTERY_PROPERTY_CAPACITY:
prop.setLong(service.getCapacity());
break;
case BatteryManager.BATTERY_PROPERTY_STATUS:
prop.setLong(service.getChargeStatus());
break;
case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
prop.setLong(service.getEnergyCounterNwh());
break;
case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryManufacturingDateSeconds);
break;
case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryFirstUsageSeconds);
break;
case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
prop.setLong(service.getChargingPolicy());
break;
case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryStateOfHealth);
break;
}
} catch (UnsupportedOperationException e) {
// Leave prop untouched.
return -1;
} catch (ServiceSpecificException e) {
// Leave prop untouched.
return -2;
}
// throws RemoteException as-is. BatteryManager wraps it into a RuntimeException
// and throw it to apps.
// If no error, return 0.
return 0;
}
@Override
public void scheduleUpdate() throws RemoteException {
getHandlerThread()
.getThreadHandler()
.post(
() -> {
traceBegin("HealthScheduleUpdate");
try {
IHealth service = mLastService.get();
if (service == null) {
Slog.e(TAG, "no health service");
return;
}
service.update();
} catch (RemoteException | ServiceSpecificException ex) {
Slog.e(TAG, "Cannot call update on health AIDL HAL", ex);
} finally {
traceEnd();
}
});
}
@Override
public HealthInfo getHealthInfo() throws RemoteException {
IHealth service = mLastService.get();
if (service == null) return null;
try {
return service.getHealthInfo();
} catch (UnsupportedOperationException | ServiceSpecificException ex) {
return null;
}
}
private static void traceBegin(String name) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
}
private static void traceEnd() {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
private class ServiceCallback extends IServiceCallback.Stub {
@Override
public void onRegistration(String name, @NonNull final IBinder newBinder)
throws RemoteException {
if (!SERVICE_NAME.equals(name)) return;
// This runnable only runs on mHandlerThread and ordering is ensured, hence
// no locking is needed inside the runnable.
getHandlerThread()
.getThreadHandler()
.post(
() -> {
IHealth newService =
IHealth.Stub.asInterface(Binder.allowBlocking(newBinder));
IHealth oldService = mLastService.getAndSet(newService);
IBinder oldBinder =
oldService != null ? oldService.asBinder() : null;
if (Objects.equals(newBinder, oldBinder)) return;
Slog.i(TAG, "New health AIDL HAL service registered");
if (mRegCallback != null) {
mRegCallback.onRegistration(oldService, newService);
}
});
}
}
}