diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e17c312dbffcb..6ae6faa5446e7 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -50,6 +50,7 @@
+
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
new file mode 100644
index 0000000000000..e870d6022058b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
@@ -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 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();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 65ac7844ec2b1..d92706d2dd190 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -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);