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

Commit 431ae3b8 authored by mayankkk's avatar mayankkk Committed by Mayank Dandwani
Browse files

Rate limit the battery changed broadcast.

Bug: 362337621
Test: atest BatteryServiceTest
Flag: com.android.server.flags.rate_limit_battery_changed_broadcast
Change-Id: I9e7a3bab4282e50c443a51c9bb98059ececd18ef
parent 8c7d7f7a
Loading
Loading
Loading
Loading
+301 −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
    }
}
+293 −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 HANDLER_IDLE_TIME_MS = 1000;
    @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_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_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_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_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,
                        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,
                        UPDATED_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);

        // updating the voltage just after the health update does not triggers the broadcast.
        mBatteryService.update(
                createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                        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_BATTERY_HEALTH));

        waitForHandlerToExecute();

        verifyNumberOfTimesBroadcastSent(1);
    }

    private HealthInfo createHealthInfo(
            int batteryVoltage,
            int batteryTemperature,
            int batteryHealth) {
        HealthInfo h = new HealthInfo();
        h.batteryVoltageMillivolts = batteryVoltage;
        h.batteryTemperatureTenthsCelsius = batteryTemperature;
        h.batteryChargeCounterUah = 4680000;
        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() {
        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_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));
    }

}