summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVeena Arvind <aveena@google.com>2023-10-18 01:22:40 +0000
committerVeena Arvind <aveena@google.com>2023-10-25 22:42:49 +0000
commit4810d926625e92dd0846f10debe1fb6164f0f7e2 (patch)
tree8639de89cc176f9af54e5693911b203f5c662ee0
parent80db8278c17157cfbc76c791517f085c74238f1d (diff)
downloadConfigInfrastructure-4810d926625e92dd0846f10debe1fb6164f0f7e2.tar.gz
Check for network connectivity before rebooting device, rescheduling if is none.
Under the current implementation, we will not reschedule reboot if there is no network connectivity. Under new implementation, we try reboot once internet connectivity is established, scheduling for the following time if the current time is outside the reboot window. The current reboot window is between 1-5. Bug: 305259443 Test: atest ConfigInfrastructureServiceUnitTests[com.google.android.configinfrastructure.apex] Change-Id: Ifaa9c80bb12472515b439c84d5f681d2a290a4ca
-rw-r--r--service/Android.bp1
-rw-r--r--service/java/com/android/server/deviceconfig/UnattendedRebootManager.java105
-rw-r--r--service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java5
-rw-r--r--service/javatests/Android.bp1
-rw-r--r--service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java161
5 files changed, 232 insertions, 41 deletions
diff --git a/service/Android.bp b/service/Android.bp
index 8d0fb79..71417cf 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -38,6 +38,7 @@ java_sdk_library {
libs: [
"framework-configinfrastructure.impl",
"DeviceConfigServiceResources",
+ "framework-connectivity.stubs.module_lib",
],
min_sdk_version: "UpsideDownCake",
sdk_version: "system_server_current",
diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
index c360f4d..655ad54 100644
--- a/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
+++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManager.java
@@ -10,6 +10,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.util.Log;
@@ -29,7 +33,8 @@ import java.time.ZoneId;
* @hide
*/
final class UnattendedRebootManager {
- private static final int DEFAULT_REBOOT_WINDOW_START_TIME_HOUR = 2;
+ private static final int DEFAULT_REBOOT_WINDOW_START_TIME_HOUR = 1;
+ private static final int DEFAULT_REBOOT_WINDOW_END_TIME_HOUR = 5;
private static final int DEFAULT_REBOOT_FREQUENCY_DAYS = 2;
@@ -67,20 +72,28 @@ final class UnattendedRebootManager {
return DEFAULT_REBOOT_WINDOW_START_TIME_HOUR;
}
+ public int getRebootEndTime() {
+ return DEFAULT_REBOOT_WINDOW_END_TIME_HOUR;
+ }
+
public int getRebootFrequency() {
return DEFAULT_REBOOT_FREQUENCY_DAYS;
}
public void setRebootAlarm(Context context, long rebootTimeMillis) {
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
- PendingIntent pendingIntent =
- PendingIntent.getBroadcast(
- context,
- /* requestCode= */ 0,
- new Intent(ACTION_TRIGGER_REBOOT),
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP, rebootTimeMillis, createTriggerRebootPendingIntent(context));
+ }
- alarmManager.setExact(AlarmManager.RTC_WAKEUP, rebootTimeMillis, pendingIntent);
+ public void triggerRebootOnNetworkAvailable(Context context) {
+ final ConnectivityManager connectivityManager =
+ context.getSystemService(ConnectivityManager.class);
+ NetworkRequest request =
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ connectivityManager.requestNetwork(request, createTriggerRebootPendingIntent(context));
}
public int rebootAndApply(@NonNull Context context, @NonNull String reason, boolean slotSwitch)
@@ -102,6 +115,14 @@ final class UnattendedRebootManager {
PowerManager powerManager = context.getSystemService(PowerManager.class);
powerManager.reboot(REBOOT_REASON);
}
+
+ private static PendingIntent createTriggerRebootPendingIntent(Context context) {
+ return PendingIntent.getBroadcast(
+ context,
+ /* requestCode= */ 0,
+ new Intent(ACTION_TRIGGER_REBOOT),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
+ }
}
@VisibleForTesting
@@ -178,22 +199,53 @@ final class UnattendedRebootManager {
@VisibleForTesting
void tryRebootOrSchedule() {
- // TODO(b/305259443): check network is connected
- // Check if RoR is supported.
+ Log.v(TAG, "Attempting unattended reboot");
+
+ // Is RoR is supported?
if (!isDeviceSecure(mContext)) {
Log.v(TAG, "Device is not secure. Proceed with regular reboot");
mInjector.regularReboot(mContext);
- } else if (isPreparedForUnattendedReboot()) {
- try {
- mInjector.rebootAndApply(mContext, REBOOT_REASON, /* slotSwitch= */ false);
- } catch (IOException e) {
- Log.e(TAG, e.getLocalizedMessage());
- }
- // If reboot is successful, should not reach this.
- } else {
- // Lskf is not captured, try again the following day
+ return;
+ }
+ // Is RoR prepared?
+ if (!isPreparedForUnattendedReboot()) {
+ Log.v(TAG, "Lskf is not captured, reschedule reboot.");
prepareUnattendedReboot();
scheduleReboot();
+ return;
+ }
+ // Is network connected?
+ // TODO(b/305259443): Use after-boot network connectivity projection
+ if (!isNetworkConnected(mContext)) {
+ Log.i(TAG, "Network is not connected, schedule reboot for another time.");
+ mInjector.triggerRebootOnNetworkAvailable(mContext);
+ return;
+ }
+ // Is current time between reboot window?
+ int currentHour =
+ Instant.ofEpochMilli(mInjector.now())
+ .atZone(mInjector.zoneId())
+ .toLocalDateTime()
+ .getHour();
+ if (currentHour < mInjector.getRebootStartTime()
+ && currentHour >= mInjector.getRebootEndTime()) {
+ Log.v(TAG, "Reboot requested outside of reboot window, reschedule.");
+ prepareUnattendedReboot();
+ scheduleReboot();
+ return;
+ }
+
+ // Proceed with RoR.
+ try {
+ int success = mInjector.rebootAndApply(mContext, REBOOT_REASON, /* slotSwitch= */ false);
+ if (success != 0) {
+ // If reboot is not successful, reschedule.
+ Log.w(TAG, "Unattended reboot failed, reschedule.");
+ scheduleReboot();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.getLocalizedMessage());
+ scheduleReboot();
}
}
@@ -220,4 +272,19 @@ final class UnattendedRebootManager {
}
return keyguardManager.isDeviceSecure();
}
+
+ private static boolean isNetworkConnected(Context context) {
+ final ConnectivityManager connectivityManager =
+ context.getSystemService(ConnectivityManager.class);
+ if (connectivityManager == null) {
+ Log.w(TAG, "ConnectivityManager is null");
+ return false;
+ }
+ Network activeNetwork = connectivityManager.getActiveNetwork();
+ NetworkCapabilities networkCapabilities =
+ connectivityManager.getNetworkCapabilities(activeNetwork);
+ return networkCapabilities != null
+ && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ }
}
diff --git a/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
index 4e883d3..e1e0909 100644
--- a/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
+++ b/service/java/com/android/server/deviceconfig/UnattendedRebootManagerInjector.java
@@ -23,11 +23,16 @@ interface UnattendedRebootManagerInjector {
/** Reboot time injectors. */
int getRebootStartTime();
+ int getRebootEndTime();
+
int getRebootFrequency();
/** Reboot Alarm injector. */
void setRebootAlarm(Context context, long rebootTimeMillis);
+ /** Connectivity injector. */
+ void triggerRebootOnNetworkAvailable(Context context);
+
/** {@link RecoverySystem} methods injectors. */
int rebootAndApply(@NonNull Context context, @NonNull String reason, boolean slotSwitch)
throws IOException;
diff --git a/service/javatests/Android.bp b/service/javatests/Android.bp
index eb1561c..d9617d8 100644
--- a/service/javatests/Android.bp
+++ b/service/javatests/Android.bp
@@ -49,6 +49,7 @@ android_test {
"android.test.base",
"android.test.mock",
"android.test.runner",
+ "framework-connectivity.stubs.module_lib",
"framework-configinfrastructure",
],
// Test coverage system runs on different devices. Need to
diff --git a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
index f87cf56..20494e5 100644
--- a/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
+++ b/service/javatests/src/com/android/server/deviceconfig/UnattendedRebootManagerTest.java
@@ -6,6 +6,7 @@ import static com.android.server.deviceconfig.UnattendedRebootManager.ACTION_TRI
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -18,10 +19,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
import android.util.Log;
import androidx.test.filters.SmallTest;
-import java.io.IOException;
import java.time.ZoneId;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -32,22 +34,27 @@ import org.junit.Test;
@SmallTest
public class UnattendedRebootManagerTest {
+ private static final String TAG = "UnattendedRebootManagerTest";
+
private static final int REBOOT_FREQUENCY = 1;
- private static final int REBOOT_HOUR = 2;
+ private static final int REBOOT_START_HOUR = 2;
+ private static final int REBOOT_END_HOUR = 3;
+
private static final long CURRENT_TIME = 1696452549304L; // 2023-10-04T13:49:09.304
private static final long REBOOT_TIME = 1696496400000L; // 2023-10-05T02:00:00
+ private static final long RESCHEDULED_REBOOT_TIME = 1696582800000L; // 2023-10-06T02:00:00
+ private static final long OUTSIDE_WINDOW_REBOOT_TIME = 1696583400000L; // 2023-10-06T03:10:00
private Context mContext;
-
private KeyguardManager mKeyguardManager;
-
- FakeInjector mFakeInjector;
-
+ private ConnectivityManager mConnectivityManager;
+ private FakeInjector mFakeInjector;
private UnattendedRebootManager mRebootManager;
@Before
public void setUp() throws Exception {
mKeyguardManager = mock(KeyguardManager.class);
+ mConnectivityManager = mock(ConnectivityManager.class);
mContext =
new ContextWrapper(getInstrumentation().getTargetContext()) {
@@ -55,6 +62,8 @@ public class UnattendedRebootManagerTest {
public Object getSystemService(String name) {
if (name.equals(Context.KEYGUARD_SERVICE)) {
return mKeyguardManager;
+ } else if (name.equals(Context.CONNECTIVITY_SERVICE)) {
+ return mConnectivityManager;
}
return super.getSystemService(name);
}
@@ -78,35 +87,111 @@ public class UnattendedRebootManagerTest {
@Test
public void scheduleReboot() {
+ Log.i(TAG, "scheduleReboot");
when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
mRebootManager.prepareUnattendedReboot();
mRebootManager.scheduleReboot();
- assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
assertTrue(mFakeInjector.isRebootAndApplied());
assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
}
@Test
public void scheduleReboot_noPinLock() {
+ Log.i(TAG, "scheduleReboot_noPinLock");
when(mKeyguardManager.isDeviceSecure()).thenReturn(false);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
mRebootManager.prepareUnattendedReboot();
mRebootManager.scheduleReboot();
- assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
assertFalse(mFakeInjector.isRebootAndApplied());
assertTrue(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
}
@Test
public void scheduleReboot_noPreparation() {
+ Log.i(TAG, "scheduleReboot_noPreparation");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
+
+ mRebootManager.scheduleReboot();
+
+ assertFalse(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(RESCHEDULED_REBOOT_TIME);
+ }
+
+ @Test
+ public void scheduleReboot_noInternet() {
+ Log.i(TAG, "scheduleReboot_noInternet");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(new NetworkCapabilities());
+
+ mRebootManager.prepareUnattendedReboot();
+ mRebootManager.scheduleReboot();
+
+ assertFalse(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
+ assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
+ assertTrue(mFakeInjector.isRequestedNetwork());
+ }
+
+ @Test
+ public void scheduleReboot_noInternetValidation() {
+ Log.i(TAG, "scheduleReboot_noInternetValidation");
when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build());
+ mRebootManager.prepareUnattendedReboot();
mRebootManager.scheduleReboot();
+ assertFalse(mFakeInjector.isRebootAndApplied());
+ assertFalse(mFakeInjector.isRegularRebooted());
assertThat(mFakeInjector.getActualRebootTime()).isEqualTo(REBOOT_TIME);
+ assertTrue(mFakeInjector.isRequestedNetwork());
+ }
+
+ @Test
+ public void tryRebootOrSchedule_outsideRebootWindow() {
+ Log.i(TAG, "scheduleReboot_internetOutsideRebootWindow");
+ when(mKeyguardManager.isDeviceSecure()).thenReturn(true);
+ when(mConnectivityManager.getNetworkCapabilities(any()))
+ .thenReturn(
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build());
+ mFakeInjector.setNow(OUTSIDE_WINDOW_REBOOT_TIME);
+
+ mRebootManager.prepareUnattendedReboot();
+ // Simulating case when reboot is tried after network connection is established outside the
+ // reboot window.
+ mRebootManager.tryRebootOrSchedule();
+
assertFalse(mFakeInjector.isRebootAndApplied());
assertFalse(mFakeInjector.isRegularRebooted());
}
@@ -116,27 +201,33 @@ public class UnattendedRebootManagerTest {
private boolean isPreparedForUnattendedReboot;
private boolean rebootAndApplied;
private boolean regularRebooted;
+ private boolean requestedNetwork;
private long actualRebootTime;
+ private boolean scheduledReboot;
+
+ private long nowMillis;
- FakeInjector() {}
+ FakeInjector() {
+ nowMillis = CURRENT_TIME;
+ }
@Override
public void prepareForUnattendedUpdate(
- @NonNull Context context, @NonNull String updateToken, @Nullable IntentSender intentSender)
- throws IOException {
+ @NonNull Context context,
+ @NonNull String updateToken,
+ @Nullable IntentSender intentSender) {
context.sendBroadcast(new Intent(ACTION_RESUME_ON_REBOOT_LSKF_CAPTURED));
isPreparedForUnattendedReboot = true;
}
@Override
- public boolean isPreparedForUnattendedUpdate(@NonNull Context context) throws IOException {
+ public boolean isPreparedForUnattendedUpdate(@NonNull Context context) {
return isPreparedForUnattendedReboot;
}
@Override
public int rebootAndApply(
@NonNull Context context, @NonNull String reason, boolean slotSwitch) {
- Log.i("UnattendedRebootManagerTest", "MockInjector.rebootAndApply");
rebootAndApplied = true;
return 0; // No error.
}
@@ -148,24 +239,51 @@ public class UnattendedRebootManagerTest {
@Override
public void setRebootAlarm(Context context, long rebootTimeMillis) {
- // reboot immediately
+ // To prevent infinite loop, do not simulate another reboot if reboot was already scheduled.
+ if (scheduledReboot) {
+ actualRebootTime = rebootTimeMillis;
+ return;
+ }
+ // Advance now to reboot time and reboot immediately.
+ scheduledReboot = true;
actualRebootTime = rebootTimeMillis;
- context.sendBroadcast(new Intent(UnattendedRebootManager.ACTION_TRIGGER_REBOOT));
+ setNow(rebootTimeMillis);
LatchingBroadcastReceiver rebootReceiver = new LatchingBroadcastReceiver();
- context.registerReceiver(
- rebootReceiver, new IntentFilter(ACTION_TRIGGER_REBOOT), Context.RECEIVER_EXPORTED);
- rebootReceiver.await(10, TimeUnit.SECONDS);
+
+ // Wait for reboot broadcast to be sent.
+ context.sendOrderedBroadcast(
+ new Intent(ACTION_TRIGGER_REBOOT), null, rebootReceiver, null, 0, null, null);
+
+ rebootReceiver.await(20, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void triggerRebootOnNetworkAvailable(Context context) {
+ requestedNetwork = true;
+ }
+
+ public boolean isRequestedNetwork() {
+ return requestedNetwork;
}
@Override
public int getRebootStartTime() {
- return REBOOT_HOUR;
+ return REBOOT_START_HOUR;
+ }
+
+ @Override
+ public int getRebootEndTime() {
+ return REBOOT_END_HOUR;
}
@Override
public long now() {
- return CURRENT_TIME;
+ return nowMillis;
+ }
+
+ public void setNow(long nowMillis) {
+ this.nowMillis = nowMillis;
}
@Override
@@ -175,7 +293,6 @@ public class UnattendedRebootManagerTest {
@Override
public void regularReboot(Context context) {
- Log.i("UnattendedRebootManagerTest", "MockInjector.regularRebooted");
regularRebooted = true;
}
@@ -207,7 +324,7 @@ public class UnattendedRebootManagerTest {
try {
return latch.await(timeoutInMs, timeUnit);
} catch (InterruptedException e) {
- return false;
+ throw new RuntimeException(e);
}
}
}