Merge "health: BatteryService etc. use health AIDL HAL" am: 104adeeb75 am: 2cbfa143b6

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1883551

Change-Id: I8840fba9cf1e69ddd6b8659ab512e7c13b19baec
This commit is contained in:
Treehugger Robot
2021-11-18 06:53:14 +00:00
committed by Automerger Merge Worker
4 changed files with 450 additions and 15 deletions

View File

@@ -0,0 +1,119 @@
/*
* 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.HealthInfo;
import android.hardware.health.IHealth;
import android.hardware.health.IHealthInfoCallback;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
/**
* On service registration, {@link #onRegistration} is called, which registers {@code this}, an
* {@link IHealthInfoCallback}, to the health service.
*
* <p>When the health service has updates to health info via {@link IHealthInfoCallback}, {@link
* HealthInfoCallback#update} is called.
*
* <p>AIDL variant of {@link HealthHalCallbackHidl}.
*
* @hide
*/
// It is made public so Mockito can access this class. It should have been package private if not
// for testing.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class HealthRegCallbackAidl {
private static final String TAG = "HealthRegCallbackAidl";
private final HealthInfoCallback mServiceInfoCallback;
private final IHealthInfoCallback mHalInfoCallback = new HalInfoCallback();
HealthRegCallbackAidl(@Nullable HealthInfoCallback healthInfoCallback) {
mServiceInfoCallback = healthInfoCallback;
}
/**
* Called when the service manager sees {@code newService} replacing {@code oldService}.
* This unregisters the health info callback from the old service (ignoring errors), then
* registers the health info callback to the new service.
*
* @param oldService the old IHealth service
* @param newService the new IHealth service
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void onRegistration(@Nullable IHealth oldService, @NonNull IHealth newService) {
if (mServiceInfoCallback == null) return;
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "HealthUnregisterCallbackAidl");
try {
unregisterCallback(oldService, mHalInfoCallback);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "HealthRegisterCallbackAidl");
try {
registerCallback(newService, mHalInfoCallback);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
private static void unregisterCallback(@Nullable IHealth oldService, IHealthInfoCallback cb) {
if (oldService == null) return;
try {
oldService.unregisterCallback(cb);
} catch (RemoteException e) {
// Ignore errors. The service might have died.
Slog.w(
TAG,
"health: cannot unregister previous callback (transaction error): "
+ e.getMessage());
}
}
private static void registerCallback(@NonNull IHealth newService, IHealthInfoCallback cb) {
try {
newService.registerCallback(cb);
} catch (RemoteException e) {
Slog.e(
TAG,
"health: cannot register callback, framework may cease to"
+ " receive updates on health / battery info!",
e);
return;
}
// registerCallback does NOT guarantee that update is called immediately, so request a
// manual update here.
try {
newService.update();
} catch (RemoteException e) {
Slog.e(TAG, "health: cannot update after registering health info callback", e);
}
}
private class HalInfoCallback extends IHealthInfoCallback.Stub {
@Override
public void healthInfoChanged(HealthInfo healthInfo) throws RemoteException {
mServiceInfoCallback.update(healthInfo);
}
}
}

View File

@@ -81,6 +81,8 @@ public abstract class HealthServiceWrapper {
public static HealthServiceWrapper create(@Nullable HealthInfoCallback healthInfoCallback)
throws RemoteException, NoSuchElementException {
return create(
healthInfoCallback == null ? null : new HealthRegCallbackAidl(healthInfoCallback),
new HealthServiceWrapperAidl.ServiceManagerStub() {},
healthInfoCallback == null ? null : new HealthHalCallbackHidl(healthInfoCallback),
new HealthServiceWrapperHidl.IServiceManagerSupplier() {},
new HealthServiceWrapperHidl.IHealthSupplier() {});
@@ -89,6 +91,9 @@ public abstract class HealthServiceWrapper {
/**
* Create a new HealthServiceWrapper instance for testing.
*
* @param aidlRegCallback callback for AIDL service registration, or {@code null} if the client
* does not care about AIDL service registration notifications
* @param aidlServiceManager Stub for AIDL ServiceManager
* @param hidlRegCallback callback for HIDL service registration, or {@code null} if the client
* does not care about HIDL service registration notifications
* @param hidlServiceManagerSupplier supplier of HIDL service manager
@@ -97,10 +102,17 @@ public abstract class HealthServiceWrapper {
*/
@VisibleForTesting
static @NonNull HealthServiceWrapper create(
@Nullable HealthRegCallbackAidl aidlRegCallback,
@NonNull HealthServiceWrapperAidl.ServiceManagerStub aidlServiceManager,
@Nullable HealthServiceWrapperHidl.Callback hidlRegCallback,
@NonNull HealthServiceWrapperHidl.IServiceManagerSupplier hidlServiceManagerSupplier,
@NonNull HealthServiceWrapperHidl.IHealthSupplier hidlHealthSupplier)
throws RemoteException, NoSuchElementException {
try {
return new HealthServiceWrapperAidl(aidlRegCallback, aidlServiceManager);
} catch (NoSuchElementException e) {
// Ignore, try HIDL
}
return new HealthServiceWrapperHidl(
hidlRegCallback, hidlServiceManagerSupplier, hidlHealthSupplier);
}

