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

Commit 2e939dd6 authored by Mayank Dandwani's avatar Mayank Dandwani Committed by Android (Google) Code Review
Browse files

Merge changes from topic "rate limit battery changed broadcast" into main

* changes:
  Don't trigger batterychanged broadcast when ChargeCounter is updated.
  Rate limit the battery changed broadcast.
parents e8082ec7 9b26e51d
Loading
Loading
Loading
Loading
+304 −71

File changed.

Preview size limit exceeded, changes collapsed.

+11 −0
Original line number Diff line number Diff line
@@ -67,3 +67,14 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    namespace: "backstage_power"
    name: "rate_limit_battery_changed_broadcast"
    description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates"
    bug: "362337621"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+338 −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;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.health.HealthInfo;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.am.BatteryStatsService;
import com.android.server.flags.Flags;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;

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

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@RunWith(AndroidJUnit4.class)
public class BatteryServiceTest {

    private static final int CURRENT_BATTERY_VOLTAGE = 3000;
    private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029;
    private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030;
    private static final int CURRENT_BATTERY_TEMP = 300;
    private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305;
    private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310;
    private static final int CURRENT_BATTERY_HEALTH = 2;
    private static final int UPDATED_BATTERY_HEALTH = 3;
    private static final int CURRENT_CHARGE_COUNTER = 4680000;
    private static final int UPDATED_CHARGE_COUNTER = 4218000;
    private static final int HANDLER_IDLE_TIME_MS = 5000;
    @Rule
    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
            .mockStatic(SystemProperties.class)
            .mockStatic(ActivityManager.class)
            .mockStatic(BatteryStatsService.class)
            .build();
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
    @Mock
    private Context mContextMock;
    @Mock
    private LightsManager mLightsManagerMock;
    @Mock
    private ActivityManagerInternal mActivityManagerInternalMock;
    @Mock
    private IBatteryStats mIBatteryStatsMock;

    private BatteryService mBatteryService;
    private String mSystemUiPackage;

    /**
     * Creates a mock and registers it to {@link LocalServices}.
     */
    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
        LocalServices.removeServiceForTest(clazz);
        LocalServices.addService(clazz, mock);
    }

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

        mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext()
                .getResources().getString(R.string.config_systemUi);

        when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
        when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true);
        when(mContextMock.getResources()).thenReturn(
                InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
        ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock);

        doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(),
                anyInt(), anyInt(), anyInt(), anyInt(), anyLong());
        doNothing().when(() -> SystemProperties.set(anyString(), anyString()));
        doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(),
                eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
                eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)));

        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);

        createBatteryService();
    }

    @Test
    public void createBatteryService_withNullLooper_throwsNullPointerException() {
        assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock));
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(0);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() {
        mBatteryService.update(
                createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                        CURRENT_CHARGE_COUNTER,
                        CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(0);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyVoltageUpdated_broadcastSent() {
        mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
        mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
                        CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(0);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void tempUpdated_broadcastSent() {
        long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
        mBatteryService.update(
                createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
                        UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
        verifyNumberOfTimesBroadcastSent(1);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                        CURRENT_CHARGE_COUNTER,
                        UPDATED_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);

        // updating counter just after the health update does not triggers broadcast.
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                        UPDATED_CHARGE_COUNTER,
                        UPDATED_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);
    }

    @Test
    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyChargeCounterUpdated_broadcastNotSent() {
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                        UPDATED_CHARGE_COUNTER,
                        CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(0);
    }

    @Test
    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
                        UPDATED_CHARGE_COUNTER,
                        CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(0);
    }

    @Test
    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
    public void onlyChargeCounterUpdated_broadcastSent() {
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                        UPDATED_CHARGE_COUNTER,
                        CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);
    }

    private HealthInfo createHealthInfo(
            int batteryVoltage,
            int batteryTemperature,
            int batteryChargeCounter,
            int batteryHealth) {
        HealthInfo h = new HealthInfo();
        h.batteryVoltageMillivolts = batteryVoltage;
        h.batteryTemperatureTenthsCelsius = batteryTemperature;
        h.batteryChargeCounterUah = batteryChargeCounter;
        h.batteryStatus = 5;
        h.batteryHealth = batteryHealth;
        h.batteryPresent = true;
        h.batteryLevel = 100;
        h.maxChargingCurrentMicroamps = 298125;
        h.batteryCurrentAverageMicroamps = -2812;
        h.batteryCurrentMicroamps = 298125;
        h.maxChargingVoltageMicrovolts = 3000;
        h.batteryCycleCount = 50;
        h.chargingState = 4;
        h.batteryCapacityLevel = 100;
        return h;
    }

    // Creates a new battery service objects and sets the initial values.
    private void createBatteryService() throws InterruptedException {
        final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest");
        handlerThread.start();

        mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper());

        // trigger the update to set the initial values.
        mBatteryService.update(
                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                        CURRENT_CHARGE_COUNTER,
                        CURRENT_BATTERY_HEALTH));

        waitForHandlerToExecute();
    }

    private void waitForHandlerToExecute() {
        final CountDownLatch latch = new CountDownLatch(1);
        mBatteryService.getHandlerForTest().post(latch::countDown);
        boolean isExecutionComplete = false;

        try {
            isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            fail("Handler interrupted before executing the message " + e);
        }

        assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete);
    }

    private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) {
        // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test
        // setUp.
        verify(() -> ActivityManager.broadcastStickyIntent(any(),
                        eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
                        eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)),
                times(++numberOfTimes));
    }
}