Merge "Add support for suspending battery charging"

This commit is contained in:
Dmitri Plotnikov
2020-09-10 04:47:37 +00:00
committed by Android (Google) Code Review
3 changed files with 154 additions and 0 deletions

View File

@@ -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" />

View File

@@ -0,0 +1,130 @@
/*
* 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();
}
}
}

View File

@@ -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);