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

Commit 5407cae4 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov Committed by Android (Google) Code Review
Browse files

Merge "Replace CompletableFuture with Consumers" into main

parents 61842268 07854231
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an
 * EnergyConsumer, as defined in
 * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a>
 *
 * @hide
 */
public final class PowerMonitor implements Parcelable {
@@ -92,6 +96,7 @@ public final class PowerMonitor implements Parcelable {
        return 0;
    }

    @NonNull
    public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
        @Override
        public PowerMonitor createFromParcel(@NonNull Parcel in) {
+7 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.os;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;

import java.util.Arrays;
@@ -43,8 +44,8 @@ public final class PowerMonitorReadings {
     * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
     * @hide
     */
    public PowerMonitorReadings(PowerMonitor[] powerMonitors,
            long[] energyUws, long[] timestampsMs) {
    public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
            @NonNull long[] energyUws, @NonNull long[] timestampsMs) {
        mPowerMonitors = powerMonitors;
        mEnergyUws = energyUws;
        mTimestampsMs = timestampsMs;
@@ -55,7 +56,7 @@ public final class PowerMonitorReadings {
     * Does not persist across reboots.
     * Represents total energy: both on-battery and plugged-in.
     */
    public long getConsumedEnergyUws(PowerMonitor powerMonitor) {
    public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) {
        int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
        if (offset >= 0) {
            return mEnergyUws[offset];
@@ -64,9 +65,10 @@ public final class PowerMonitorReadings {
    }

    /**
     * Elapsed realtime when the snapshot was taken.
     * Elapsed realtime, in milliseconds, when the snapshot was taken.
     */
    public long getTimestampMs(PowerMonitor powerMonitor) {
    @ElapsedRealtimeLong
    public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
        int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
        if (offset >= 0) {
            return mTimestampsMs[offset];
+96 −58
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.content.Context;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.IPowerStatsService;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
@@ -36,8 +38,8 @@ import com.android.internal.app.IBatteryStats;

import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.function.Consumer;

/**
 * Provides access to data about how various system resources are used by applications.
@@ -62,7 +64,8 @@ public class SystemHealthManager {
    private final IBatteryStats mBatteryStats;
    @Nullable
    private final IPowerStatsService mPowerStats;
    private PowerMonitor[] mPowerMonitorsInfo;
    private List<PowerMonitor> mPowerMonitorsInfo;
    private final Object mPowerMonitorsLock = new Object();

    /**
     * Construct a new SystemHealthManager object.
@@ -161,107 +164,142 @@ public class SystemHealthManager {
     * @hide
     */
    @NonNull
    public PowerMonitor[] getSupportedPowerMonitors() {
        synchronized (this) {
    public List<PowerMonitor> getSupportedPowerMonitors() {
        synchronized (mPowerMonitorsLock) {
            if (mPowerMonitorsInfo != null) {
                return mPowerMonitorsInfo;
            }

            CompletableFuture<PowerMonitor[]> future = new CompletableFuture<>();
            getSupportedPowerMonitors(future);
            try {
                return future.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
        }
        ConditionVariable lock = new ConditionVariable();
        // Populate mPowerMonitorsInfo by side-effect
        getSupportedPowerMonitors(null, unused -> lock.open());
        lock.block();

        synchronized (mPowerMonitorsLock) {
            return mPowerMonitorsInfo;
        }
    }

    /**
     * Retrieves a list of supported power monitors, see {@link #getSupportedPowerMonitors()}
     * Asynchronously retrieves a list of supported power monitors, see
     * {@link #getSupportedPowerMonitors()}
     *
     * @param handler optional Handler to deliver the callback. If not supplied, the callback
     *                may be invoked on an arbitrary thread.
     * @param onResult callback for the result
     *
     * @hide
     */
    public void getSupportedPowerMonitors(@NonNull CompletableFuture<PowerMonitor[]> future) {
        synchronized (this) {
    public void getSupportedPowerMonitors(@Nullable Handler handler,
            @NonNull Consumer<List<PowerMonitor>> onResult) {
        final List<PowerMonitor> result;
        synchronized (mPowerMonitorsLock) {
            if (mPowerMonitorsInfo != null) {
                future.complete(mPowerMonitorsInfo);
                return;
                result = mPowerMonitorsInfo;
            } else if (mPowerStats == null) {
                mPowerMonitorsInfo = List.of();
                result = mPowerMonitorsInfo;
            } else {
                result = null;
            }
        }
        if (result != null) {
            if (handler != null) {
                handler.post(() -> onResult.accept(result));
            } else {
                onResult.accept(result);
            }
            try {
                if (mPowerStats == null) {
                    mPowerMonitorsInfo = new PowerMonitor[0];
                    future.complete(mPowerMonitorsInfo);
            return;
        }

                mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
        try {
            mPowerStats.getSupportedPowerMonitors(new ResultReceiver(handler) {
                @Override
                protected void onReceiveResult(int resultCode, Bundle resultData) {
                        synchronized (this) {
                            mPowerMonitorsInfo = resultData.getParcelableArray(
                    PowerMonitor[] array = resultData.getParcelableArray(
                            IPowerStatsService.KEY_MONITORS, PowerMonitor.class);
                    List<PowerMonitor> result = array != null ? Arrays.asList(array) : List.of();
                    synchronized (mPowerMonitorsLock) {
                        mPowerMonitorsInfo = result;
                    }
                        future.complete(mPowerMonitorsInfo);
                    onResult.accept(result);
                }
            });
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    }

    /**
     * Retrieves the accumulated power consumption reported by the specified power monitors.
     *
     * @param powerMonitors power monitors to be returned.
     *
     * @hide
     */
    @NonNull
    public PowerMonitorReadings getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors) {
        CompletableFuture<PowerMonitorReadings> future = new CompletableFuture<>();
        getPowerMonitorReadings(powerMonitors, future);
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
    public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) {
        PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1];
        RuntimeException[] outException = new RuntimeException[1];
        ConditionVariable lock = new ConditionVariable();
        getPowerMonitorReadings(powerMonitors, null,
                pms -> {
                    outReadings[0] = pms;
                    lock.open();
                },
                error -> {
                    outException[0] = error;
                    lock.open();
                }
        );
        lock.block();
        if (outException[0] != null) {
            throw outException[0];
        }
        return outReadings[0];
    }

    private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
            Comparator.comparingInt(pm -> pm.index);

    /**
     * Asynchronously retrieves the accumulated power consumption reported by the specified power
     * monitors.
     *
     * @param powerMonitors power monitors to be retrieved.
     * @param handler       optional Handler to deliver the callbacks. If not supplied, the callback
     *                      may be invoked on an arbitrary thread.
     * @param onSuccess     callback for the result
     * @param onError       callback invoked in case of an error
     *
     * @hide
     */
    public void getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
            @NonNull CompletableFuture<PowerMonitorReadings> future) {
    public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
            @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
            @NonNull Consumer<RuntimeException> onError) {
        if (mPowerStats == null) {
            future.completeExceptionally(
                    new IllegalArgumentException("Unsupported power monitor"));
            onError.accept(new IllegalArgumentException("Unsupported power monitor"));
            return;
        }

        Arrays.sort(powerMonitors, POWER_MONITOR_COMPARATOR);
        int[] indices = new int[powerMonitors.length];
        for (int i = 0; i < powerMonitors.length; i++) {
            indices[i] = powerMonitors[i].index;
        PowerMonitor[] powerMonitorsArray =
                powerMonitors.toArray(new PowerMonitor[powerMonitors.size()]);
        Arrays.sort(powerMonitorsArray, POWER_MONITOR_COMPARATOR);
        int[] indices = new int[powerMonitors.size()];
        for (int i = 0; i < powerMonitors.size(); i++) {
            indices[i] = powerMonitorsArray[i].index;
        }
        try {
            mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
            mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(handler) {
                @Override
                protected void onReceiveResult(int resultCode, Bundle resultData) {
                    if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
                        future.complete(new PowerMonitorReadings(powerMonitors,
                        onSuccess.accept(new PowerMonitorReadings(powerMonitorsArray,
                                resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
                                resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS)));
                    } else if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
                        future.completeExceptionally(
                                new IllegalArgumentException("Unsupported power monitor"));
                        onError.accept(new IllegalArgumentException("Unsupported power monitor"));
                    } else {
                        future.completeExceptionally(
                                new IllegalStateException(
                        onError.accept(new IllegalStateException(
                                "Unrecognized result code " + resultCode));
                    }
                }
+64 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static androidx.test.InstrumentationRegistry.getContext;

import static com.google.common.truth.Truth.assertThat;

import android.os.ConditionVariable;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;

@@ -29,13 +30,16 @@ import java.util.ArrayList;
import java.util.List;

public class SystemHealthManagerTest {
    private List<PowerMonitor> mPowerMonitorInfo;
    private PowerMonitorReadings mReadings;
    private RuntimeException mException;

    @Test
    public void getPowerMonitors() {
        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
        PowerMonitor[] powerMonitorInfo = shm.getSupportedPowerMonitors();
        List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
        assertThat(powerMonitorInfo).isNotNull();
        if (powerMonitorInfo.length == 0) {
        if (powerMonitorInfo.isEmpty()) {
            // This device does not support PowerStats HAL
            return;
        }
@@ -50,20 +54,73 @@ public class SystemHealthManagerTest {
            }
        }

        List<PowerMonitor> pmis = new ArrayList<>();
        List<PowerMonitor> selectedMonitors = new ArrayList<>();
        if (consumerMonitor != null) {
            pmis.add(consumerMonitor);
            selectedMonitors.add(consumerMonitor);
        }
        if (measurementMonitor != null) {
            pmis.add(measurementMonitor);
            selectedMonitors.add(measurementMonitor);
        }

        PowerMonitor[] selectedMonitors = pmis.toArray(new PowerMonitor[0]);
        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);

        for (PowerMonitor monitor : selectedMonitors) {
            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
            assertThat(readings.getTimestampMs(monitor)).isGreaterThan(0);
            assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
        }
    }

    @Test
    public void getPowerMonitorsAsync() {
        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
        ConditionVariable done = new ConditionVariable();
        shm.getSupportedPowerMonitors(null, pms -> {
            mPowerMonitorInfo = pms;
            done.open();
        });
        done.block();
        assertThat(mPowerMonitorInfo).isNotNull();
        if (mPowerMonitorInfo.isEmpty()) {
            // This device does not support PowerStats HAL
            return;
        }

        PowerMonitor consumerMonitor = null;
        PowerMonitor measurementMonitor = null;
        for (PowerMonitor pmi : mPowerMonitorInfo) {
            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
                measurementMonitor = pmi;
            } else {
                consumerMonitor = pmi;
            }
        }

        List<PowerMonitor> selectedMonitors = new ArrayList<>();
        if (consumerMonitor != null) {
            selectedMonitors.add(consumerMonitor);
        }
        if (measurementMonitor != null) {
            selectedMonitors.add(measurementMonitor);
        }

        done.close();
        shm.getPowerMonitorReadings(selectedMonitors, null,
                readings -> {
                    mReadings = readings;
                    done.open();
                },
                exception -> {
                    mException = exception;
                    done.open();
                }
        );
        done.block();

        assertThat(mException).isNull();

        for (PowerMonitor monitor : selectedMonitors) {
            assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
            assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
        }
    }
}