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

Commit b5ab4bab authored by Ivo Kay's avatar Ivo Kay
Browse files

Wait for GnssPowerStats to be updated before populating atom

Test: atest GnssPowerStatsTests GnssNativeTest; manual
Bug: 340176818
Flag: EXEMPT bugfix
Change-Id: Ie59cc51480d654eb31870fb0239ba4737f1e3061
parent d2f4ecb8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1181,7 +1181,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
                        GnssPsdsDownloader.LONG_TERM_PSDS_SERVER_INDEX));
            }
        } else if ("request_power_stats".equals(command)) {
            mGnssNative.requestPowerStats();
            mGnssNative.requestPowerStats(Runnable::run, powerStats -> {});
        } else {
            Log.w(TAG, "sendExtraCommand: unknown command " + command);
        }
+2 −2
Original line number Diff line number Diff line
@@ -314,9 +314,9 @@ public class GnssManagerService {
            ipw.decreaseIndent();
        }

        GnssPowerStats powerStats = mGnssNative.getPowerStats();
        GnssPowerStats powerStats = mGnssNative.getLastKnownPowerStats();
        if (powerStats != null) {
            ipw.println("Last Power Stats:");
            ipw.println("Last Known Power Stats:");
            ipw.increaseIndent();
            powerStats.dump(fd, ipw, args, mGnssNative.getCapabilities());
            ipw.decreaseIndent();
+65 −57
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.location.gnss;

import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
import android.location.GnssSignalQuality;
@@ -60,7 +61,6 @@ public class GnssMetrics {
    private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * 1e6;
    private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * 1e6;


    private long mLogStartInElapsedRealtimeMs;

    GnssPowerMetrics mGnssPowerMetrics;
@@ -608,8 +608,19 @@ public class GnssMetrics {
        }

        @Override
        public int onPullAtom(int atomTag, List<StatsEvent> data) {
            if (atomTag == FrameworkStatsLog.GNSS_STATS) {
        public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
            switch (atomTag) {
                case FrameworkStatsLog.GNSS_STATS:
                    return pullGnssStats(atomTag, data);
                case FrameworkStatsLog.GNSS_POWER_STATS:
                    return pullGnssPowerStats(atomTag, data);
                default:
                    throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
            }
        }
    }

    private int pullGnssStats(int atomTag, List<StatsEvent> data) {
        data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
                mLocationFailureReportsStatistics.getCount(),
                mLocationFailureReportsStatistics.getLongSum(),
@@ -623,34 +634,36 @@ public class GnssMetrics {
                mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
                mSvStatusReportsUsedInFix, mL5SvStatusReports,
                mL5SvStatusReportsUsedInFix));
            } else if (atomTag == FrameworkStatsLog.GNSS_POWER_STATS) {
                mGnssNative.requestPowerStats();
                GnssPowerStats gnssPowerStats = mGnssNative.getPowerStats();
                if (gnssPowerStats == null) {
        return StatsManager.PULL_SUCCESS;
    }

    private int pullGnssPowerStats(int atomTag, List<StatsEvent> data) {
        GnssPowerStats powerStats = mGnssNative.requestPowerStatsBlocking();
        if (powerStats == null) {
            return StatsManager.PULL_SKIP;
        } else {
            data.add(createPowerStatsEvent(atomTag, powerStats));
            return StatsManager.PULL_SUCCESS;
        }
    }

    private static StatsEvent createPowerStatsEvent(int atomTag,
            @NonNull GnssPowerStats powerStats) {
        double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
                double[] tempGnssPowerStatsOtherModes =
                        gnssPowerStats.getOtherModesEnergyMilliJoule();
                if (tempGnssPowerStatsOtherModes.length < VENDOR_SPECIFIC_POWER_MODES_SIZE) {
                    System.arraycopy(tempGnssPowerStatsOtherModes, 0,
                            otherModesEnergyMilliJoule, 0,
                            tempGnssPowerStatsOtherModes.length);
                } else {
        double[] tempGnssPowerStatsOtherModes = powerStats.getOtherModesEnergyMilliJoule();
        System.arraycopy(tempGnssPowerStatsOtherModes, 0,
                otherModesEnergyMilliJoule, 0,
                            VENDOR_SPECIFIC_POWER_MODES_SIZE);
                }
                data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
                        (long) (gnssPowerStats.getElapsedRealtimeUncertaintyNanos()),
                        (long) (gnssPowerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
                        (long) (gnssPowerStats.getSinglebandTrackingModeEnergyMilliJoule()
                Math.min(tempGnssPowerStatsOtherModes.length, VENDOR_SPECIFIC_POWER_MODES_SIZE));
        return FrameworkStatsLog.buildStatsEvent(atomTag,
                (long) (powerStats.getElapsedRealtimeUncertaintyNanos()),
                (long) (powerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
                (long) (powerStats.getSinglebandTrackingModeEnergyMilliJoule()
                        * CONVERT_MILLI_TO_MICRO),
                        (long) (gnssPowerStats.getMultibandTrackingModeEnergyMilliJoule()
                (long) (powerStats.getMultibandTrackingModeEnergyMilliJoule()
                        * CONVERT_MILLI_TO_MICRO),
                        (long) (gnssPowerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
                (long) (powerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
                        * CONVERT_MILLI_TO_MICRO),
                        (long) (gnssPowerStats.getMultibandAcquisitionModeEnergyMilliJoule()
                (long) (powerStats.getMultibandAcquisitionModeEnergyMilliJoule()
                        * CONVERT_MILLI_TO_MICRO),
                (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
                (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
@@ -661,11 +674,6 @@ public class GnssMetrics {
                (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
                (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
                (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
                        (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO)));
            } else {
                throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
            }
            return StatsManager.PULL_SUCCESS;
        }
                (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO));
    }
}
 No newline at end of file
+80 −6
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.server.location.gnss.hal;

import static com.android.server.location.gnss.GnssManagerService.TAG;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.location.GnssAntennaInfo;
import android.location.GnssCapabilities;
@@ -29,6 +31,7 @@ import android.location.GnssSignalType;
import android.location.GnssStatus;
import android.location.Location;
import android.os.Binder;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;

@@ -46,9 +49,13 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Entry point for most GNSS HAL commands and callbacks.
@@ -140,6 +147,8 @@ public class GnssNative {
    public static final int AGPS_SETID_TYPE_IMSI = 1;
    public static final int AGPS_SETID_TYPE_MSISDN = 2;

    private static final int POWER_STATS_REQUEST_TIMEOUT_MILLIS = 100;

    @IntDef(prefix = "AGPS_SETID_TYPE_", value = {AGPS_SETID_TYPE_NONE, AGPS_SETID_TYPE_IMSI,
            AGPS_SETID_TYPE_MSISDN})
    @Retention(RetentionPolicy.SOURCE)
@@ -289,6 +298,15 @@ public class GnssNative {
                byte responseType, boolean inEmergencyMode, boolean isCachedLocation);
    }

    /** Callback for reporting {@link GnssPowerStats} */
    public interface PowerStatsCallback {
        /**
         * Called when power stats are reported.
         * @param powerStats non-null value when power stats are available, {@code null} otherwise.
         */
        void onReportPowerStats(@Nullable GnssPowerStats powerStats);
    }

    // set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
    // stops output right at 600m/s, depriving this of the information of a device that reaches
    // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
@@ -311,6 +329,8 @@ public class GnssNative {
    @GuardedBy("GnssNative.class")
    private static GnssNative sInstance;

    private final Handler mHandler;

    /**
     * Sets GnssHal instance to use for testing.
     */
@@ -367,6 +387,14 @@ public class GnssNative {
    private NavigationMessageCallbacks[] mNavigationMessageCallbacks =
            new NavigationMessageCallbacks[0];

    private @Nullable GnssPowerStats mLastKnownPowerStats = null;
    private final Object mPowerStatsLock = new Object();
    private final Runnable mPowerStatsTimeoutCallback = () -> {
        Log.d(TAG, "Request for power stats timed out");
        reportGnssPowerStats(null);
    };
    private final List<PowerStatsCallback> mPendingPowerStatsCallbacks = new ArrayList<>();

    // these callbacks may only have a single implementation
    private GeofenceCallbacks mGeofenceCallbacks;
    private TimeCallbacks mTimeCallbacks;
@@ -381,7 +409,6 @@ public class GnssNative {

    private GnssCapabilities mCapabilities = new GnssCapabilities.Builder().build();
    private @GnssCapabilities.TopHalCapabilityFlags int mTopFlags;
    private @Nullable GnssPowerStats mPowerStats = null;
    private int mHardwareYear = 0;
    private @Nullable String mHardwareModelName = null;
    private long mStartRealtimeMs = 0;
@@ -391,6 +418,7 @@ public class GnssNative {
        mGnssHal = Objects.requireNonNull(gnssHal);
        mEmergencyHelper = injector.getEmergencyHelper();
        mConfiguration = configuration;
        mHandler = FgThread.getHandler();
    }

    public void addBaseCallbacks(BaseCallbacks callbacks) {
@@ -532,8 +560,8 @@ public class GnssNative {
    /**
     * Returns the latest power stats from the GNSS HAL.
     */
    public @Nullable GnssPowerStats getPowerStats() {
        return mPowerStats;
    public @Nullable GnssPowerStats getLastKnownPowerStats() {
        return mLastKnownPowerStats;
    }

    /**
@@ -931,10 +959,49 @@ public class GnssNative {

    /**
     * Request an eventual update of GNSS power statistics.
     *
     * @param executor Executor that will run {@code callback}
     * @param callback Called with non-null power stats if they were obtained in time, called with
     *                 {@code null} if stats could not be obtained in time.
     */
    public void requestPowerStats() {
    public void requestPowerStats(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull PowerStatsCallback callback) {
        Preconditions.checkState(mRegistered);
        synchronized (mPowerStatsLock) {
            mPendingPowerStatsCallbacks.add(powerStats -> {
                Binder.withCleanCallingIdentity(
                        () -> executor.execute(() -> callback.onReportPowerStats(powerStats)));
            });
            if (mPendingPowerStatsCallbacks.size() == 1) {
                mGnssHal.requestPowerStats();
                mHandler.postDelayed(mPowerStatsTimeoutCallback,
                        POWER_STATS_REQUEST_TIMEOUT_MILLIS);
            }
        }
    }

    /**
     * Request GNSS power statistics and blocks for a short time waiting for the result.
     *
     * @return non-null power stats, or {@code null} if stats could not be obtained in time.
     */
    public @Nullable GnssPowerStats requestPowerStatsBlocking() {
        AtomicReference<GnssPowerStats> statsWrapper = new AtomicReference<>();
        CountDownLatch latch = new CountDownLatch(1);
        requestPowerStats(Runnable::run, powerStats -> {
            statsWrapper.set(powerStats);
            latch.countDown();
        });

        try {
            latch.await(POWER_STATS_REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Log.d(TAG, "Interrupted while waiting for power stats");
            Thread.currentThread().interrupt();
        }

        return statsWrapper.get();
    }

    /**
@@ -1167,7 +1234,14 @@ public class GnssNative {

    @NativeEntryPoint
    void reportGnssPowerStats(GnssPowerStats powerStats) {
        mPowerStats = powerStats;
        synchronized (mPowerStatsLock) {
            mHandler.removeCallbacks(mPowerStatsTimeoutCallback);
            if (powerStats != null) {
                mLastKnownPowerStats = powerStats;
            }
            mPendingPowerStatsCallbacks.forEach(cb -> cb.onReportPowerStats(powerStats));
            mPendingPowerStatsCallbacks.clear();
        }
    }

    @NativeEntryPoint
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.location.gnss.hal;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.location.gnss.GnssConfiguration;
import com.android.server.location.gnss.GnssPowerStats;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.TestInjector;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Objects;
import java.util.concurrent.Executor;

@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class GnssNativeTest {

    private @Mock Context mContext;
    private @Mock GnssConfiguration mMockConfiguration;
    private FakeGnssHal mFakeGnssHal;
    private GnssNative mGnssNative;

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

        mFakeGnssHal = new FakeGnssHal();
        GnssNative.setGnssHalForTest(mFakeGnssHal);
        Injector injector = new TestInjector(mContext);
        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
        mGnssNative.register();
    }

    @Test
    public void testRequestPowerStats_onNull_executesCallbackWithNull() {
        mFakeGnssHal.setPowerStats(null);
        Executor executor = spy(Runnable::run);
        GnssNative.PowerStatsCallback callback = spy(stats -> {});

        mGnssNative.requestPowerStats(executor, callback);

        verify(executor).execute(any());
        verify(callback).onReportPowerStats(null);
    }

    @Test
    public void testRequestPowerStats_onPowerStats_executesCallbackWithStats() {
        GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});
        mFakeGnssHal.setPowerStats(powerStats);
        Executor executor = spy(Runnable::run);
        GnssNative.PowerStatsCallback callback = spy(stats -> {});

        mGnssNative.requestPowerStats(executor, callback);

        verify(executor).execute(any());
        verify(callback).onReportPowerStats(powerStats);
    }

    @Test
    public void testRequestPowerStatsBlocking_onNull_returnsNull() {
        mFakeGnssHal.setPowerStats(null);

        assertThat(mGnssNative.requestPowerStatsBlocking()).isNull();
    }

    @Test
    public void testRequestPowerStatsBlocking_onPowerStats_returnsStats() {
        GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});
        mFakeGnssHal.setPowerStats(powerStats);

        assertThat(mGnssNative.requestPowerStatsBlocking()).isEqualTo(powerStats);
    }

    @Test
    public void testGetLastKnownPowerStats_onNull_preservesLastKnownPowerStats() {
        GnssPowerStats powerStats = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 10});

        mGnssNative.reportGnssPowerStats(powerStats);
        assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats);

        mGnssNative.reportGnssPowerStats(null);
        assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats);
    }

    @Test
    public void testGetLastKnownPowerStats_onPowerStats_updatesLastKnownPowerStats() {
        GnssPowerStats powerStats1 = new GnssPowerStats(1, 2, 3, 4, 5, 6, 7, 8, new double[]{9, 0});
        GnssPowerStats powerStats2 = new GnssPowerStats(2, 3, 4, 5, 6, 7, 8, 9, new double[]{0, 9});

        mGnssNative.reportGnssPowerStats(powerStats1);
        assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats1);

        mGnssNative.reportGnssPowerStats(powerStats2);
        assertThat(mGnssNative.getLastKnownPowerStats()).isEqualTo(powerStats2);
    }

}