summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/ServiceResources/res/values/config.xml3
-rw-r--r--service/flags.aconfig7
-rw-r--r--service/java/com/android/server/deviceconfig/UnattendedRebootManager.java43
-rw-r--r--service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java2
-rw-r--r--service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java146
5 files changed, 200 insertions, 1 deletions
diff --git a/service/ServiceResources/res/values/config.xml b/service/ServiceResources/res/values/config.xml
index a582b6f..b116767 100644
--- a/service/ServiceResources/res/values/config.xml
+++ b/service/ServiceResources/res/values/config.xml
@@ -37,4 +37,7 @@
<!-- The unattended reboot frequency in days. Should be a positive value.
If a non-positive value is provided, the default frqeuency will be applied. -->
<integer name="config_unattendedRebootFrequencyDays">2</integer>
+
+ <!-- Whether or not the device needs to be charging to trigger unattended reboot.-->
+ <bool name="config_requireChargingForUnattendedReboot">false</bool>
</resources> \ No newline at end of file
diff --git a/service/flags.aconfig b/service/flags.aconfig
index 3b431d9..f8a8d25 100644
--- a/service/flags.aconfig
+++ b/service/flags.aconfig
@@ -26,3 +26,10 @@ flag {
description: "This flags controls allowing devices to configure the reboot window and frequency."
bug: "322076175"
}
+
+flag {
+ name: "enable_charger_dependency_for_reboot"
+ namespace: "core_experiments_team_internal"
+ description: "This flags controls allowing devices to configure reboot to require charging."
+ bug: "322076175"
+} \ No newline at end of file
diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
index 34f4602..4b7159a 100644
--- a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
+++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
@@ -1,5 +1,6 @@
package com.android.server.deviceconfig;
+import static com.android.server.deviceconfig.Flags.enableChargerDependencyForReboot;
import static com.android.server.deviceconfig.Flags.enableCustomRebootTimeConfigurations;
import static com.android.server.deviceconfig.Flags.enableSimPinReplay;
@@ -17,12 +18,15 @@ import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.os.BatteryManager;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.deviceconfig.resources.R;
import java.io.IOException;
import java.time.Instant;
@@ -68,6 +72,17 @@ final class UnattendedRebootManager {
private final SimPinReplayManager mSimPinReplayManager;
+ private boolean mChargingReceiverRegistered;
+
+ private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mChargingReceiverRegistered = false;
+ mContext.unregisterReceiver(mChargingReceiver);
+ tryRebootOrSchedule();
+ }
+ };
+
private static class InjectorImpl implements UnattendedRebootManagerInjector {
InjectorImpl() {
/*no op*/
@@ -129,6 +144,11 @@ final class UnattendedRebootManager {
return RecoverySystem.isPreparedForUnattendedUpdate(context);
}
+ @Override
+ public boolean requiresChargingForReboot(Context context) {
+ return context.getResources().getBoolean(R.bool.config_requireChargingForUnattendedReboot);
+ }
+
public void regularReboot(Context context) {
PowerManager powerManager = context.getSystemService(PowerManager.class);
powerManager.reboot(REBOOT_REASON);
@@ -292,6 +312,13 @@ final class UnattendedRebootManager {
scheduleReboot();
}
+ if (enableChargerDependencyForReboot()
+ && mInjector.requiresChargingForReboot(mContext)
+ && !isCharging(mContext)) {
+ triggerRebootOnCharging();
+ return;
+ }
+
// Proceed with RoR.
Log.v(TAG, "Rebooting device to apply device config flags.");
try {
@@ -326,6 +353,16 @@ final class UnattendedRebootManager {
}
}
+ private void triggerRebootOnCharging() {
+ if (!mChargingReceiverRegistered) {
+ mChargingReceiverRegistered = true;
+ mContext.registerReceiver(
+ mChargingReceiver,
+ new IntentFilter(BatteryManager.ACTION_CHARGING),
+ Context.RECEIVER_EXPORTED);
+ }
+ }
+
/** Returns true if the device has screen lock. */
private static boolean isDeviceSecure(Context context) {
KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
@@ -337,6 +374,12 @@ final class UnattendedRebootManager {
return keyguardManager.isDeviceSecure();
}
+ private static boolean isCharging(Context context) {
+ BatteryManager batteryManager =
+ (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
+ return batteryManager.isCharging();
+ }
+
private static boolean isNetworkConnected(Context context) {
final ConnectivityManager connectivityManager =
context.getSystemService(ConnectivityManager.class);
diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
index 5ca3e1e..f5f9850 100644
--- a/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
+++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
@@ -45,6 +45,8 @@ interface UnattendedRebootManagerInjector {
boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException;
+ boolean requiresChargingForReboot(Context context);
+
/** Regular reboot injector. */
void regularReboot(Context context);
}
diff --git a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
index 72ad00d..93bd2b1 100644
--- a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
+++ b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
@@ -1,6 +1,7 @@
package com.android.server.deviceconfig;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.server.deviceconfig.Flags.FLAG_ENABLE_CHARGER_DEPENDENCY_FOR_REBOOT;
import static com.android.server.deviceconfig.Flags.FLAG_ENABLE_CUSTOM_REBOOT_TIME_CONFIGURATIONS;
import static com.android.server.deviceconfig.Flags.FLAG_ENABLE_SIM_PIN_REPLAY;
@@ -12,6 +13,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -24,18 +26,25 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
+import android.os.BatteryManager;
import android.util.Log;
import androidx.test.filters.SmallTest;
import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
@SmallTest
public class UnattendedRebootManagerTest {
@@ -54,7 +63,10 @@ public class UnattendedRebootManagerTest {
1696669920000L; // 2023-10-07T02:12:00
private static final long ELAPSED_REALTIME_1_DAY = 86400000L;
+ private final List<BroadcastReceiverRegistration> mRegisteredReceivers = new ArrayList<>();
+
private Context mContext;
+ private BatteryManager mBatterManager;
private KeyguardManager mKeyguardManager;
private ConnectivityManager mConnectivityManager;
private RebootTimingConfiguration mRebootTimingConfiguration;
@@ -68,11 +80,14 @@ public class UnattendedRebootManagerTest {
@Before
public void setUp() throws Exception {
- mSetFlagsRule.enableFlags(FLAG_ENABLE_SIM_PIN_REPLAY);
+ mSetFlagsRule.enableFlags(
+ FLAG_ENABLE_SIM_PIN_REPLAY, FLAG_ENABLE_CHARGER_DEPENDENCY_FOR_REBOOT);
mSimPinReplayManager = mock(SimPinReplayManager.class);
mKeyguardManager = mock(KeyguardManager.class);
mConnectivityManager = mock(ConnectivityManager.class);
+ mBatterManager = mock(BatteryManager.class);
+
mRebootTimingConfiguration =
new RebootTimingConfiguration(REBOOT_START_HOUR, REBOOT_END_HOUR, REBOOT_FREQUENCY);
@@ -84,9 +99,18 @@ public class UnattendedRebootManagerTest {
return mKeyguardManager;
} else if (name.equals(Context.CONNECTIVITY_SERVICE)) {
return mConnectivityManager;
+ } else if (name.equals(Context.BATTERY_SERVICE)) {
+ return mBatterManager;
}
return super.getSystemService(name);
}
+
+ @Override
+ public Intent registerReceiver(
+ @Nullable BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ mRegisteredReceivers.add(new BroadcastReceiverRegistration(receiver, filter, flags));
+ return super.registerReceiver(receiver, filter, flags);
+ }
};
mFakeInjector = new FakeInjector();
@@ -107,6 +131,9 @@ public class UnattendedRebootManagerTest {
Context.RECEIVER_EXPORTED);
mFakeInjector.setElapsedRealtime(ELAPSED_REALTIME_1_DAY);
+
+ mFakeInjector.setRequiresChargingForReboot(true);
+ when(mBatterManager.isCharging()).thenReturn(true);
}
@Test
@@ -130,6 +157,93 @@ public class UnattendedRebootManagerTest {
}
@Test
+ public void scheduleReboot_requiresCharging_notCharging() {
+ Log.i(TAG, "scheduleReboot_requiresCharging_notCharging");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
+ when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_CHARGER_DEPENDENCY_FOR_REBOOT);
+ mFakeInjector.setRequiresChargingForReboot(true);
+ when(mBatterManager.isCharging()).thenReturn(false);
+
+ mRebootManager.prepareUnattendedReboot();
+ mRebootManager.scheduleReboot();
+
+ // Charging is required and device is not charging, so reboot should not be triggered.
+ assertFalse(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
+ List<BroadcastReceiverRegistration> chargingStateReceiverRegistrations =
+ getRegistrationsForAction(BatteryManager.ACTION_CHARGING);
+ assertThat(chargingStateReceiverRegistrations).hasSize(1);
+
+ // Now mimic a change in a charging state changed, and verify that we do the reboot once device
+ // is charging.
+ when(mBatterManager.isCharging()).thenReturn(true);
+ BroadcastReceiver chargingStateReceiver = chargingStateReceiverRegistrations.get(0).mReceiver;
+ chargingStateReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+
+ assertTrue(mFakeInjector.isRebootAndApplied());
+ }
+
+ @Test
+ public void scheduleReboot_doesNotRequireCharging_notCharging() {
+ Log.i(TAG, "scheduleReboot_doesNotRequireCharging_notCharging");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
+ when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_CHARGER_DEPENDENCY_FOR_REBOOT);
+ mFakeInjector.setRequiresChargingForReboot(false);
+ when(mBatterManager.isCharging()).thenReturn(false);
+
+ mRebootManager.prepareUnattendedReboot();
+ mRebootManager.scheduleReboot();
+
+ // Charging is not required, so reboot should be triggered despite the fact that the device
+ // is not charging.
+ assertTrue(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
+ assertThat(getRegistrationsForAction(BatteryManager.ACTION_CHARGING)).isEmpty();
+ }
+
+ @Test
+ public void scheduleReboot_requiresCharging_flagNotEnabled() {
+ Log.i(TAG, "scheduleReboot_requiresCharging_flagNotEnabled");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
+ when(mSimPinReplayManager.prepareSimPinReplay()).thenReturn(true);
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_CHARGER_DEPENDENCY_FOR_REBOOT);
+ mFakeInjector.setRequiresChargingForReboot(true);
+ when(mBatterManager.isCharging()).thenReturn(false);
+
+ mRebootManager.prepareUnattendedReboot();
+ mRebootManager.scheduleReboot();
+
+ // Charging is required, but the flag that controls the feature to depend on charging is not
+ // enabled, so eboot should be triggered despite the fact that the device is not charging.
+ assertTrue(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
+ assertThat(getRegistrationsForAction(BatteryManager.ACTION_CHARGING)).isEmpty();
+ }
+
+ @Test
public void scheduleReboot_noPinLock() {
Log.i(TAG, "scheduleReboot_noPinLock");
when(mKeyguardManager.isDeviceSecure()).thenReturn(false);
@@ -298,6 +412,7 @@ public class UnattendedRebootManagerTest {
static class FakeInjector implements UnattendedRebootManagerInjector {
private boolean isPreparedForUnattendedReboot;
+ private boolean requiresChargingForReboot;
private boolean rebootAndApplied;
private boolean regularRebooted;
private boolean requestedNetwork;
@@ -327,6 +442,15 @@ public class UnattendedRebootManagerTest {
}
@Override
+ public boolean requiresChargingForReboot(Context context) {
+ return requiresChargingForReboot;
+ }
+
+ void setRequiresChargingForReboot(boolean requiresCharging) {
+ requiresChargingForReboot = requiresCharging;
+ }
+
+ @Override
public int rebootAndApply(
@NonNull Context context, @NonNull String reason, boolean slotSwitch) {
rebootAndApplied = true;
@@ -438,4 +562,24 @@ public class UnattendedRebootManagerTest {
}
}
}
+
+ private List<BroadcastReceiverRegistration> getRegistrationsForAction(String action) {
+ return mRegisteredReceivers
+ .stream()
+ .filter(r -> r.mFilter.hasAction(action))
+ .collect(Collectors.toList());
+ }
+
+ /** Data class to store BroadcastReceiver registration info. */
+ private static final class BroadcastReceiverRegistration {
+ final BroadcastReceiver mReceiver;
+ final IntentFilter mFilter;
+ final int mFlags;
+
+ BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ mReceiver = receiver;
+ mFilter = filter;
+ mFlags = flags;
+ }
+ }
}