Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70ba2506 authored by Automerger Merge Worker's avatar Automerger Merge Worker Committed by Android (Google) Code Review
Browse files

Merge "Merge "health: BatteryService etc. use health AIDL HAL" am: 104adeeb...

Merge "Merge "health: BatteryService etc. use health AIDL HAL" am: 104adeeb am: 2cbfa143 am: b7b98179 am: 7a6c6d2a"
parents 41885f1e 935a7803
Loading
Loading
Loading
Loading
+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);
        }
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@ public abstract class HealthServiceWrapper {
    public static HealthServiceWrapper create(@Nullable HealthInfoCallback healthInfoCallback)
            throws RemoteException, NoSuchElementException {
        return create(
                healthInfoCallback == null ? null : new HealthRegCallbackAidl(healthInfoCallback),
                new HealthServiceWrapperAidl.ServiceManagerStub() {},
                healthInfoCallback == null ? null : new HealthHalCallbackHidl(healthInfoCallback),
                new HealthServiceWrapperHidl.IServiceManagerSupplier() {},
                new HealthServiceWrapperHidl.IHealthSupplier() {});
@@ -89,6 +91,9 @@ public abstract class HealthServiceWrapper {
    /**
     * Create a new HealthServiceWrapper instance for testing.
     *
     * @param aidlRegCallback callback for AIDL service registration, or {@code null} if the client
     *     does not care about AIDL service registration notifications
     * @param aidlServiceManager Stub for AIDL ServiceManager
     * @param hidlRegCallback callback for HIDL service registration, or {@code null} if the client
     *     does not care about HIDL service registration notifications
     * @param hidlServiceManagerSupplier supplier of HIDL service manager
@@ -97,10 +102,17 @@ public abstract class HealthServiceWrapper {
     */
    @VisibleForTesting
    static @NonNull HealthServiceWrapper create(
            @Nullable HealthRegCallbackAidl aidlRegCallback,
            @NonNull HealthServiceWrapperAidl.ServiceManagerStub aidlServiceManager,
            @Nullable HealthServiceWrapperHidl.Callback hidlRegCallback,
            @NonNull HealthServiceWrapperHidl.IServiceManagerSupplier hidlServiceManagerSupplier,
            @NonNull HealthServiceWrapperHidl.IHealthSupplier hidlHealthSupplier)
            throws RemoteException, NoSuchElementException {
        try {
            return new HealthServiceWrapperAidl(aidlRegCallback, aidlServiceManager);
        } catch (NoSuchElementException e) {
            // Ignore, try HIDL
        }
        return new HealthServiceWrapperHidl(
                hidlRegCallback, hidlServiceManagerSupplier, hidlHealthSupplier);
    }
+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);
                            });
        }
    }
}
+104 −15
Original line number Diff line number Diff line
@@ -19,11 +19,12 @@ package com.android.server.health;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.fail;

import static org.mockito.AdditionalMatchers.not;
import static org.mockito.Mockito.*;

import android.hardware.health.V2_0.IHealth;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IServiceCallback;
import android.os.RemoteException;

import androidx.test.filters.SmallTest;
@@ -44,28 +45,47 @@ import java.util.NoSuchElementException;

@RunWith(AndroidJUnit4.class)
public class HealthServiceWrapperTest {

    @Mock IServiceManager mMockedManager;
    @Mock IHealth mMockedHal;
    @Mock IHealth mMockedHal2;
    @Mock android.hardware.health.V2_0.IHealth mMockedHal;
    @Mock android.hardware.health.V2_0.IHealth mMockedHal2;

    @Mock HealthServiceWrapperHidl.Callback mCallback;
    @Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier;
    @Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier;

    @Mock android.hardware.health.IHealth.Stub mMockedAidlHal;
    @Mock android.hardware.health.IHealth.Stub mMockedAidlHal2;
    @Mock HealthServiceWrapperAidl.ServiceManagerStub mMockedAidlManager;
    @Mock HealthRegCallbackAidl mRegCallbackAidl;

    HealthServiceWrapper mWrapper;

