1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.testharness; 18 19 import android.annotation.Nullable; 20 import android.annotation.UserIdInt; 21 import android.app.KeyguardManager; 22 import android.app.Notification; 23 import android.app.NotificationManager; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.debug.AdbManagerInternal; 28 import android.location.LocationManager; 29 import android.os.BatteryManager; 30 import android.os.Binder; 31 import android.os.IBinder; 32 import android.os.ResultReceiver; 33 import android.os.ShellCallback; 34 import android.os.ShellCommand; 35 import android.os.SystemProperties; 36 import android.os.UserHandle; 37 import android.provider.Settings; 38 import android.util.Slog; 39 40 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 41 import com.android.internal.notification.SystemNotificationChannels; 42 import com.android.internal.widget.LockPatternUtils; 43 import com.android.server.LocalServices; 44 import com.android.server.SystemService; 45 import com.android.server.pdb.PersistentDataBlockManagerInternal; 46 import com.android.server.pm.UserManagerInternal; 47 48 import java.io.ByteArrayInputStream; 49 import java.io.ByteArrayOutputStream; 50 import java.io.DataInputStream; 51 import java.io.DataOutputStream; 52 import java.io.File; 53 import java.io.FileDescriptor; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.OutputStream; 57 import java.io.PrintWriter; 58 import java.nio.file.Files; 59 import java.nio.file.Path; 60 import java.nio.file.attribute.PosixFilePermission; 61 import java.util.Set; 62 63 /** 64 * Manages the Test Harness Mode service for setting up test harness mode on the device. 65 * 66 * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys, 67 * and provision the device for Instrumentation testing. This means that all parts of the device 68 * that would otherwise interfere with testing (auto-syncing accounts, package verification, 69 * automatic updates, etc.) are all disabled by default but may be re-enabled by the user. 70 */ 71 public class TestHarnessModeService extends SystemService { 72 public static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; 73 private static final String TAG = TestHarnessModeService.class.getSimpleName(); 74 private boolean mEnableKeepMemtagMode = false; 75 76 private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; 77 TestHarnessModeService(Context context)78 public TestHarnessModeService(Context context) { 79 super(context); 80 } 81 82 @Override onStart()83 public void onStart() { 84 publishBinderService("testharness", mService); 85 } 86 87 @Override onBootPhase(int phase)88 public void onBootPhase(int phase) { 89 switch (phase) { 90 case PHASE_SYSTEM_SERVICES_READY: 91 setUpTestHarnessMode(); 92 break; 93 case PHASE_BOOT_COMPLETED: 94 completeTestHarnessModeSetup(); 95 showNotificationIfEnabled(); 96 break; 97 } 98 super.onBootPhase(phase); 99 } 100 101 /** 102 * Begin the setup for Test Harness Mode. 103 * 104 * <p>Note: This is just the things that <em>need</em> to be done before the device finishes 105 * booting for the first time. Everything else should be done after the system is done booting. 106 */ setUpTestHarnessMode()107 private void setUpTestHarnessMode() { 108 Slog.d(TAG, "Setting up test harness mode"); 109 byte[] testHarnessModeData = getTestHarnessModeData(); 110 if (testHarnessModeData == null) { 111 return; 112 } 113 // If there is data, we should set the device as provisioned, so that we skip the setup 114 // wizard. 115 setDeviceProvisioned(); 116 disableLockScreen(); 117 SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1"); 118 } 119 disableLockScreen()120 private void disableLockScreen() { 121 int mainUserId = getMainUserId(); 122 LockPatternUtils utils = new LockPatternUtils(getContext()); 123 utils.setLockScreenDisabled(true, mainUserId); 124 } 125 completeTestHarnessModeSetup()126 private void completeTestHarnessModeSetup() { 127 Slog.d(TAG, "Completing Test Harness Mode setup."); 128 byte[] testHarnessModeData = getTestHarnessModeData(); 129 if (testHarnessModeData == null) { 130 return; 131 } 132 try { 133 setUpAdbFiles(PersistentData.fromBytes(testHarnessModeData)); 134 configureSettings(); 135 configureUser(); 136 } catch (SetUpTestHarnessModeException e) { 137 Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e); 138 } finally { 139 // Clear out the Test Harness Mode data so that we don't repeat the setup. If it failed 140 // to set up, then retrying without enabling Test Harness Mode should allow it to boot. 141 // If we succeeded setting up, we shouldn't be re-applying the THM steps every boot 142 // anyway. 143 getPersistentDataBlock().clearTestHarnessModeData(); 144 } 145 } 146 getTestHarnessModeData()147 private byte[] getTestHarnessModeData() { 148 PersistentDataBlockManagerInternal blockManager = getPersistentDataBlock(); 149 if (blockManager == null) { 150 Slog.e(TAG, "Failed to start Test Harness Mode; no implementation of " 151 + "PersistentDataBlockManagerInternal was bound!"); 152 return null; 153 } 154 byte[] testHarnessModeData = blockManager.getTestHarnessModeData(); 155 if (testHarnessModeData == null || testHarnessModeData.length == 0) { 156 // There's no data to apply, so leave it as-is. 157 return null; 158 } 159 return testHarnessModeData; 160 } 161 configureSettings()162 private void configureSettings() { 163 ContentResolver cr = getContext().getContentResolver(); 164 165 // If adb is already enabled, then we need to restart the daemon to pick up the change in 166 // keys. This is only really useful for userdebug/eng builds. 167 if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 1) { 168 SystemProperties.set("ctl.restart", "adbd"); 169 Slog.d(TAG, "Restarted adbd"); 170 } 171 172 // Disable the TTL for ADB keys before ADB is enabled as a part of AdbService's 173 // initialization. 174 Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0); 175 Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); 176 Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 0); 177 Settings.Global.putInt( 178 cr, 179 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 180 BatteryManager.BATTERY_PLUGGED_ANY); 181 Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1); 182 } 183 setUpAdbFiles(PersistentData persistentData)184 private void setUpAdbFiles(PersistentData persistentData) { 185 AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); 186 187 if (adbManager.getAdbKeysFile() != null) { 188 writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); 189 } 190 if (adbManager.getAdbTempKeysFile() != null) { 191 writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); 192 } 193 adbManager.notifyKeyFilesUpdated(); 194 } 195 configureUser()196 private void configureUser() { 197 int mainUserId = getMainUserId(); 198 199 ContentResolver.setMasterSyncAutomaticallyAsUser(false, mainUserId); 200 201 LocationManager locationManager = getContext().getSystemService(LocationManager.class); 202 locationManager.setLocationEnabledForUser(true, UserHandle.of(mainUserId)); 203 } 204 getMainUserId()205 private @UserIdInt int getMainUserId() { 206 UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); 207 int mainUserId = umi.getMainUserId(); 208 if (mainUserId >= 0) { 209 return mainUserId; 210 } else { 211 // If there is no MainUser, fall back to the historical usage of user 0. 212 Slog.w(TAG, "No MainUser exists; using user 0 instead"); 213 return UserHandle.USER_SYSTEM; 214 } 215 } 216 writeBytesToFile(byte[] keys, Path adbKeys)217 private void writeBytesToFile(byte[] keys, Path adbKeys) { 218 try { 219 OutputStream fileOutputStream = Files.newOutputStream(adbKeys); 220 fileOutputStream.write(keys); 221 fileOutputStream.close(); 222 223 Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys); 224 permissions.add(PosixFilePermission.GROUP_READ); 225 Files.setPosixFilePermissions(adbKeys, permissions); 226 } catch (IOException e) { 227 Slog.e(TAG, "Failed to set up adb keys", e); 228 // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all 229 // other settings will be set up. 230 } 231 } 232 233 // Setting the device as provisioned skips the setup wizard. setDeviceProvisioned()234 private void setDeviceProvisioned() { 235 ContentResolver cr = getContext().getContentResolver(); 236 Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1); 237 Settings.Secure.putIntForUser( 238 cr, 239 Settings.Secure.USER_SETUP_COMPLETE, 240 1, 241 UserHandle.USER_CURRENT); 242 } 243 showNotificationIfEnabled()244 private void showNotificationIfEnabled() { 245 if (!SystemProperties.getBoolean(TEST_HARNESS_MODE_PROPERTY, false)) { 246 return; 247 } 248 String title = getContext() 249 .getString(com.android.internal.R.string.test_harness_mode_notification_title); 250 String message = getContext() 251 .getString(com.android.internal.R.string.test_harness_mode_notification_message); 252 253 Notification notification = 254 new Notification.Builder(getContext(), SystemNotificationChannels.DEVELOPER) 255 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) 256 .setWhen(0) 257 .setOngoing(true) 258 .setTicker(title) 259 .setDefaults(0) // please be quiet 260 .setColor(getContext().getColor( 261 com.android.internal.R.color 262 .system_notification_accent_color)) 263 .setContentTitle(title) 264 .setContentText(message) 265 .setVisibility(Notification.VISIBILITY_PUBLIC) 266 .build(); 267 268 NotificationManager notificationManager = 269 getContext().getSystemService(NotificationManager.class); 270 notificationManager.notifyAsUser( 271 null, SystemMessage.NOTE_TEST_HARNESS_MODE_ENABLED, notification, UserHandle.ALL); 272 } 273 274 @Nullable getPersistentDataBlock()275 private PersistentDataBlockManagerInternal getPersistentDataBlock() { 276 if (mPersistentDataBlockManagerInternal == null) { 277 Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices"); 278 mPersistentDataBlockManagerInternal = 279 LocalServices.getService(PersistentDataBlockManagerInternal.class); 280 } 281 return mPersistentDataBlockManagerInternal; 282 } 283 284 private final IBinder mService = new Binder() { 285 @Override 286 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 287 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 288 (new TestHarnessModeShellCommand()) 289 .exec(this, in, out, err, args, callback, resultReceiver); 290 } 291 }; 292 293 private class TestHarnessModeShellCommand extends ShellCommand { 294 @Override onCommand(String cmd)295 public int onCommand(String cmd) { 296 if (cmd == null) { 297 return handleDefaultCommands(cmd); 298 } 299 switch (cmd) { 300 case "enable": 301 case "restore": 302 String opt; 303 while ((opt = getNextOption()) != null) { 304 switch (opt) { 305 case "--keep-memtag": 306 mEnableKeepMemtagMode = true; 307 break; 308 default: 309 getErrPrintWriter().println("Invalid option: " + opt); 310 return 1; 311 } 312 } 313 314 checkPermissions(); 315 final long originalId = Binder.clearCallingIdentity(); 316 try { 317 if (isDeviceSecure()) { 318 getErrPrintWriter().println( 319 "Test Harness Mode cannot be enabled if there is a lock " 320 + "screen"); 321 return 2; 322 } 323 return handleEnable(); 324 } finally { 325 Binder.restoreCallingIdentity(originalId); 326 } 327 default: 328 return handleDefaultCommands(cmd); 329 } 330 } 331 checkPermissions()332 private void checkPermissions() { 333 getContext().enforceCallingPermission( 334 android.Manifest.permission.ENABLE_TEST_HARNESS_MODE, 335 "You must hold android.permission.ENABLE_TEST_HARNESS_MODE " 336 + "to enable Test Harness Mode"); 337 } 338 isDeviceSecure()339 private boolean isDeviceSecure() { 340 KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class); 341 return keyguardManager.isDeviceSecure(getMainUserId()); 342 } 343 handleEnable()344 private int handleEnable() { 345 AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); 346 File adbKeys = adbManager.getAdbKeysFile(); 347 File adbTempKeys = adbManager.getAdbTempKeysFile(); 348 349 try { 350 byte[] adbKeysBytes = getBytesFromFile(adbKeys); 351 byte[] adbTempKeysBytes = getBytesFromFile(adbTempKeys); 352 353 PersistentData persistentData = new PersistentData(adbKeysBytes, adbTempKeysBytes); 354 PersistentDataBlockManagerInternal blockManager = getPersistentDataBlock(); 355 if (blockManager == null) { 356 Slog.e(TAG, "Failed to enable Test Harness Mode. No implementation of " 357 + "PersistentDataBlockManagerInternal was bound."); 358 getErrPrintWriter().println("Failed to enable Test Harness Mode"); 359 return 1; 360 } 361 blockManager.setTestHarnessModeData(persistentData.toBytes()); 362 } catch (IOException e) { 363 Slog.e(TAG, "Failed to store ADB keys.", e); 364 getErrPrintWriter().println("Failed to enable Test Harness Mode"); 365 return 1; 366 } 367 368 Intent i = new Intent(Intent.ACTION_FACTORY_RESET); 369 i.setPackage("android"); 370 i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 371 i.putExtra(Intent.EXTRA_REASON, TAG); 372 i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true); 373 i.putExtra("keep_memtag_mode", mEnableKeepMemtagMode); 374 getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM); 375 return 0; 376 } 377 getBytesFromFile(File file)378 private byte[] getBytesFromFile(File file) throws IOException { 379 if (file == null || !file.exists()) { 380 return new byte[0]; 381 } 382 Path path = file.toPath(); 383 try (InputStream inputStream = Files.newInputStream(path)) { 384 int size = (int) Files.size(path); 385 byte[] bytes = new byte[size]; 386 int numBytes = inputStream.read(bytes); 387 if (numBytes != size) { 388 throw new IOException("Failed to read the whole file"); 389 } 390 return bytes; 391 } 392 } 393 394 @Override onHelp()395 public void onHelp() { 396 PrintWriter pw = getOutPrintWriter(); 397 pw.println("About:"); 398 pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare"); 399 pw.println(" the device for running UI tests. The device is placed into this mode by"); 400 pw.println(" first wiping all data from the device, preserving ADB keys."); 401 pw.println(); 402 pw.println(" By default, the following settings are configured:"); 403 pw.println(" * Package Verifier is disabled"); 404 pw.println(" * Stay Awake While Charging is enabled"); 405 pw.println(" * OTA Updates are disabled"); 406 pw.println(" * Auto-Sync for accounts is disabled"); 407 pw.println(); 408 pw.println(" Other apps may configure themselves differently in Test Harness Mode by"); 409 pw.println(" checking ActivityManager.isRunningInUserTestHarness()"); 410 pw.println(); 411 pw.println("Test Harness Mode commands:"); 412 pw.println(" help"); 413 pw.println(" Print this help text."); 414 pw.println(); 415 pw.println(" enable|restore"); 416 pw.println(" Erase all data from this device and enable Test Harness Mode,"); 417 pw.println(" preserving the stored ADB keys currently on the device and toggling"); 418 pw.println(" settings in a way that are conducive to Instrumentation testing."); 419 } 420 } 421 422 /** 423 * The object that will serialize/deserialize the Test Harness Mode data to and from the 424 * persistent data block. 425 */ 426 public static class PersistentData { 427 static final byte VERSION_1 = 1; 428 static final byte VERSION_2 = 2; 429 430 final int mVersion; 431 final byte[] mAdbKeys; 432 final byte[] mAdbTempKeys; 433 PersistentData(byte[] adbKeys, byte[] adbTempKeys)434 PersistentData(byte[] adbKeys, byte[] adbTempKeys) { 435 this(VERSION_2, adbKeys, adbTempKeys); 436 } 437 PersistentData(int version, byte[] adbKeys, byte[] adbTempKeys)438 PersistentData(int version, byte[] adbKeys, byte[] adbTempKeys) { 439 this.mVersion = version; 440 this.mAdbKeys = adbKeys; 441 this.mAdbTempKeys = adbTempKeys; 442 } 443 fromBytes(byte[] bytes)444 static PersistentData fromBytes(byte[] bytes) throws SetUpTestHarnessModeException { 445 try { 446 DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); 447 int version = is.readInt(); 448 if (version == VERSION_1) { 449 // Version 1 of Test Harness Mode contained an "enabled" bit that we need to 450 // skip. If we don't, the binary format will be bad and it will fail to set up. 451 is.readBoolean(); 452 } 453 int adbKeysLength = is.readInt(); 454 byte[] adbKeys = new byte[adbKeysLength]; 455 is.readFully(adbKeys); 456 int adbTempKeysLength = is.readInt(); 457 byte[] adbTempKeys = new byte[adbTempKeysLength]; 458 is.readFully(adbTempKeys); 459 return new PersistentData(version, adbKeys, adbTempKeys); 460 } catch (IOException e) { 461 throw new SetUpTestHarnessModeException(e); 462 } 463 } 464 toBytes()465 byte[] toBytes() { 466 try { 467 ByteArrayOutputStream os = new ByteArrayOutputStream(); 468 DataOutputStream dos = new DataOutputStream(os); 469 dos.writeInt(VERSION_2); 470 dos.writeInt(mAdbKeys.length); 471 dos.write(mAdbKeys); 472 dos.writeInt(mAdbTempKeys.length); 473 dos.write(mAdbTempKeys); 474 dos.close(); 475 return os.toByteArray(); 476 } catch (IOException e) { 477 throw new RuntimeException(e); 478 } 479 } 480 } 481 482 /** 483 * An exception thrown when Test Harness Mode fails to set up. 484 * 485 * <p>In the event that Test Harness Mode fails to set up, all of the data should be discarded 486 * and the Test Harness Mode portion of the persistent data block should be wiped. This will 487 * prevent the device from becoming stuck, as there is no way (without rooting the device) to 488 * clear the persistent data block. 489 */ 490 private static class SetUpTestHarnessModeException extends Exception { SetUpTestHarnessModeException(Exception e)491 SetUpTestHarnessModeException(Exception e) { 492 super(e); 493 } 494 } 495 } 496