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

Commit 07854231 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Replace CompletableFuture with Consumers

This is a requirement from Android API Guidelines

Bug: 295027802
Test: atest FrameworksCoreTests:SystemHealthManagerTest

Change-Id: I0bcc66b1a669d0ad28eaa0c7c1b7df91eda5f361
parent d6f51fdb
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);
        }
    }
}