Loading services/core/java/com/android/server/health/HealthRegCallbackAidl.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * 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); } } } services/core/java/com/android/server/health/HealthServiceWrapper.java +12 −0 Original line number Diff line number Diff line Loading @@ -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() {}); Loading @@ -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 Loading @@ -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); } Loading services/core/java/com/android/server/health/HealthServiceWrapperAidl.java 0 → 100644 +215 −0 Original line number Diff line number Diff line /* * 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); }); } } } services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java +104 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading @@ -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(); Loading @@ -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 { Loading @@ -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 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 testWrapPreferVendor() throws Exception { initForInstances(VENDOR); 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)); Loading @@ -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"); Loading Loading
services/core/java/com/android/server/health/HealthRegCallbackAidl.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * 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); } } }
services/core/java/com/android/server/health/HealthServiceWrapper.java +12 −0 Original line number Diff line number Diff line Loading @@ -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() {}); Loading @@ -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 Loading @@ -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); } Loading
services/core/java/com/android/server/health/HealthServiceWrapperAidl.java 0 → 100644 +215 −0 Original line number Diff line number Diff line /* * 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); }); } } }
services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java +104 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading @@ -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(); Loading @@ -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 { Loading @@ -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 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 testWrapPreferVendor() throws Exception { initForInstances(VENDOR); 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)); Loading @@ -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"); Loading