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

Commit 8902c87e authored by Yifan Hong's avatar Yifan Hong
Browse files

BatteryService: remove HIDL-ness

Move HIDL-specifics of BatteryService to a list of classes
under com.android.server.health, so that we can replace
with the AIDL HAL easily.

HealthInfo types are kept as HIDL types. This will be
transformed to AIDL types in the next CL.

Also move BatteryServiceTest to HealthServiceWrapperTest,
since it essentially test just HealthServiceWrapper. Moving
the test to the proper package allows us to make things
package private.

Also make HealthServiceWrapperTest JUnit 4, since AndroidTestCase
is deprecated.

Test: builds
Test: atest FrameworksServicesTests
Bug: 177269435

Change-Id: Ie4dff341594fe9185a188ad963f29ade5d199e85
parent e6817902
Loading
Loading
Loading
Loading
+5 −336
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server;
package com.android.server;


import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copy;


import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager;
@@ -26,13 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.ContentObserver;
import android.hardware.health.V1_0.HealthInfo;
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.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.metrics.LogMaker;
import android.os.BatteryManager;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryManagerInternal;
@@ -44,7 +39,6 @@ import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.DropBoxManager;
import android.os.FileUtils;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IBinder;
import android.os.OsProtoEnums;
import android.os.OsProtoEnums;
@@ -62,15 +56,14 @@ import android.provider.Settings;
import android.service.battery.BatteryServiceDumpProto;
import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.sysprop.PowerProperties;
import android.util.EventLog;
import android.util.EventLog;
import android.util.MutableInt;
import android.util.Slog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoOutputStream;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.am.BatteryStatsService;
import com.android.server.health.HealthServiceWrapper;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.lights.LogicalLight;


@@ -82,8 +75,6 @@ import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.NoSuchElementException;
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
 * <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 ActivityManagerInternal mActivityManagerInternal;


    private HealthServiceWrapper mHealthServiceWrapper;
    private HealthServiceWrapper mHealthServiceWrapper;
    private HealthHalCallback mHealthHalCallback;
    private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
    private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
    private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
    private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
    private long mLastBatteryLevelChangedSentMs;
    private long mLastBatteryLevelChangedSentMs;
@@ -274,13 +264,9 @@ public final class BatteryService extends SystemService {


    private void registerHealthCallback() {
    private void registerHealthCallback() {
        traceBegin("HealthInitWrapper");
        traceBegin("HealthInitWrapper");
        mHealthServiceWrapper = new HealthServiceWrapper();
        mHealthHalCallback = new HealthHalCallback();
        // IHealth is lazily retrieved.
        // IHealth is lazily retrieved.
        try {
        try {
            mHealthServiceWrapper.init(mHealthHalCallback,
            mHealthServiceWrapper = HealthServiceWrapper.create(this::update);
                    new HealthServiceWrapper.IServiceManagerSupplier() {},
                    new HealthServiceWrapper.IHealthSupplier() {});
        } catch (RemoteException ex) {
        } catch (RemoteException ex) {
            Slog.e(TAG, "health: cannot register callback. (RemoteException)");
            Slog.e(TAG, "health: cannot register callback. (RemoteException)");
            throw ex.rethrowFromSystemServer();
            throw ex.rethrowFromSystemServer();
@@ -454,25 +440,6 @@ public final class BatteryService extends SystemService {
        traceEnd();
        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) {
    private static int plugType(HealthInfo healthInfo) {
        if (healthInfo.chargerAcOnline) {
        if (healthInfo.chargerAcOnline) {
            return BatteryManager.BATTERY_PLUGGED_AC;
            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 {
    private final class BinderService extends Binder {
        @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1265,71 +1174,11 @@ public final class BatteryService extends SystemService {
    private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
    private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
        @Override
        @Override
        public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
        public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
            traceBegin("HealthGetProperty");
            return mHealthServiceWrapper.getProperty(id, prop);
            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();
            }
        }
        }
        @Override
        @Override
        public void scheduleUpdate() throws RemoteException {
        public void scheduleUpdate() throws RemoteException {
            mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> {
            mHealthServiceWrapper.scheduleUpdate();
                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();
                }
            });
        }
        }
    }
    }


@@ -1418,184 +1267,4 @@ public final class BatteryService extends SystemService {
            BatteryService.this.suspendBatteryInput();
            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?");
                        }
                    }
                });
            }
        }
    }
}
}
+115 −0
Original line number Original line 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.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();
        }
    }
}
+32 −0
Original line number Original line 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;

/**
 * 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);
}
+109 −0

File added.

Preview size limit exceeded, changes collapsed.

+317 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading