Loading services/core/java/com/android/server/BatteryService.java +124 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.os.PowerManager; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.server.am.BatteryStatsService; Loading @@ -35,7 +36,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.hardware.health.V2_0.HealthInfo; import android.hardware.health.V2_0.IHealth; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.BatteryProperties; Loading Loading @@ -63,6 +67,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; /** * <p>BatteryService monitors the charging status, and charge level of the device Loading Loading @@ -1020,4 +1027,121 @@ public final class BatteryService extends SystemService { } } } /** * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when * necessary. * * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and * the internal service is refreshed. * On death of an existing IHealth service, the internal service is NOT cleared to avoid * race condition between death notification and new service notification. Hence, * a caller must check for transaction errors when calling into the service. * * @hide Should only be used internally. */ @VisibleForTesting static final class HealthServiceWrapper { private static final String TAG = "HealthServiceWrapper"; public static final String INSTANCE_HEALTHD = "backup"; public static final String INSTANCE_VENDOR = "default"; // All interesting instances, sorted by priority high -> low. private static final List<String> sAllInstances = Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD); private final IServiceNotification mNotification = new Notification(); private Callback mCallback; private IHealthSupplier mHealthSupplier; /** * init should be called after constructor. For testing purposes, init is not called by * constructor. */ HealthServiceWrapper() { } /** * Start monitoring registration of new IHealth services. Only instances that are in * {@code sAllInstances} and in device / framework manifest are used. This function should * only be called once. * @throws RemoteException transaction error when talking to IServiceManager * @throws NoSuchElementException if one of the following cases: * - No service manager; * - none of {@code sAllInstances} are in manifests (i.e. not * available on this device), or none of these instances are available to current * process. * @throws NullPointerException when callback is null or supplier is null */ void init(Callback callback, IServiceManagerSupplier managerSupplier, IHealthSupplier healthSupplier) throws RemoteException, NoSuchElementException, NullPointerException { if (callback == null || managerSupplier == null || healthSupplier == null) throw new NullPointerException(); mCallback = callback; mHealthSupplier = healthSupplier; IServiceManager manager = managerSupplier.get(); for (String name : sAllInstances) { if (manager.getTransport(IHealth.kInterfaceName, name) == IServiceManager.Transport.EMPTY) { continue; } manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification); Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name); return; } throw new NoSuchElementException(String.format( "No IHealth service instance among %s is available. Perhaps no permission?", sAllInstances.toString())); } interface Callback { /** * This function is invoked asynchronously when a new and related IServiceNotification * is received. * @param service the recently retrieved service from IServiceManager. * Can be a dead service before service notification of a new service is delivered. * Implementation must handle cases for {@link RemoteException}s when calling * into service. * @param instance instance name. */ void onRegistration(IHealth service, String instance); } /** * Supplier of services. * Must not return null; throw {@link NoSuchElementException} if a service is not available. */ interface IServiceManagerSupplier { IServiceManager get() throws NoSuchElementException, RemoteException; } /** * Supplier of services. * Must not return null; throw {@link NoSuchElementException} if a service is not available. */ interface IHealthSupplier { IHealth get(String instanceName) throws NoSuchElementException, RemoteException; } private class Notification extends IServiceNotification.Stub { @Override public final void onRegistration(String interfaceName, String instanceName, boolean preexisting) { if (!IHealth.kInterfaceName.equals(interfaceName)) return; if (!sAllInstances.contains(instanceName)) return; try { IHealth service = mHealthSupplier.get(instanceName); Slog.i(TAG, "health: new instance registered " + instanceName); mCallback.onRegistration(service, instanceName); } catch (NoSuchElementException | RemoteException ex) { Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " + ex.getMessage() + ". Perhaps no permission?"); } } } } } services/tests/servicestests/Android.mk +4 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aid aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/JobTestApp/src) LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test LOCAL_JAVA_LIBRARIES := \ android.hidl.manager-V1.0-java \ android.test.mock \ legacy-android-test \ LOCAL_PACKAGE_NAME := FrameworksServicesTests LOCAL_COMPATIBILITY_SUITE := device-tests Loading services/tests/servicestests/src/com/android/server/BatteryServiceTest.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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; import static junit.framework.Assert.*; 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.RemoteException; import android.support.test.filters.SmallTest; import android.test.AndroidTestCase; import android.util.Slog; import java.util.Arrays; import java.util.Collection; import java.util.NoSuchElementException; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; public class BatteryServiceTest extends AndroidTestCase { @Mock IServiceManager mMockedManager; @Mock IHealth mMockedHal; @Mock BatteryService.HealthServiceWrapper.Callback mCallback; @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier; @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier; BatteryService.HealthServiceWrapper mWrapper; private static final String HEALTHD = BatteryService.HealthServiceWrapper.INSTANCE_HEALTHD; private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR; @Override public void setUp() { MockitoAnnotations.initMocks(this); } public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) { return new ArgumentMatcher<T>() { @Override public boolean matches(T e) { return collection.contains(e); } @Override public String toString() { return collection.toString(); } }; } private void initForInstances(String... instanceNamesArr) throws Exception { final Collection<String> instanceNames = Arrays.asList(instanceNamesArr); doAnswer((invocation) -> { Slog.e("BatteryServiceTest", "health: onRegistration " + invocation.getArguments()[2]); ((IServiceNotification)invocation.getArguments()[2]).onRegistration( IHealth.kInterfaceName, (String)invocation.getArguments()[1], true /* preexisting */); return null; }).when(mMockedManager).registerForNotifications( eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)), any(IServiceNotification.class)); doReturn(mMockedHal).when(mMockedManager) .get(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames))); doReturn(IServiceManager.Transport.HWBINDER).when(mMockedManager) .getTransport(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames))); doReturn(mMockedManager).when(mManagerSupplier).get(); doReturn(mMockedHal).when(mHealthServiceSupplier) .get(argThat(isOneOf(instanceNames))); mWrapper = new BatteryService.HealthServiceWrapper(); } @SmallTest public void testWrapPreferVendor() throws Exception { initForInstances(VENDOR, HEALTHD); mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); verify(mCallback).onRegistration(same(mMockedHal), eq(VENDOR)); } @SmallTest public void testUseHealthd() throws Exception { initForInstances(HEALTHD); mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); verify(mCallback).onRegistration(same(mMockedHal), eq(HEALTHD)); } @SmallTest public void testNoService() throws Exception { initForInstances("unrelated"); try { mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); fail("Expect NoSuchElementException"); } catch (NoSuchElementException ex) { // expected } } } Loading
services/core/java/com/android/server/BatteryService.java +124 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.os.PowerManager; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.server.am.BatteryStatsService; Loading @@ -35,7 +36,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.hardware.health.V2_0.HealthInfo; import android.hardware.health.V2_0.IHealth; import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.BatteryProperties; Loading Loading @@ -63,6 +67,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; /** * <p>BatteryService monitors the charging status, and charge level of the device Loading Loading @@ -1020,4 +1027,121 @@ public final class BatteryService extends SystemService { } } } /** * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when * necessary. * * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and * the internal service is refreshed. * On death of an existing IHealth service, the internal service is NOT cleared to avoid * race condition between death notification and new service notification. Hence, * a caller must check for transaction errors when calling into the service. * * @hide Should only be used internally. */ @VisibleForTesting static final class HealthServiceWrapper { private static final String TAG = "HealthServiceWrapper"; public static final String INSTANCE_HEALTHD = "backup"; public static final String INSTANCE_VENDOR = "default"; // All interesting instances, sorted by priority high -> low. private static final List<String> sAllInstances = Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD); private final IServiceNotification mNotification = new Notification(); private Callback mCallback; private IHealthSupplier mHealthSupplier; /** * init should be called after constructor. For testing purposes, init is not called by * constructor. */ HealthServiceWrapper() { } /** * Start monitoring registration of new IHealth services. Only instances that are in * {@code sAllInstances} and in device / framework manifest are used. This function should * only be called once. * @throws RemoteException transaction error when talking to IServiceManager * @throws NoSuchElementException if one of the following cases: * - No service manager; * - none of {@code sAllInstances} are in manifests (i.e. not * available on this device), or none of these instances are available to current * process. * @throws NullPointerException when callback is null or supplier is null */ void init(Callback callback, IServiceManagerSupplier managerSupplier, IHealthSupplier healthSupplier) throws RemoteException, NoSuchElementException, NullPointerException { if (callback == null || managerSupplier == null || healthSupplier == null) throw new NullPointerException(); mCallback = callback; mHealthSupplier = healthSupplier; IServiceManager manager = managerSupplier.get(); for (String name : sAllInstances) { if (manager.getTransport(IHealth.kInterfaceName, name) == IServiceManager.Transport.EMPTY) { continue; } manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification); Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name); return; } throw new NoSuchElementException(String.format( "No IHealth service instance among %s is available. Perhaps no permission?", sAllInstances.toString())); } interface Callback { /** * This function is invoked asynchronously when a new and related IServiceNotification * is received. * @param service the recently retrieved service from IServiceManager. * Can be a dead service before service notification of a new service is delivered. * Implementation must handle cases for {@link RemoteException}s when calling * into service. * @param instance instance name. */ void onRegistration(IHealth service, String instance); } /** * Supplier of services. * Must not return null; throw {@link NoSuchElementException} if a service is not available. */ interface IServiceManagerSupplier { IServiceManager get() throws NoSuchElementException, RemoteException; } /** * Supplier of services. * Must not return null; throw {@link NoSuchElementException} if a service is not available. */ interface IHealthSupplier { IHealth get(String instanceName) throws NoSuchElementException, RemoteException; } private class Notification extends IServiceNotification.Stub { @Override public final void onRegistration(String interfaceName, String instanceName, boolean preexisting) { if (!IHealth.kInterfaceName.equals(interfaceName)) return; if (!sAllInstances.contains(instanceName)) return; try { IHealth service = mHealthSupplier.get(instanceName); Slog.i(TAG, "health: new instance registered " + instanceName); mCallback.onRegistration(service, instanceName); } catch (NoSuchElementException | RemoteException ex) { Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " + ex.getMessage() + ". Perhaps no permission?"); } } } } }
services/tests/servicestests/Android.mk +4 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,10 @@ LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aid aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/JobTestApp/src) LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test LOCAL_JAVA_LIBRARIES := \ android.hidl.manager-V1.0-java \ android.test.mock \ legacy-android-test \ LOCAL_PACKAGE_NAME := FrameworksServicesTests LOCAL_COMPATIBILITY_SUITE := device-tests Loading
services/tests/servicestests/src/com/android/server/BatteryServiceTest.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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; import static junit.framework.Assert.*; 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.RemoteException; import android.support.test.filters.SmallTest; import android.test.AndroidTestCase; import android.util.Slog; import java.util.Arrays; import java.util.Collection; import java.util.NoSuchElementException; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; public class BatteryServiceTest extends AndroidTestCase { @Mock IServiceManager mMockedManager; @Mock IHealth mMockedHal; @Mock BatteryService.HealthServiceWrapper.Callback mCallback; @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier; @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier; BatteryService.HealthServiceWrapper mWrapper; private static final String HEALTHD = BatteryService.HealthServiceWrapper.INSTANCE_HEALTHD; private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR; @Override public void setUp() { MockitoAnnotations.initMocks(this); } public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) { return new ArgumentMatcher<T>() { @Override public boolean matches(T e) { return collection.contains(e); } @Override public String toString() { return collection.toString(); } }; } private void initForInstances(String... instanceNamesArr) throws Exception { final Collection<String> instanceNames = Arrays.asList(instanceNamesArr); doAnswer((invocation) -> { Slog.e("BatteryServiceTest", "health: onRegistration " + invocation.getArguments()[2]); ((IServiceNotification)invocation.getArguments()[2]).onRegistration( IHealth.kInterfaceName, (String)invocation.getArguments()[1], true /* preexisting */); return null; }).when(mMockedManager).registerForNotifications( eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)), any(IServiceNotification.class)); doReturn(mMockedHal).when(mMockedManager) .get(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames))); doReturn(IServiceManager.Transport.HWBINDER).when(mMockedManager) .getTransport(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames))); doReturn(mMockedManager).when(mManagerSupplier).get(); doReturn(mMockedHal).when(mHealthServiceSupplier) .get(argThat(isOneOf(instanceNames))); mWrapper = new BatteryService.HealthServiceWrapper(); } @SmallTest public void testWrapPreferVendor() throws Exception { initForInstances(VENDOR, HEALTHD); mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); verify(mCallback).onRegistration(same(mMockedHal), eq(VENDOR)); } @SmallTest public void testUseHealthd() throws Exception { initForInstances(HEALTHD); mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); verify(mCallback).onRegistration(same(mMockedHal), eq(HEALTHD)); } @SmallTest public void testNoService() throws Exception { initForInstances("unrelated"); try { mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); fail("Expect NoSuchElementException"); } catch (NoSuchElementException ex) { // expected } } }