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