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