• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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