summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <treehugger-gerrit@google.com> 2021-11-09 22:26:26 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2021-11-09 22:26:26 +0000
commit593bc644db5e966e4a586d73e796afcfe8c207d6 (patch)
tree9f22cd8824c7e4d40dff725ca02601ddebfedaa2
parent01d71f11eacc6d8d8d5d4bace143974293306432 (diff)
parenta43024b6e81507e10d43a35b845b80687b923026 (diff)
Merge changes from topic "health-service-wrapper-test" am: 6057fe80f1 am: a43024b6e8
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1883488 Change-Id: I96ee3a48d5f2b7a1bbd38a3bce1a4ad656b72dcc
-rw-r--r--services/core/java/com/android/server/BatteryService.java341
-rw-r--r--services/core/java/com/android/server/health/HealthHalCallbackHidl.java115
-rw-r--r--services/core/java/com/android/server/health/HealthInfoCallback.java32
-rw-r--r--services/core/java/com/android/server/health/HealthServiceWrapper.java108
-rw-r--r--services/core/java/com/android/server/health/HealthServiceWrapperHidl.java311
-rw-r--r--services/core/java/com/android/server/health/Utils.java53
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java65
-rw-r--r--services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java (renamed from services/tests/servicestests/src/com/android/server/BatteryServiceTest.java)96
8 files changed, 713 insertions, 408 deletions
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 0146aa82a217..728efa505d99 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -17,6 +17,7 @@
package com.android.server;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.server.health.Utils.copy;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -26,13 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.hardware.health.V1_0.HealthInfo;
-import android.hardware.health.V2_0.IHealth;
-import android.hardware.health.V2_0.Result;
import android.hardware.health.V2_1.BatteryCapacityLevel;
-import android.hardware.health.V2_1.Constants;
-import android.hardware.health.V2_1.IHealthInfoCallback;
-import android.hidl.manager.V1_0.IServiceManager;
-import android.hidl.manager.V1_0.IServiceNotification;
import android.metrics.LogMaker;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -44,7 +39,6 @@ import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.OsProtoEnums;
@@ -62,15 +56,14 @@ import android.provider.Settings;
import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.util.EventLog;
-import android.util.MutableInt;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
import com.android.server.am.BatteryStatsService;
+import com.android.server.health.HealthServiceWrapper;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
@@ -82,8 +75,6 @@ import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -191,7 +182,6 @@ public final class BatteryService extends SystemService {
private ActivityManagerInternal mActivityManagerInternal;
private HealthServiceWrapper mHealthServiceWrapper;
- private HealthHalCallback mHealthHalCallback;
private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
@@ -274,13 +264,9 @@ public final class BatteryService extends SystemService {
private void registerHealthCallback() {
traceBegin("HealthInitWrapper");
- mHealthServiceWrapper = new HealthServiceWrapper();
- mHealthHalCallback = new HealthHalCallback();
// IHealth is lazily retrieved.
try {
- mHealthServiceWrapper.init(mHealthHalCallback,
- new HealthServiceWrapper.IServiceManagerSupplier() {},
- new HealthServiceWrapper.IHealthSupplier() {});
+ mHealthServiceWrapper = HealthServiceWrapper.create(this::update);
} catch (RemoteException ex) {
Slog.e(TAG, "health: cannot register callback. (RemoteException)");
throw ex.rethrowFromSystemServer();
@@ -454,25 +440,6 @@ public final class BatteryService extends SystemService {
traceEnd();
}
- private static void copy(HealthInfo dst, HealthInfo src) {
- dst.chargerAcOnline = src.chargerAcOnline;
- dst.chargerUsbOnline = src.chargerUsbOnline;
- dst.chargerWirelessOnline = src.chargerWirelessOnline;
- dst.maxChargingCurrent = src.maxChargingCurrent;
- dst.maxChargingVoltage = src.maxChargingVoltage;
- dst.batteryStatus = src.batteryStatus;
- dst.batteryHealth = src.batteryHealth;
- dst.batteryPresent = src.batteryPresent;
- dst.batteryLevel = src.batteryLevel;
- dst.batteryVoltage = src.batteryVoltage;
- dst.batteryTemperature = src.batteryTemperature;
- dst.batteryCurrent = src.batteryCurrent;
- dst.batteryCycleCount = src.batteryCycleCount;
- dst.batteryFullCharge = src.batteryFullCharge;
- dst.batteryChargeCounter = src.batteryChargeCounter;
- dst.batteryTechnology = src.batteryTechnology;
- }
-
private static int plugType(HealthInfo healthInfo) {
if (healthInfo.chargerAcOnline) {
return BatteryManager.BATTERY_PLUGGED_AC;
@@ -1184,64 +1151,6 @@ public final class BatteryService extends SystemService {
}
}
- private final class HealthHalCallback extends IHealthInfoCallback.Stub
- implements HealthServiceWrapper.Callback {
- @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
- android.hardware.health.V2_1.HealthInfo propsLatest =
- new android.hardware.health.V2_1.HealthInfo();
- propsLatest.legacy = props;
-
- propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED;
- propsLatest.batteryChargeTimeToFullNowSeconds =
- Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
-
- BatteryService.this.update(propsLatest);
- }
-
- @Override public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) {
- BatteryService.this.update(props);
- }
-
- // on new service registered
- @Override public void onRegistration(IHealth oldService, IHealth newService,
- String instance) {
- if (newService == null) return;
-
- traceBegin("HealthUnregisterCallback");
- try {
- if (oldService != null) {
- int r = oldService.unregisterCallback(this);
- if (r != Result.SUCCESS) {
- Slog.w(TAG, "health: cannot unregister previous callback: " +
- Result.toString(r));
- }
- }
- } catch (RemoteException ex) {
- Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
- + ex.getMessage());
- } finally {
- traceEnd();
- }
-
- traceBegin("HealthRegisterCallback");
- try {
- int r = newService.registerCallback(this);
- if (r != Result.SUCCESS) {
- Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
- return;
- }
- // registerCallback does NOT guarantee that update is called
- // immediately, so request a manual update here.
- newService.update();
- } catch (RemoteException ex) {
- Slog.e(TAG, "health: cannot register callback (transaction error): "
- + ex.getMessage());
- } finally {
- traceEnd();
- }
- }
- }
-
private final class BinderService extends Binder {
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1265,71 +1174,11 @@ public final class BatteryService extends SystemService {
private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
@Override
public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
- traceBegin("HealthGetProperty");
- try {
- IHealth service = mHealthServiceWrapper.getLastService();
- if (service == null) throw new RemoteException("no health service");
- final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
- switch(id) {
- case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
- service.getChargeCounter((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
- service.getCurrentNow((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
- service.getCurrentAverage((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_CAPACITY:
- service.getCapacity((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_STATUS:
- service.getChargeStatus((int result, int value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
- service.getEnergyCounter((int result, long value) -> {
- outResult.value = result;
- if (result == Result.SUCCESS) prop.setLong(value);
- });
- break;
- }
- return outResult.value;
- } finally {
- traceEnd();
- }
+ return mHealthServiceWrapper.getProperty(id, prop);
}
@Override
public void scheduleUpdate() throws RemoteException {
- mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> {
- traceBegin("HealthScheduleUpdate");
- try {
- IHealth service = mHealthServiceWrapper.getLastService();
- if (service == null) {
- Slog.e(TAG, "no health service");
- return;
- }
- service.update();
- } catch (RemoteException ex) {
- Slog.e(TAG, "Cannot call update on health HAL", ex);
- } finally {
- traceEnd();
- }
- });
+ mHealthServiceWrapper.scheduleUpdate();
}
}
@@ -1418,184 +1267,4 @@ public final class BatteryService extends SystemService {
BatteryService.this.suspendBatteryInput();
}
}
-
- /**
- * 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.
- */
- public static final class HealthServiceWrapper {
- private static final String TAG = "HealthServiceWrapper";
- public static final String INSTANCE_VENDOR = "default";
-
- private final IServiceNotification mNotification = new Notification();
- private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
- // These variables are fixed after init.
- private Callback mCallback;
- private IHealthSupplier mHealthSupplier;
- private String mInstanceName;
-
- // Last IHealth service received.
- private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
-
- /**
- * init should be called after constructor. For testing purposes, init is not called by
- * constructor.
- */
- public HealthServiceWrapper() {
- }
-
- public IHealth getLastService() {
- return mLastService.get();
- }
-
- /**
- * See {@link #init(Callback, IServiceManagerSupplier, IHealthSupplier)}
- */
- public void init() throws RemoteException, NoSuchElementException {
- init(/* callback= */null, new HealthServiceWrapper.IServiceManagerSupplier() {},
- new HealthServiceWrapper.IHealthSupplier() {});
- }
-
- /**
- * Start monitoring registration of new IHealth services. Only instance
- * {@link #INSTANCE_VENDOR} and in device / framework manifest are used. This function should
- * only be called once.
- *
- * mCallback.onRegistration() is called synchronously (aka in init thread) before
- * this method returns if callback is not null.
- *
- * @throws RemoteException transaction error when talking to IServiceManager
- * @throws NoSuchElementException if one of the following cases:
- * - No service manager;
- * - {@link #INSTANCE_VENDOR} is not in manifests (i.e. not
- * available on this device), or none of these instances are available to current
- * process.
- * @throws NullPointerException when supplier is null
- */
- void init(@Nullable Callback callback,
- IServiceManagerSupplier managerSupplier,
- IHealthSupplier healthSupplier)
- throws RemoteException, NoSuchElementException, NullPointerException {
- if (managerSupplier == null || healthSupplier == null) {
- throw new NullPointerException();
- }
- IServiceManager manager;
-
- mHealthSupplier = healthSupplier;
-
- // Initialize mLastService and call callback for the first time (in init thread)
- IHealth newService = null;
- traceBegin("HealthInitGetService_" + INSTANCE_VENDOR);
- try {
- newService = healthSupplier.get(INSTANCE_VENDOR);
- } catch (NoSuchElementException ex) {
- /* ignored, handled below */
- } finally {
- traceEnd();
- }
- if (newService != null) {
- mInstanceName = INSTANCE_VENDOR;
- mLastService.set(newService);
- }
-
- if (mInstanceName == null || newService == null) {
- throw new NoSuchElementException(String.format(
- "IHealth service instance %s isn't available. Perhaps no permission?",
- INSTANCE_VENDOR));
- }
-
- if (callback != null) {
- mCallback = callback;
- mCallback.onRegistration(null, newService, mInstanceName);
- }
-
- // Register for future service registrations
- traceBegin("HealthInitRegisterNotification");
- mHandlerThread.start();
- try {
- managerSupplier.get().registerForNotifications(
- IHealth.kInterfaceName, mInstanceName, mNotification);
- } finally {
- traceEnd();
- }
- Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
- }
-
- @VisibleForTesting
- HandlerThread getHandlerThread() {
- return mHandlerThread;
- }
-
- 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 oldService, IHealth newService, String instance);
- }
-
- /**
- * Supplier of services.
- * Must not return null; throw {@link NoSuchElementException} if a service is not available.
- */
- interface IServiceManagerSupplier {
- default IServiceManager get() throws NoSuchElementException, RemoteException {
- return IServiceManager.getService();
- }
- }
- /**
- * Supplier of services.
- * Must not return null; throw {@link NoSuchElementException} if a service is not available.
- */
- interface IHealthSupplier {
- default IHealth get(String name) throws NoSuchElementException, RemoteException {
- return IHealth.getService(name, true /* retry */);
- }
- }
-
- private class Notification extends IServiceNotification.Stub {
- @Override
- public final void onRegistration(String interfaceName, String instanceName,
- boolean preexisting) {
- if (!IHealth.kInterfaceName.equals(interfaceName)) return;
- if (!mInstanceName.equals(instanceName)) return;
-
- // This runnable only runs on mHandlerThread and ordering is ensured, hence
- // no locking is needed inside the runnable.
- mHandlerThread.getThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- try {
- IHealth newService = mHealthSupplier.get(mInstanceName);
- IHealth oldService = mLastService.getAndSet(newService);
-
- // preexisting may be inaccurate (race). Check for equality here.
- if (Objects.equals(newService, oldService)) return;
-
- Slog.i(TAG, "health: new instance registered " + mInstanceName);
- // #init() may be called with null callback. Skip null callbacks.
- if (mCallback == null) return;
- mCallback.onRegistration(oldService, newService, mInstanceName);
- } catch (NoSuchElementException | RemoteException ex) {
- Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
- + "': " + ex.getMessage() + ". Perhaps no permission?");
- }
- }
- });
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/health/HealthHalCallbackHidl.java b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java
new file mode 100644
index 000000000000..6b4d7b7c17c1
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
+import android.hardware.health.V2_1.BatteryCapacityLevel;
+import android.hardware.health.V2_1.Constants;
+import android.hardware.health.V2_1.IHealthInfoCallback;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * On service registration, {@link HealthServiceWrapperHidl.Callback#onRegistration} is called,
+ * which registers {@code this}, a {@link IHealthInfoCallback}, to the health service.
+ *
+ * <p>When the health service has updates to health info, {@link HealthInfoCallback#update} is
+ * called.
+ *
+ * @hide
+ */
+class HealthHalCallbackHidl extends IHealthInfoCallback.Stub
+ implements HealthServiceWrapperHidl.Callback {
+
+ private static final String TAG = HealthHalCallbackHidl.class.getSimpleName();
+
+ 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 HealthInfoCallback mCallback;
+
+ HealthHalCallbackHidl(@NonNull HealthInfoCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
+ android.hardware.health.V2_1.HealthInfo propsLatest =
+ new android.hardware.health.V2_1.HealthInfo();
+ propsLatest.legacy = props;
+
+ propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED;
+ propsLatest.batteryChargeTimeToFullNowSeconds =
+ Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
+
+ mCallback.update(propsLatest);
+ }
+
+ @Override
+ public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) {
+ mCallback.update(props);
+ }
+
+ // on new service registered
+ @Override
+ public void onRegistration(IHealth oldService, IHealth newService, String instance) {
+ if (newService == null) return;
+
+ traceBegin("HealthUnregisterCallback");
+ try {
+ if (oldService != null) {
+ int r = oldService.unregisterCallback(this);
+ if (r != Result.SUCCESS) {
+ Slog.w(
+ TAG,
+ "health: cannot unregister previous callback: " + Result.toString(r));
+ }
+ }
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "health: cannot unregister previous callback (transaction error): "
+ + ex.getMessage());
+ } finally {
+ traceEnd();
+ }
+
+ traceBegin("HealthRegisterCallback");
+ try {
+ int r = newService.registerCallback(this);
+ if (r != Result.SUCCESS) {
+ Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+ return;
+ }
+ // registerCallback does NOT guarantee that update is called
+ // immediately, so request a manual update here.
+ newService.update();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "health: cannot register callback (transaction error): " + ex.getMessage());
+ } finally {
+ traceEnd();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/health/HealthInfoCallback.java b/services/core/java/com/android/server/health/HealthInfoCallback.java
new file mode 100644
index 000000000000..8136ca0c6068
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthInfoCallback.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * A wrapper over HIDL / AIDL IHealthInfoCallback.
+ *
+ * @hide
+ */
+public interface HealthInfoCallback {
+ /**
+ * Signals to the client that health info is changed.
+ *
+ * @param props the new health info.
+ */
+ // TODO(b/177269435): AIDL
+ void update(android.hardware.health.V2_1.HealthInfo props);
+}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapper.java b/services/core/java/com/android/server/health/HealthServiceWrapper.java
new file mode 100644
index 000000000000..0b43f26e294d
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthServiceWrapper.java
@@ -0,0 +1,108 @@
+/*
+ * 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.os.BatteryProperty;
+import android.os.HandlerThread;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.NoSuchElementException;
+
+/**
+ * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when necessary.
+ * This is essentially a wrapper over IHealth that is useful for BatteryService.
+ *
+ * <p>The implementation may be backed by a HIDL or AIDL HAL.
+ *
+ * <p>On new registration of IHealth service, 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.
+ */
+public abstract class HealthServiceWrapper {
+ /** @return the handler thread. Exposed for testing. */
+ @VisibleForTesting
+ abstract HandlerThread getHandlerThread();
+
+ /**
+ * Calls into get*() functions in the health HAL. This reads into the kernel interfaces
+ * directly.
+ *
+ * @see IBatteryPropertiesRegistrar#getProperty
+ */
+ public abstract int getProperty(int id, BatteryProperty prop) throws RemoteException;
+
+ /**
+ * Calls update() in the health HAL.
+ *
+ * @see IBatteryPropertiesRegistrar#scheduleUpdate
+ */
+ public abstract void scheduleUpdate() throws RemoteException;
+
+ /**
+ * Calls into getHealthInfo() in the health HAL. This returns a cached value in the health HAL
+ * implementation.
+ *
+ * @return health info. {@code null} if no health HAL service. {@code null} if any
+ * service-specific error when calling {@code getHealthInfo}, e.g. it is unsupported.
+ * @throws RemoteException for any transaction-level errors
+ */
+ // TODO(b/177269435): AIDL
+ public abstract android.hardware.health.V1_0.HealthInfo getHealthInfo() throws RemoteException;
+
+ /**
+ * Create a new HealthServiceWrapper instance.
+ *
+ * @param healthInfoCallback the callback to call when health info changes
+ * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service.
+ * @throws RemoteException transaction errors
+ * @throws NoSuchElementException no HIDL or AIDL service is available
+ */
+ public static HealthServiceWrapper create(@Nullable HealthInfoCallback healthInfoCallback)
+ throws RemoteException, NoSuchElementException {
+ return create(
+ healthInfoCallback == null ? null : new HealthHalCallbackHidl(healthInfoCallback),
+ new HealthServiceWrapperHidl.IServiceManagerSupplier() {},
+ new HealthServiceWrapperHidl.IHealthSupplier() {});
+ }
+
+ /**
+ * Create a new HealthServiceWrapper instance for testing.
+ *
+ * @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
+ * @param hidlHealthSupplier supplier of HIDL health HAL
+ * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service.
+ */
+ @VisibleForTesting
+ static @NonNull HealthServiceWrapper create(
+ @Nullable HealthServiceWrapperHidl.Callback hidlRegCallback,
+ @NonNull HealthServiceWrapperHidl.IServiceManagerSupplier hidlServiceManagerSupplier,
+ @NonNull HealthServiceWrapperHidl.IHealthSupplier hidlHealthSupplier)
+ throws RemoteException, NoSuchElementException {
+ return new HealthServiceWrapperHidl(
+ hidlRegCallback, hidlServiceManagerSupplier, hidlHealthSupplier);
+ }
+}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java
new file mode 100644
index 000000000000..3bff2f83d3d2
--- /dev/null
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java
@@ -0,0 +1,311 @@
+/*
+ * 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.V1_0.HealthInfo;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.BatteryManager;
+import android.os.BatteryProperty;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.MutableInt;
+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 HIDL HAL.
+ *
+ * @hide
+ */
+final class HealthServiceWrapperHidl extends HealthServiceWrapper {
+ private static final String TAG = "HealthServiceWrapperHidl";
+ public static final String INSTANCE_VENDOR = "default";
+
+ private final IServiceNotification mNotification = new Notification();
+ private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder");
+ // These variables are fixed after init.
+ private Callback mCallback;
+ private IHealthSupplier mHealthSupplier;
+ private String mInstanceName;
+
+ // Last IHealth service received.
+ private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
+ 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);
+ }
+
+ @Override
+ public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+ traceBegin("HealthGetProperty");
+ try {
+ IHealth service = mLastService.get();
+ if (service == null) throw new RemoteException("no health service");
+ final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+ switch (id) {
+ case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+ service.getChargeCounter(
+ (int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+ service.getCurrentNow(
+ (int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+ service.getCurrentAverage(
+ (int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+ service.getCapacity(
+ (int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_STATUS:
+ service.getChargeStatus(
+ (int result, int value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+ service.getEnergyCounter(
+ (int result, long value) -> {
+ outResult.value = result;
+ if (result == Result.SUCCESS) prop.setLong(value);
+ });
+ break;
+ }
+ return outResult.value;
+ } finally {
+ traceEnd();
+ }
+ }
+
+ @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 ex) {
+ Slog.e(TAG, "Cannot call update on health HAL", ex);
+ } finally {
+ traceEnd();
+ }
+ });
+ }
+
+ private static class Mutable<T> {
+ public T value;
+ }
+
+ @Override
+ public HealthInfo getHealthInfo() throws RemoteException {
+ IHealth service = mLastService.get();
+ if (service == null) return null;
+ final Mutable<HealthInfo> ret = new Mutable<>();
+ service.getHealthInfo(
+ (result, value) -> {
+ if (result == Result.SUCCESS) {
+ ret.value = value.legacy;
+ }
+ });
+ return ret.value;
+ }
+
+ /**
+ * Start monitoring registration of new IHealth services. Only instance {@link #INSTANCE_VENDOR}
+ * and in device / framework manifest are used. This function should only be called once.
+ *
+ * <p>mCallback.onRegistration() is called synchronously (aka in init thread) before this method
+ * returns if callback is not null.
+ *
+ * @throws RemoteException transaction error when talking to IServiceManager
+ * @throws NoSuchElementException if one of the following cases: - No service manager; - {@link
+ * #INSTANCE_VENDOR} is not in manifests (i.e. not available on this device), or none of
+ * these instances are available to current process.
+ * @throws NullPointerException when supplier is null
+ */
+ @VisibleForTesting
+ HealthServiceWrapperHidl(
+ @Nullable Callback callback,
+ @NonNull IServiceManagerSupplier managerSupplier,
+ @NonNull IHealthSupplier healthSupplier)
+ throws RemoteException, NoSuchElementException, NullPointerException {
+ if (managerSupplier == null || healthSupplier == null) {
+ throw new NullPointerException();
+ }
+ mHealthSupplier = healthSupplier;
+
+ // Initialize mLastService and call callback for the first time (in init thread)
+ IHealth newService = null;
+ traceBegin("HealthInitGetService_" + INSTANCE_VENDOR);
+ try {
+ newService = healthSupplier.get(INSTANCE_VENDOR);
+ } catch (NoSuchElementException ex) {
+ /* ignored, handled below */
+ } finally {
+ traceEnd();
+ }
+ if (newService != null) {
+ mInstanceName = INSTANCE_VENDOR;
+ mLastService.set(newService);
+ }
+
+ if (mInstanceName == null || newService == null) {
+ throw new NoSuchElementException(
+ String.format(
+ "IHealth service instance %s isn't available. Perhaps no permission?",
+ INSTANCE_VENDOR));
+ }
+
+ if (callback != null) {
+ mCallback = callback;
+ mCallback.onRegistration(null, newService, mInstanceName);
+ }
+
+ // Register for future service registrations
+ traceBegin("HealthInitRegisterNotification");
+ mHandlerThread.start();
+ try {
+ managerSupplier
+ .get()
+ .registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+ } finally {
+ traceEnd();
+ }
+ Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
+ }
+
+ @VisibleForTesting
+ public HandlerThread getHandlerThread() {
+ return mHandlerThread;
+ }
+
+ /** Service registration callback. */
+ 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 oldService, IHealth newService, String instance);
+ }
+
+ /**
+ * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service
+ * is not available.
+ */
+ interface IServiceManagerSupplier {
+ default IServiceManager get() throws NoSuchElementException, RemoteException {
+ return IServiceManager.getService();
+ }
+ }
+
+ /**
+ * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service
+ * is not available.
+ */
+ interface IHealthSupplier {
+ default IHealth get(String name) throws NoSuchElementException, RemoteException {
+ return IHealth.getService(name, true /* retry */);
+ }
+ }
+
+ private class Notification extends IServiceNotification.Stub {
+ @Override
+ public final void onRegistration(
+ String interfaceName, String instanceName, boolean preexisting) {
+ if (!IHealth.kInterfaceName.equals(interfaceName)) return;
+ if (!mInstanceName.equals(instanceName)) return;
+
+ // This runnable only runs on mHandlerThread and ordering is ensured, hence
+ // no locking is needed inside the runnable.
+ mHandlerThread
+ .getThreadHandler()
+ .post(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IHealth newService = mHealthSupplier.get(mInstanceName);
+ IHealth oldService = mLastService.getAndSet(newService);
+
+ // preexisting may be inaccurate (race). Check for equality
+ // here.
+ if (Objects.equals(newService, oldService)) return;
+
+ Slog.i(
+ TAG,
+ "health: new instance registered " + mInstanceName);
+ // #init() may be called with null callback. Skip null
+ // callbacks.
+ if (mCallback == null) return;
+ mCallback.onRegistration(
+ oldService, newService, mInstanceName);
+ } catch (NoSuchElementException | RemoteException ex) {
+ Slog.e(
+ TAG,
+ "health: Cannot get instance '"
+ + mInstanceName
+ + "': "
+ + ex.getMessage()
+ + ". Perhaps no permission?");
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/health/Utils.java b/services/core/java/com/android/server/health/Utils.java
new file mode 100644
index 000000000000..fc039eb54950
--- /dev/null
+++ b/services/core/java/com/android/server/health/Utils.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Utils for {@link om.android.server.BatteryService} to deal with health info structs.
+ *
+ * @hide
+ */
+public class Utils {
+ private Utils() {}
+
+ /**
+ * Copy health info struct.
+ *
+ * @param dst destination
+ * @param src source
+ */
+ public static void copy(
+ android.hardware.health.V1_0.HealthInfo dst,
+ android.hardware.health.V1_0.HealthInfo src) {
+ dst.chargerAcOnline = src.chargerAcOnline;
+ dst.chargerUsbOnline = src.chargerUsbOnline;
+ dst.chargerWirelessOnline = src.chargerWirelessOnline;
+ dst.maxChargingCurrent = src.maxChargingCurrent;
+ dst.maxChargingVoltage = src.maxChargingVoltage;
+ dst.batteryStatus = src.batteryStatus;
+ dst.batteryHealth = src.batteryHealth;
+ dst.batteryPresent = src.batteryPresent;
+ dst.batteryLevel = src.batteryLevel;
+ dst.batteryVoltage = src.batteryVoltage;
+ dst.batteryTemperature = src.batteryTemperature;
+ dst.batteryCurrent = src.batteryCurrent;
+ dst.batteryCycleCount = src.batteryCycleCount;
+ dst.batteryFullCharge = src.batteryFullCharge;
+ dst.batteryChargeCounter = src.batteryChargeCounter;
+ dst.batteryTechnology = src.batteryTechnology;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 68b760a1be34..e6fed88a8a4a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -92,7 +92,6 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.health.V2_0.IHealth;
import android.net.ConnectivityManager;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
@@ -190,13 +189,13 @@ import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThrea
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.role.RoleManagerLocal;
-import com.android.server.BatteryService;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.health.HealthServiceWrapper;
import com.android.server.notification.NotificationManagerService;
import com.android.server.stats.pull.IonMemoryUtil.IonAllocations;
import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
@@ -226,6 +225,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
@@ -354,7 +354,7 @@ public class StatsPullAtomService extends SystemService {
private File mBaseDir;
@GuardedBy("mHealthHalLock")
- private BatteryService.HealthServiceWrapper mHealthService;
+ private HealthServiceWrapper mHealthService;
@Nullable
@GuardedBy("mCpuTimePerThreadFreqLock")
@@ -799,10 +799,9 @@ public class StatsPullAtomService extends SystemService {
KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
// Initialize HealthService
- mHealthService = new BatteryService.HealthServiceWrapper();
try {
- mHealthService.init();
- } catch (RemoteException e) {
+ mHealthService = HealthServiceWrapper.create(null);
+ } catch (RemoteException | NoSuchElementException e) {
Slog.e(TAG, "failed to initialize healthHalWrapper");
}
@@ -3975,38 +3974,40 @@ public class StatsPullAtomService extends SystemService {
}
int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) {
- IHealth healthService = mHealthService.getLastService();
- if (healthService == null) {
+ if (mHealthService == null) {
return StatsManager.PULL_SKIP;
}
+ android.hardware.health.V1_0.HealthInfo healthInfo;
try {
- healthService.getHealthInfo((result, value) -> {
- int pulledValue;
- switch(atomTag) {
- case FrameworkStatsLog.BATTERY_LEVEL:
- pulledValue = value.legacy.batteryLevel;
- break;
- case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY:
- pulledValue = value.legacy.batteryChargeCounter;
- break;
- case FrameworkStatsLog.FULL_BATTERY_CAPACITY:
- pulledValue = value.legacy.batteryFullCharge;
- break;
- case FrameworkStatsLog.BATTERY_VOLTAGE:
- pulledValue = value.legacy.batteryVoltage;
- break;
- case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
- pulledValue = value.legacy.batteryCycleCount;
- break;
- default:
- throw new IllegalStateException("Invalid atomTag in healthHal puller: "
- + atomTag);
- }
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue));
- });
+ healthInfo = mHealthService.getHealthInfo();
} catch (RemoteException | IllegalStateException e) {
return StatsManager.PULL_SKIP;
}
+ if (healthInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ int pulledValue;
+ switch (atomTag) {
+ case FrameworkStatsLog.BATTERY_LEVEL:
+ pulledValue = healthInfo.batteryLevel;
+ break;
+ case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY:
+ pulledValue = healthInfo.batteryChargeCounter;
+ break;
+ case FrameworkStatsLog.FULL_BATTERY_CAPACITY:
+ pulledValue = healthInfo.batteryFullCharge;
+ break;
+ case FrameworkStatsLog.BATTERY_VOLTAGE:
+ pulledValue = healthInfo.batteryVoltage;
+ break;
+ case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
+ pulledValue = healthInfo.batteryCycleCount;
+ break;
+ default:
+ return StatsManager.PULL_SKIP;
+ }
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue));
return StatsManager.PULL_SUCCESS;
}
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java
index a2ecbc30ec64..c97a67bad590 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java
@@ -14,19 +14,25 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.health;
-import static junit.framework.Assert.*;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.fail;
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.test.AndroidTestCase;
+import android.os.RemoteException;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -36,36 +42,39 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.NoSuchElementException;
-public class BatteryServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class HealthServiceWrapperTest {
@Mock IServiceManager mMockedManager;
@Mock IHealth mMockedHal;
@Mock IHealth mMockedHal2;
- @Mock BatteryService.HealthServiceWrapper.Callback mCallback;
- @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
- @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier;
- BatteryService.HealthServiceWrapper mWrapper;
+ @Mock HealthServiceWrapperHidl.Callback mCallback;
+ @Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier;
+ @Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier;
+ HealthServiceWrapper mWrapper;
- private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR;
+ private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR;
- @Override
+ @Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
- @Override
+ @After
public void tearDown() {
- if (mWrapper != null)
- mWrapper.getHandlerThread().quitSafely();
+ if (mWrapper != null) mWrapper.getHandlerThread().quitSafely();
}
public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
return new ArgumentMatcher<T>() {
- @Override public boolean matches(T e) {
+ @Override
+ public boolean matches(T e) {
return collection.contains(e);
}
- @Override public String toString() {
+
+ @Override
+ public String toString() {
return collection.toString();
}
};
@@ -73,27 +82,29 @@ public class BatteryServiceTest extends AndroidTestCase {
private void initForInstances(String... instanceNamesArr) throws Exception {
final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
- doAnswer((invocation) -> {
- // technically, preexisting is ignored by
- // BatteryService.HealthServiceWrapper.Notification, but still call it correctly.
- sendNotification(invocation, true);
- sendNotification(invocation, true);
- sendNotification(invocation, false);
- return null;
- }).when(mMockedManager).registerForNotifications(
- eq(IHealth.kInterfaceName),
- argThat(isOneOf(instanceNames)),
- any(IServiceNotification.class));
+ doAnswer(
+ (invocation) -> {
+ // technically, preexisting is ignored by
+ // HealthServiceWrapperHidl.Notification, but still call it correctly.
+ sendNotification(invocation, true);
+ sendNotification(invocation, true);
+ sendNotification(invocation, false);
+ return null;
+ })
+ .when(mMockedManager)
+ .registerForNotifications(
+ eq(IHealth.kInterfaceName),
+ argThat(isOneOf(instanceNames)),
+ any(IServiceNotification.class));
doReturn(mMockedManager).when(mManagerSupplier).get();
- doReturn(mMockedHal) // init calls this
- .doReturn(mMockedHal) // notification 1
- .doReturn(mMockedHal) // notification 2
- .doReturn(mMockedHal2) // notification 3
- .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
- .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames)));
-
- mWrapper = new BatteryService.HealthServiceWrapper();
+ doReturn(mMockedHal) // init calls this
+ .doReturn(mMockedHal) // notification 1
+ .doReturn(mMockedHal) // notification 2
+ .doReturn(mMockedHal2) // notification 3
+ .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
+ .when(mHealthServiceSupplier)
+ .get(argThat(isOneOf(instanceNames)));
}
private void waitHandlerThreadFinish() throws Exception {
@@ -108,16 +119,20 @@ public class BatteryServiceTest extends AndroidTestCase {
private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
throws Exception {
- ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
- IHealth.kInterfaceName,
- (String)invocation.getArguments()[1],
- preexisting);
+ ((IServiceNotification) invocation.getArguments()[2])
+ .onRegistration(
+ IHealth.kInterfaceName, (String) invocation.getArguments()[1], preexisting);
+ }
+
+ private void createWrapper() throws RemoteException {
+ mWrapper = HealthServiceWrapper.create(mCallback, mManagerSupplier, mHealthServiceSupplier);
}
@SmallTest
+ @Test
public void testWrapPreferVendor() throws Exception {
initForInstances(VENDOR);
- mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
+ createWrapper();
waitHandlerThreadFinish();
verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
@@ -125,10 +140,11 @@ public class BatteryServiceTest extends AndroidTestCase {
}
@SmallTest
+ @Test
public void testNoService() throws Exception {
initForInstances("unrelated");
try {
- mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
+ createWrapper();
fail("Expect NoSuchElementException");
} catch (NoSuchElementException ex) {
// expected