summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Anderson <dvander@google.com> 2025-01-09 04:55:28 +0000
committer David Anderson <dvander@google.com> 2025-01-31 18:44:02 +0000
commitf17b79fa6b1715d6dea68d6734f74498bcbd68f7 (patch)
treeb673d49ddf4f41b1e1562e7fba8c685b0b58201f
parentc7fa90a2f1e41325fed9a658a5f6e44f993755fb (diff)
Add helper functions for testing trade-in mode.
Trade-in mode is difficult to test in automation because it is inherently a user build feature. If it were enabled on ro.debuggable=1 builds, "TIM adb" would conflict with normal adb. So, it is one or the other, and we have to pick "normal adb" to not break all CI and developer workflows. To work around this, we are adding new APIs to ITradeInMode to enable testing TIM when ro.debuggable=1. This will allow us to test certain aspects of the SUW machinery and the "getstatus" command. We do this by setting a new persist property that is read on the next reboot. On next reboot, normal adb is forbidden from starting, and trade-in mode adb is allowed to start instead. disableTesting() unsets the persist property. These APIs are only allowed when ro.debuggable=1. Finally, we add a test method to schedule the same factory reset procedure that occurs within "tradeinmode evaluate". All the testing methods are restricted to ENTER_TRADE_IN_MODE privileges and require ro.debuggable=1. Test: adb shell tradeinmode testing start Bug: 379970773 Flag: com.android.tradeinmode.flags.enable_trade_in_mode Change-Id: I58234d4d2202e0be347e1a0486b7a0869a7867d2
-rw-r--r--core/java/android/os/ITradeInMode.aidl33
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--services/core/java/com/android/server/TradeInModeService.java115
4 files changed, 141 insertions, 11 deletions
diff --git a/core/java/android/os/ITradeInMode.aidl b/core/java/android/os/ITradeInMode.aidl
index f15954d14d0e..d05f52cf6a90 100644
--- a/core/java/android/os/ITradeInMode.aidl
+++ b/core/java/android/os/ITradeInMode.aidl
@@ -59,4 +59,37 @@ interface ITradeInMode {
* ENTER_TRADE_IN_MODE permission is required.
*/
boolean enterEvaluationMode();
+
+ /**
+ * Schedules a wipe to trigger SUW for trade-in mode testing. A reboot is
+ * required. After this, startTesting() can be called.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ void scheduleWipeForTesting();
+
+ /**
+ * Enables testing. This only takes effect after the next reboot, and is
+ * only allowed in ro.debuggable builds. On the following boot, normal
+ * adbd will be disabled and trade-in mode adbd will be enabled instead.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ void startTesting();
+
+ /**
+ * Disables testing. This disables trade-in mode and removes any scheduled
+ * trade-in mode wipe.
+ *
+ * ENTER_TRADE_IN_MODE permission is required, ro.debuggable must be 1, and
+ * startTesting() must have been called.
+ */
+ void stopTesting();
+
+ /**
+ * Returns whether the device is testing trade-in mode.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ boolean isTesting();
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1edbffa9d572..1de8664231d7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -259,6 +259,7 @@ applications that come with the platform
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.ACCESS_LOWPAN_STATE"/>
<permission name="android.permission.BACKUP"/>
+ <permission name="android.permission.ENTER_TRADE_IN_MODE"/>
<!-- Needed for GMSCore Location API test only -->
<permission name="android.permission.LOCATION_BYPASS"/>
<!-- Needed for test only -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8fe3a0c4b4ae..55f7317f25e4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1015,6 +1015,9 @@
<uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
<uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+ <!-- Permission required for trade-in mode testing -->
+ <uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index a69667395ebd..1a9e02c86560 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -41,12 +41,15 @@ import android.util.Slog;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
public final class TradeInModeService extends SystemService {
private static final String TAG = "TradeInModeService";
private static final String TIM_PROP = "persist.adb.tradeinmode";
+ private static final String TIM_TEST_PROP = "persist.adb.test_tradeinmode";
private static final int TIM_STATE_UNSET = 0;
@@ -108,6 +111,10 @@ public final class TradeInModeService extends SystemService {
// setup completion observer.
if (isDeviceSetup()) {
stopTradeInMode();
+ } else if (isDebuggable() && !isForceEnabledForTesting()) {
+ // The device was made debuggable after entering TIM. This
+ // can happen while flashing. For convenience, leave test mode.
+ leaveTestMode();
} else {
watchForSetupCompletion();
watchForNetworkChange();
@@ -171,12 +178,7 @@ public final class TradeInModeService extends SystemService {
Slog.e(TAG, "Cannot enter evaluation mode, FRP lock is present.");
return false;
}
-
- try (FileWriter fw = new FileWriter(WIPE_INDICATOR_FILE,
- StandardCharsets.US_ASCII)) {
- fw.write("0");
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write " + WIPE_INDICATOR_FILE, e);
+ if (!scheduleTradeInModeWipe()) {
return false;
}
@@ -189,7 +191,7 @@ public final class TradeInModeService extends SystemService {
}
SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_EVALUATION_MODE));
- SystemProperties.set("ctl.restart", "adbd");
+ restartAdbd();
return true;
}
@@ -200,6 +202,55 @@ public final class TradeInModeService extends SystemService {
"Cannot test for trade-in evaluation mode allowed");
return !isFrpActive();
}
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void scheduleWipeForTesting() {
+ enforceTestingPermissions();
+
+ scheduleTradeInModeWipe();
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void startTesting() {
+ enforceTestingPermissions();
+
+ enterTestMode();
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void stopTesting() {
+ enforceTestingPermissions();
+
+ if (!isForceEnabledForTesting()) {
+ throw new IllegalStateException("testing must have been started");
+ }
+
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ leaveTestMode();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public boolean isTesting() {
+ enforceTestingPermissions();
+
+ return isForceEnabledForTesting();
+ }
+
+ private void enforceTestingPermissions() {
+ mContext.enforceCallingOrSelfPermission("android.permission.ENTER_TRADE_IN_MODE",
+ "Caller must have ENTER_TRADE_IN_MODE permission");
+ if (!isDebuggable()) {
+ throw new SecurityException("ro.debuggable must be set to 1");
+ }
+ }
}
private void startTradeInMode() {
@@ -207,8 +258,7 @@ public final class TradeInModeService extends SystemService {
SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_FOYER));
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+ setAdbEnabled(true);
watchForSetupCompletion();
watchForNetworkChange();
@@ -223,8 +273,51 @@ public final class TradeInModeService extends SystemService {
removeNetworkWatch();
removeAccountsWatch();
+ if (isForceEnabledForTesting()) {
+ // If testing in a debug build, we need to re-enable ADB.
+ restartAdbd();
+ } else {
+ // Otherwise, ADB must not be enabled.
+ setAdbEnabled(false);
+ }
+ }
+
+ private void enterTestMode() {
+ SystemProperties.set(TIM_TEST_PROP, "1");
+ }
+
+ private void leaveTestMode() {
+ if (getTradeInModeState() == TIM_STATE_FOYER) {
+ stopTradeInMode();
+ }
+
+ SystemProperties.set(TIM_TEST_PROP, "");
+ SystemProperties.set(TIM_PROP, "");
+ try {
+ Files.deleteIfExists(Paths.get(WIPE_INDICATOR_FILE));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to remove wipe indicator", e);
+ }
+ }
+
+ private boolean scheduleTradeInModeWipe() {
+ try (FileWriter fw = new FileWriter(WIPE_INDICATOR_FILE,
+ StandardCharsets.US_ASCII)) {
+ fw.write("0");
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write " + WIPE_INDICATOR_FILE, e);
+ return false;
+ }
+ return true;
+ }
+
+ private void restartAdbd() {
+ SystemProperties.set("ctl.restart", "adbd");
+ }
+
+ private void setAdbEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 0);
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
}
private int getTradeInModeState() {
@@ -236,7 +329,7 @@ public final class TradeInModeService extends SystemService {
}
private boolean isForceEnabledForTesting() {
- return SystemProperties.getInt("persist.adb.test_tradeinmode", 0) == 1;
+ return isDebuggable() && SystemProperties.getInt(TIM_TEST_PROP, 0) == 1;
}
private boolean isAdbEnabled() {