View File

@@ -0,0 +1,215 @@
/*
* 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.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");
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;
}
} 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");
mRegCallback.onRegistration(oldService, newService);
});
}
}
}

View File

@@ -19,11 +19,12 @@ package com.android.server.health;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.fail;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.Mockito.*;
import android.hardware.health.V2_0.IHealth;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IServiceCallback;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
@@ -44,28 +45,47 @@ import java.util.NoSuchElementException;
@RunWith(AndroidJUnit4.class)
public class HealthServiceWrapperTest {
@Mock IServiceManager mMockedManager;
@Mock IHealth mMockedHal;
@Mock IHealth mMockedHal2;
@Mock android.hardware.health.V2_0.IHealth mMockedHal;
@Mock android.hardware.health.V2_0.IHealth mMockedHal2;
@Mock HealthServiceWrapperHidl.Callback mCallback;
@Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier;
@Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier;
@Mock android.hardware.health.IHealth.Stub mMockedAidlHal;
@Mock android.hardware.health.IHealth.Stub mMockedAidlHal2;
@Mock HealthServiceWrapperAidl.ServiceManagerStub mMockedAidlManager;
@Mock HealthRegCallbackAidl mRegCallbackAidl;
HealthServiceWrapper mWrapper;
private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR;
private static final String AIDL_SERVICE_NAME = HealthServiceWrapperAidl.SERVICE_NAME;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// Mocks the conversion between IHealth and IBinder.
when(mMockedAidlHal.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal
when(mMockedAidlHal2.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal2
when(mMockedAidlHal.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
.thenReturn(mMockedAidlHal);
when(mMockedAidlHal2.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
.thenReturn(mMockedAidlHal2);
}
@After
public void tearDown() {
validateMockitoUsage();
if (mWrapper != null) mWrapper.getHandlerThread().quitSafely();
}
public static <T> ArgumentMatcher<T> isOneOf(T[] collection) {
return isOneOf(Arrays.asList(collection));
}
public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
return new ArgumentMatcher<T>() {
@Override
@@ -75,13 +95,39 @@ public class HealthServiceWrapperTest {
@Override
public String toString() {
return collection.toString();
return "is one of " + collection.toString();
}
};
}
private void initForInstances(String... instanceNamesArr) throws Exception {
final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
/**
* Set up mock objects to pretend that the given AIDL and HIDL instances exists.
*
* <p>Also, when registering service notifications, the mocked service managers immediately
* sends 3 registration notifications, including 2 referring to the original HAL and 1 referring
* to the new HAL.
*
* @param aidlInstances e.g. {"android.hardware.health.IHealth/default"}
* @param hidlInstances e.g. {"default", "backup"}
* @throws Exception
*/
private void initForInstances(String[] aidlInstances, String[] hidlInstances) throws Exception {
doAnswer(
(invocation) -> {
sendAidlRegCallback(invocation, mMockedAidlHal);
sendAidlRegCallback(invocation, mMockedAidlHal);
sendAidlRegCallback(invocation, mMockedAidlHal2);
return null;
})
.when(mMockedAidlManager)
.registerForNotifications(
argThat(isOneOf(aidlInstances)), any(IServiceCallback.class));
when(mMockedAidlManager.waitForDeclaredService(argThat(isOneOf(aidlInstances))))
.thenReturn(mMockedAidlHal)
.thenThrow(new RuntimeException("waitForDeclaredService called more than once"));
when(mMockedAidlManager.waitForDeclaredService(not(argThat(isOneOf(aidlInstances)))))
.thenReturn(null);
doAnswer(
(invocation) -> {
// technically, preexisting is ignored by
@@ -93,8 +139,8 @@ public class HealthServiceWrapperTest {
})
.when(mMockedManager)
.registerForNotifications(
eq(IHealth.kInterfaceName),
argThat(isOneOf(instanceNames)),
eq(android.hardware.health.V2_0.IHealth.kInterfaceName),
argThat(isOneOf(hidlInstances)),
any(IServiceNotification.class));
doReturn(mMockedManager).when(mManagerSupplier).get();
@@ -104,7 +150,7 @@ public class HealthServiceWrapperTest {
.doReturn(mMockedHal2) // notification 3
.doThrow(new RuntimeException("Should not call getService for more than 4 times"))
.when(mHealthServiceSupplier)
.get(argThat(isOneOf(instanceNames)));
.get(argThat(isOneOf(hidlInstances)));
}
private void waitHandlerThreadFinish() throws Exception {
@@ -121,19 +167,62 @@ public class HealthServiceWrapperTest {
throws Exception {
((IServiceNotification) invocation.getArguments()[2])
.onRegistration(
IHealth.kInterfaceName, (String) invocation.getArguments()[1], preexisting);
android.hardware.health.V2_0.IHealth.kInterfaceName,
(String) invocation.getArguments()[1],
preexisting);
}
private static void sendAidlRegCallback(
InvocationOnMock invocation, android.hardware.health.IHealth service) throws Exception {
((IServiceCallback) invocation.getArguments()[1])
.onRegistration((String) invocation.getArguments()[0], service.asBinder());
}
private void createWrapper() throws RemoteException {
mWrapper = HealthServiceWrapper.create(mCallback, mManagerSupplier, mHealthServiceSupplier);
mWrapper =
HealthServiceWrapper.create(
mRegCallbackAidl,
mMockedAidlManager,
mCallback,
mManagerSupplier,
mHealthServiceSupplier);
}
@SmallTest
@Test
public void testWrapPreferVendor() throws Exception {
initForInstances(VENDOR);
public void testWrapAidlOnly() throws Exception {
initForInstances(new String[] {AIDL_SERVICE_NAME}, new String[0]);
createWrapper();
waitHandlerThreadFinish();
verify(mRegCallbackAidl, times(1)).onRegistration(same(null), same(mMockedAidlHal));
verify(mRegCallbackAidl, never())
.onRegistration(same(mMockedAidlHal), same(mMockedAidlHal));
verify(mRegCallbackAidl, times(1))
.onRegistration(same(mMockedAidlHal), same(mMockedAidlHal2));
verify(mCallback, never()).onRegistration(any(), any(), anyString());
}
@SmallTest
@Test
public void testWrapPreferAidl() throws Exception {
initForInstances(new String[] {AIDL_SERVICE_NAME}, new String[] {VENDOR});
createWrapper();
waitHandlerThreadFinish();
verify(mRegCallbackAidl, times(1)).onRegistration(same(null), same(mMockedAidlHal));
verify(mRegCallbackAidl, never())
.onRegistration(same(mMockedAidlHal), same(mMockedAidlHal));
verify(mRegCallbackAidl, times(1))
.onRegistration(same(mMockedAidlHal), same(mMockedAidlHal2));
verify(mCallback, never()).onRegistration(any(), any(), anyString());
}
@SmallTest
@Test
public void testWrapFallbackHidl() throws Exception {
initForInstances(new String[0], new String[] {VENDOR});
createWrapper();
waitHandlerThreadFinish();
verify(mRegCallbackAidl, never()).onRegistration(any(), any());
verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
@@ -142,7 +231,7 @@ public class HealthServiceWrapperTest {
@SmallTest
@Test
public void testNoService() throws Exception {
initForInstances("unrelated");
initForInstances(new String[0], new String[] {"unrelated"});
try {
createWrapper();
fail("Expect NoSuchElementException");