1 /*
2  * Copyright (C) 2016 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 android.appsecurity.cts;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.CollectingOutputReceiver;
21 import com.android.ddmlib.Log;
22 import com.android.tradefed.build.IBuildInfo;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.testtype.DeviceTestCase;
25 import com.android.tradefed.testtype.IAbi;
26 import com.android.tradefed.testtype.IAbiReceiver;
27 import com.android.tradefed.testtype.IBuildReceiver;
28 
29 /**
30  * Set of tests that verify behavior of direct boot, if supported.
31  * <p>
32  * Note that these tests drive PIN setup manually instead of relying on device
33  * administrators, which are not supported by all devices.
34  */
35 public class DirectBootHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
36     private static final String TAG = "DirectBootHostTest";
37 
38     private static final String PKG = "com.android.cts.encryptionapp";
39     private static final String CLASS = ".EncryptionAppTest";
40     private static final String APK = "CtsEncryptionApp.apk";
41 
42     private static final String OTHER_APK = "CtsSplitApp.apk";
43     private static final String OTHER_PKG = "com.android.cts.splitapp";
44     private static final String OTHER_CLASS = ".SplitAppTest";
45 
46     private static final String MODE_NATIVE = "native";
47     private static final String MODE_EMULATED = "emulated";
48     private static final String MODE_NONE = "none";
49 
50     private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin\n";
51     private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive\n";
52 
53     private static final long SHUTDOWN_TIME_MS = 30 * 1000;
54 
55     private String mFeatureList = null;
56 
57     private int[] mUsers;
58     private IAbi mAbi;
59     private IBuildInfo mCtsBuild;
60 
61     @Override
setAbi(IAbi abi)62     public void setAbi(IAbi abi) {
63         mAbi = abi;
64     }
65 
66     @Override
setBuild(IBuildInfo buildInfo)67     public void setBuild(IBuildInfo buildInfo) {
68         mCtsBuild = buildInfo;
69     }
70 
71     @Override
setUp()72     protected void setUp() throws Exception {
73         super.setUp();
74 
75         mUsers = Utils.prepareSingleUser(getDevice());
76         assertNotNull(mAbi);
77         assertNotNull(mCtsBuild);
78 
79         getDevice().uninstallPackage(PKG);
80         getDevice().uninstallPackage(OTHER_PKG);
81     }
82 
83     @Override
tearDown()84     protected void tearDown() throws Exception {
85         super.tearDown();
86 
87         getDevice().uninstallPackage(PKG);
88         getDevice().uninstallPackage(OTHER_PKG);
89     }
90 
91     /**
92      * Automotive devices MUST support native FBE.
93      */
testAutomotiveNativeFbe()94     public void testAutomotiveNativeFbe() throws Exception {
95         if (!isSupportedDevice()) {
96             Log.v(TAG, "Device not supported; skipping test");
97             return;
98         } else if (!isAutomotiveDevice()) {
99             Log.v(TAG, "Device not automotive; skipping test");
100             return;
101         }
102 
103         assertTrue("Automotive devices must support native FBE",
104             MODE_NATIVE.equals(getFbeMode()));
105     }
106 
107     /**
108      * If device has native FBE, verify lifecycle.
109      */
testDirectBootNative()110     public void testDirectBootNative() throws Exception {
111         if (!isSupportedDevice()) {
112             Log.v(TAG, "Device not supported; skipping test");
113             return;
114         } else if (!MODE_NATIVE.equals(getFbeMode())) {
115             Log.v(TAG, "Device doesn't have native FBE; skipping test");
116             return;
117         }
118 
119         doDirectBootTest(MODE_NATIVE);
120     }
121 
122     /**
123      * If device doesn't have native FBE, enable emulation and verify lifecycle.
124      */
testDirectBootEmulated()125     public void testDirectBootEmulated() throws Exception {
126         if (!isSupportedDevice()) {
127             Log.v(TAG, "Device not supported; skipping test");
128             return;
129         } else if (MODE_NATIVE.equals(getFbeMode())) {
130             Log.v(TAG, "Device has native FBE; skipping test");
131             return;
132         }
133 
134         doDirectBootTest(MODE_EMULATED);
135     }
136 
137     /**
138      * If device doesn't have native FBE, verify normal lifecycle.
139      */
testDirectBootNone()140     public void testDirectBootNone() throws Exception {
141         if (!isSupportedDevice()) {
142             Log.v(TAG, "Device not supported; skipping test");
143             return;
144         } else if (MODE_NATIVE.equals(getFbeMode())) {
145             Log.v(TAG, "Device has native FBE; skipping test");
146             return;
147         }
148 
149         doDirectBootTest(MODE_NONE);
150     }
151 
doDirectBootTest(String mode)152     public void doDirectBootTest(String mode) throws Exception {
153         boolean doTest = true;
154         try {
155             // Set up test app and secure lock screens
156             new InstallMultiple().addApk(APK).run();
157             new InstallMultiple().addApk(OTHER_APK).run();
158 
159             // To receive boot broadcasts, kick our other app out of stopped state
160             getDevice().executeShellCommand("am start -a android.intent.action.MAIN"
161                     + " -c android.intent.category.LAUNCHER com.android.cts.splitapp/.MyActivity");
162 
163             // Give enough time for PackageManager to persist stopped state
164             Thread.sleep(15000);
165 
166             runDeviceTests(PKG, CLASS, "testSetUp", mUsers);
167 
168             // Give enough time for vold to update keys
169             Thread.sleep(15000);
170 
171             // Reboot system into known state with keys ejected
172             if (MODE_EMULATED.equals(mode)) {
173                 final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
174                 if (res != null && res.contains("Emulation not supported")) {
175                     doTest = false;
176                 }
177                 getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
178                 getDevice().waitForDeviceOnline();
179             } else {
180                 getDevice().rebootUntilOnline();
181             }
182             waitForBootCompleted();
183 
184             if (doTest) {
185                 if (MODE_NONE.equals(mode)) {
186                     runDeviceTests(PKG, CLASS, "testVerifyUnlockedAndDismiss", mUsers);
187                 } else {
188                     runDeviceTests(PKG, CLASS, "testVerifyLockedAndDismiss", mUsers);
189                 }
190             }
191 
192         } finally {
193             try {
194                 // Remove secure lock screens and tear down test app
195                 runDeviceTests(PKG, CLASS, "testTearDown", mUsers);
196             } finally {
197                 getDevice().uninstallPackage(PKG);
198 
199                 // Get ourselves back into a known-good state
200                 if (MODE_EMULATED.equals(mode)) {
201                     getDevice().executeShellCommand("sm set-emulate-fbe false");
202                     getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
203                     getDevice().waitForDeviceOnline();
204                 } else {
205                     getDevice().rebootUntilOnline();
206                 }
207                 getDevice().waitForDeviceAvailable();
208             }
209         }
210     }
211 
runDeviceTests(String packageName, String testClassName, String testMethodName, int... users)212     private void runDeviceTests(String packageName, String testClassName, String testMethodName,
213             int... users) throws DeviceNotAvailableException {
214         for (int user : users) {
215             Log.d(TAG, "runDeviceTests " + testMethodName + " u" + user);
216             Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, user);
217         }
218     }
219 
getFbeMode()220     private String getFbeMode() throws Exception {
221         return getDevice().executeShellCommand("sm get-fbe-mode").trim();
222     }
223 
isBootCompleted()224     private boolean isBootCompleted() throws Exception {
225         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
226         try {
227             getDevice().getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
228         } catch (AdbCommandRejectedException e) {
229             // do nothing: device might be temporarily disconnected
230             Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
231         }
232         String output = receiver.getOutput();
233         if (output != null) {
234             output = output.trim();
235         }
236         return "1".equals(output);
237     }
238 
hasSystemFeature(final String feature)239     private boolean hasSystemFeature(final String feature) throws Exception {
240         if (mFeatureList == null) {
241             mFeatureList = getDevice().executeShellCommand("pm list features");
242         }
243 
244         return mFeatureList.contains(feature);
245     }
246 
isSupportedDevice()247     private boolean isSupportedDevice() throws Exception {
248         return hasSystemFeature(FEATURE_DEVICE_ADMIN);
249     }
250 
isAutomotiveDevice()251     private boolean isAutomotiveDevice() throws Exception {
252         return hasSystemFeature(FEATURE_AUTOMOTIVE);
253     }
254 
waitForBootCompleted()255     private void waitForBootCompleted() throws Exception {
256         for (int i = 0; i < 45; i++) {
257             if (isBootCompleted()) {
258                 Log.d(TAG, "Yay, system is ready!");
259                 // or is it really ready?
260                 // guard against potential USB mode switch weirdness at boot
261                 Thread.sleep(10 * 1000);
262                 return;
263             }
264             Log.d(TAG, "Waiting for system ready...");
265             Thread.sleep(1000);
266         }
267         throw new AssertionError("System failed to become ready!");
268     }
269 
270     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple()271         public InstallMultiple() {
272             super(getDevice(), mCtsBuild, mAbi);
273         }
274     }
275 }
276