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

Commit 40fecedf authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge changes from topic "health-service-wrapper-test" am: 6057fe80 am:...

Merge changes from topic "health-service-wrapper-test" am: 6057fe80 am: a43024b6 am: 593bc644 am: 64931941

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1883488

Change-Id: I01e6519538aac0b222cd345dd7dd64b28b613bfb
parents 69a32ce6 64931941
Loading
Loading
Loading
Loading
+5 −336
Original line number Diff line number Diff line
@@ -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?");
                        }
                    }
                });
            }
        }
    }
}
+115 −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.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 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);
}
+108 −0

File added.

Preview size limit exceeded, changes collapsed.

+311 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading