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

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

Merge "Add support for suspending battery charging"

parents 4e8a7a8b 6c922da5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@
    <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
    <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
    <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
    <uses-permission android:name="android.permission.DEVICE_POWER"/>
    <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
+130 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.internal.os;

import static org.junit.Assert.assertTrue;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build;
import android.os.ConditionVariable;

import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.compatibility.common.util.SystemUtil;

import com.google.android.collect.Sets;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Set;

@RunWith(AndroidJUnit4.class)
public class BatteryInputSuspendTest {

    public static final Set<String> SUPPORTED_DEVICES = Sets.newArraySet(
            "blueline",
            "crosshatch",
            "coral"
    );

    private ChargerStateMonitor mChargerStateMonitor;

    @Before
    public void verifyCharging() {
        if (!SUPPORTED_DEVICES.contains(Build.DEVICE)) {
            return;
        }

        mChargerStateMonitor = new ChargerStateMonitor();

        assertTrue("Device must be connected to USB", mChargerStateMonitor.isCharging());
    }

    @Test
    public void testSuspendInput() {
        if (!SUPPORTED_DEVICES.contains(Build.DEVICE)) {
            return;
        }

        SystemUtil.runShellCommand("dumpsys battery suspend_input");

        mChargerStateMonitor.waitForChargerState(/* isPluggedIn */false);
    }

    @After
    public void reenableCharging() {
        if (!SUPPORTED_DEVICES.contains(Build.DEVICE)) {
            return;
        }

        mChargerStateMonitor.reset();

        SystemUtil.runShellCommand("dumpsys battery reset");

        mChargerStateMonitor.waitForChargerState(/* isPluggedIn */true);
    }

    private static class ChargerStateMonitor {
        private final Intent mBatteryMonitor;
        private final ConditionVariable mReady = new ConditionVariable();
        private boolean mExpectedChargingState;

        ChargerStateMonitor() {
            Context context = InstrumentationRegistry.getInstrumentation().getContext();
            mBatteryMonitor = context.registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (isCharging(intent) == mExpectedChargingState) {
                        mReady.open();
                    }
                }
            }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        }

        public boolean isCharging() {
            return isCharging(mBatteryMonitor);
        }

        private boolean isCharging(Intent batteryMonitor) {
            return batteryMonitor.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0;
        }

        void waitForChargerState(boolean isPluggedIn) {
            mExpectedChargingState = isPluggedIn;

            boolean charging = isCharging();
            if (charging == mExpectedChargingState) {
                return;
            }

            boolean success = mReady.block(100000);
            assertTrue("Timed out waiting for charging state to change", success);
        }

        void reset() {
            mReady.close();
        }
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.os.BatteryManagerInternal;
import android.os.BatteryProperty;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.FileUtils;
@@ -59,6 +60,7 @@ import android.os.UEventObserver;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.util.EventLog;
import android.util.MutableInt;
import android.util.Slog;
@@ -182,6 +184,7 @@ public final class BatteryService extends SystemService {
    private int mChargeStartLevel;

    private boolean mUpdatesStopped;
    private boolean mBatteryInputSuspended;

    private Led mLed;

@@ -234,6 +237,8 @@ public final class BatteryService extends SystemService {
            invalidChargerObserver.startObserving(
                    "DEVPATH=/devices/virtual/switch/invalid_charger");
        }

        mBatteryInputSuspended = PowerProperties.battery_input_suspended().orElse(false);
    }

    @Override
@@ -876,6 +881,10 @@ public final class BatteryService extends SystemService {
        pw.println("  reset [-f]");
        pw.println("    Unfreeze battery state, returning to current hardware values.");
        pw.println("    -f: force a battery change broadcast be sent, prints new sequence.");
        if (Build.IS_DEBUGGABLE) {
            pw.println("  disable_charge");
            pw.println("    Suspend charging even if plugged in. ");
        }
    }

    static final int OPTION_FORCE_UPDATE = 1<<0;
@@ -997,6 +1006,20 @@ public final class BatteryService extends SystemService {
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
                if (mBatteryInputSuspended) {
                    PowerProperties.battery_input_suspended(false);
                    mBatteryInputSuspended = false;
                }
            } break;
            case "suspend_input": {
                if (!Build.IS_DEBUGGABLE) {
                    throw new SecurityException(
                            "battery suspend_input is only supported on debuggable builds");
                }
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.DEVICE_POWER, null);
                PowerProperties.battery_input_suspended(true);
                mBatteryInputSuspended = true;
            } break;
            default:
                return shell.handleDefaultCommands(cmd);