    private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR;
    private static final String AIDL_SERVICE_NAME = HealthServiceWrapperAidl.SERVICE_NAME;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        // Mocks the conversion between IHealth and IBinder.
        when(mMockedAidlHal.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal
        when(mMockedAidlHal2.asBinder()).thenCallRealMethod(); // returns mMockedAidlHal2
        when(mMockedAidlHal.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
                .thenReturn(mMockedAidlHal);
        when(mMockedAidlHal2.queryLocalInterface(android.hardware.health.IHealth.DESCRIPTOR))
                .thenReturn(mMockedAidlHal2);
    }

    @After
    public void tearDown() {
        validateMockitoUsage();
        if (mWrapper != null) mWrapper.getHandlerThread().quitSafely();
    }

    public static <T> ArgumentMatcher<T> isOneOf(T[] collection) {
        return isOneOf(Arrays.asList(collection));
    }

    public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
        return new ArgumentMatcher<T>() {
            @Override
@@ -75,13 +95,39 @@ public class HealthServiceWrapperTest {

            @Override
            public String toString() {
                return collection.toString();
                return "is one of " + collection.toString();
            }
        };
    }

    private void initForInstances(String... instanceNamesArr) throws Exception {
        final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
    /**
     * Set up mock objects to pretend that the given AIDL and HIDL instances exists.
     *
     * <p>Also, when registering service notifications, the mocked service managers immediately
     * sends 3 registration notifications, including 2 referring to the original HAL and 1 referring
     * to the new HAL.
     *
     * @param aidlInstances e.g. {"android.hardware.health.IHealth/default"}
     * @param hidlInstances e.g. {"default", "backup"}
     * @throws Exception
     */
    private void initForInstances(String[] aidlInstances, String[] hidlInstances) throws Exception {
        doAnswer(
                (invocation) -> {
                    sendAidlRegCallback(invocation, mMockedAidlHal);
                    sendAidlRegCallback(invocation, mMockedAidlHal);
                    sendAidlRegCallback(invocation, mMockedAidlHal2);
                    return null;
                })
                .when(mMockedAidlManager)
                .registerForNotifications(
                        argThat(isOneOf(aidlInstances)), any(IServiceCallback.class));
        when(mMockedAidlManager.waitForDeclaredService(argThat(isOneOf(aidlInstances))))
                .thenReturn(mMockedAidlHal)
                .thenThrow(new RuntimeException("waitForDeclaredService called more than once"));
        when(mMockedAidlManager.waitForDeclaredService(not(argThat(isOneOf(aidlInstances)))))
                .thenReturn(null);

        doAnswer(
                (invocation) -> {
                    // technically, preexisting is ignored by
@@ -93,8 +139,8 @@ public class HealthServiceWrapperTest {
                })
                .when(mMockedManager)
                .registerForNotifications(
                        eq(IHealth.kInterfaceName),
                        argThat(isOneOf(instanceNames)),
                        eq(android.hardware.health.V2_0.IHealth.kInterfaceName),
                        argThat(isOneOf(hidlInstances)),
                        any(IServiceNotification.class));

        doReturn(mMockedManager).when(mManagerSupplier).get();
@@ -104,7 +150,7 @@ public class HealthServiceWrapperTest {
                .doReturn(mMockedHal2) // notification 3
                .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
                .when(mHealthServiceSupplier)
                .get(argThat(isOneOf(instanceNames)));
                .get(argThat(isOneOf(hidlInstances)));
    }

    private void waitHandlerThreadFinish() throws Exception {
@@ -121,19 +167,62 @@ public class HealthServiceWrapperTest {
            throws Exception {
        ((IServiceNotification) invocation.getArguments()[2])
                .onRegistration(
                        IHealth.kInterfaceName, (String) invocation.getArguments()[1], preexisting);
                        android.hardware.health.V2_0.IHealth.kInterfaceName,
                        (String) invocation.getArguments()[1],
                        preexisting);
    }

    private static void sendAidlRegCallback(
            InvocationOnMock invocation, android.hardware.health.IHealth service) throws Exception {
        ((IServiceCallback) invocation.getArguments()[1])
                .onRegistration((String) invocation.getArguments()[0], service.asBinder());
    }

    private void createWrapper() throws RemoteException {
        mWrapper = HealthServiceWrapper.create(mCallback, mManagerSupplier, mHealthServiceSupplier);
        mWrapper =
                HealthServiceWrapper.create(
                        mRegCallbackAidl,
                        mMockedAidlManager,
                        mCallback,
                        mManagerSupplier,
                        mHealthServiceSupplier);
    }

    @SmallTest
    @Test
    public void 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));
@@ -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");