1 /*
2  * Copyright (C) 2015 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 static android.appsecurity.cts.SplitTests.ABI_TO_APK;
20 import static android.appsecurity.cts.SplitTests.APK;
21 import static android.appsecurity.cts.SplitTests.APK_mdpi;
22 import static android.appsecurity.cts.SplitTests.APK_xxhdpi;
23 import static android.appsecurity.cts.SplitTests.CLASS;
24 import static android.appsecurity.cts.SplitTests.PKG;
25 
26 import android.appsecurity.cts.SplitTests.BaseInstallMultiple;
27 import com.android.cts.migration.MigrationHelper;
28 import com.android.tradefed.build.IBuildInfo;
29 import com.android.tradefed.device.CollectingOutputReceiver;
30 import com.android.tradefed.device.DeviceNotAvailableException;
31 import com.android.tradefed.testtype.DeviceTestCase;
32 import com.android.tradefed.testtype.IAbi;
33 import com.android.tradefed.testtype.IAbiReceiver;
34 import com.android.tradefed.testtype.IBuildReceiver;
35 
36 import java.util.Arrays;
37 import java.util.concurrent.TimeUnit;
38 
39 /**
40  * Set of tests that verify behavior of adopted storage media, if supported.
41  */
42 public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
43     private IAbi mAbi;
44     private IBuildInfo mCtsBuild;
45 
46     @Override
setAbi(IAbi abi)47     public void setAbi(IAbi abi) {
48         mAbi = abi;
49     }
50 
51     @Override
setBuild(IBuildInfo buildInfo)52     public void setBuild(IBuildInfo buildInfo) {
53         mCtsBuild = buildInfo;
54     }
55 
56     @Override
setUp()57     protected void setUp() throws Exception {
58         super.setUp();
59 
60         assertNotNull(mAbi);
61         assertNotNull(mCtsBuild);
62 
63         getDevice().uninstallPackage(PKG);
64     }
65 
66     @Override
tearDown()67     protected void tearDown() throws Exception {
68         super.tearDown();
69 
70         getDevice().uninstallPackage(PKG);
71     }
72 
testApps()73     public void testApps() throws Exception {
74         if (!hasAdoptable()) return;
75         final String diskId = getAdoptionDisk();
76         try {
77             final String abi = mAbi.getName();
78             final String apk = ABI_TO_APK.get(abi);
79             assertNotNull("Failed to find APK for ABI " + abi, apk);
80 
81             // Install simple app on internal
82             new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
83             runDeviceTests(PKG, CLASS, "testDataInternal");
84             runDeviceTests(PKG, CLASS, "testDataWrite");
85             runDeviceTests(PKG, CLASS, "testDataRead");
86             runDeviceTests(PKG, CLASS, "testNative");
87 
88             // Adopt that disk!
89             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
90             final LocalVolumeInfo vol = getAdoptionVolume();
91 
92             // Move app and verify
93             assertSuccess(getDevice().executeShellCommand(
94                     "pm move-package " + PKG + " " + vol.uuid));
95             runDeviceTests(PKG, CLASS, "testDataNotInternal");
96             runDeviceTests(PKG, CLASS, "testDataRead");
97             runDeviceTests(PKG, CLASS, "testNative");
98 
99             // Unmount, remount and verify
100             getDevice().executeShellCommand("sm unmount " + vol.volId);
101             getDevice().executeShellCommand("sm mount " + vol.volId);
102             runDeviceTests(PKG, CLASS, "testDataNotInternal");
103             runDeviceTests(PKG, CLASS, "testDataRead");
104             runDeviceTests(PKG, CLASS, "testNative");
105 
106             // Move app back and verify
107             assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
108             runDeviceTests(PKG, CLASS, "testDataInternal");
109             runDeviceTests(PKG, CLASS, "testDataRead");
110             runDeviceTests(PKG, CLASS, "testNative");
111 
112             // Un-adopt volume and app should still be fine
113             getDevice().executeShellCommand("sm partition " + diskId + " public");
114             runDeviceTests(PKG, CLASS, "testDataInternal");
115             runDeviceTests(PKG, CLASS, "testDataRead");
116             runDeviceTests(PKG, CLASS, "testNative");
117 
118         } finally {
119             cleanUp(diskId);
120         }
121     }
122 
testPrimaryStorage()123     public void testPrimaryStorage() throws Exception {
124         if (!hasAdoptable()) return;
125         final String diskId = getAdoptionDisk();
126         try {
127             final String originalVol = getDevice()
128                     .executeShellCommand("sm get-primary-storage-uuid").trim();
129 
130             if ("null".equals(originalVol)) {
131                 verifyPrimaryInternal(diskId);
132             } else if ("primary_physical".equals(originalVol)) {
133                 verifyPrimaryPhysical(diskId);
134             }
135         } finally {
136             cleanUp(diskId);
137         }
138     }
139 
verifyPrimaryInternal(String diskId)140     private void verifyPrimaryInternal(String diskId) throws Exception {
141         // Write some data to shared storage
142         new InstallMultiple().addApk(APK).run();
143         runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
144         runDeviceTests(PKG, CLASS, "testPrimaryInternal");
145         runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
146         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
147 
148         // Adopt that disk!
149         assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
150         final LocalVolumeInfo vol = getAdoptionVolume();
151 
152         // Move storage there and verify that data went along for ride
153         CollectingOutputReceiver out = new CollectingOutputReceiver();
154         getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid, out, 2,
155                 TimeUnit.HOURS, 1);
156         assertSuccess(out.getOutput());
157         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
158         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
159 
160         // Unmount and verify
161         getDevice().executeShellCommand("sm unmount " + vol.volId);
162         runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
163         getDevice().executeShellCommand("sm mount " + vol.volId);
164         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
165         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
166 
167         // Move app and verify backing storage volume is same
168         assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
169         runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
170         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
171 
172         // And move back to internal
173         out = new CollectingOutputReceiver();
174         getDevice().executeShellCommand("pm move-primary-storage internal", out, 2,
175                 TimeUnit.HOURS, 1);
176         assertSuccess(out.getOutput());
177 
178         runDeviceTests(PKG, CLASS, "testPrimaryInternal");
179         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
180 
181         assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
182         runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
183         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
184     }
185 
verifyPrimaryPhysical(String diskId)186     private void verifyPrimaryPhysical(String diskId) throws Exception {
187         // Write some data to shared storage
188         new InstallMultiple().addApk(APK).run();
189         runDeviceTests(PKG, CLASS, "testPrimaryPhysical");
190         runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
191         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
192 
193         // Adopt that disk!
194         assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
195         final LocalVolumeInfo vol = getAdoptionVolume();
196 
197         // Move primary storage there, but since we just nuked primary physical
198         // the storage device will be empty
199         assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
200         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
201         runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
202         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
203 
204         // Unmount and verify
205         getDevice().executeShellCommand("sm unmount " + vol.volId);
206         runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
207         getDevice().executeShellCommand("sm mount " + vol.volId);
208         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
209         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
210 
211         // And move to internal
212         assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
213         runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
214         runDeviceTests(PKG, CLASS, "testPrimaryInternal");
215         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
216     }
217 
218     /**
219      * Verify that we can install both new and inherited packages directly on
220      * adopted volumes.
221      */
testPackageInstaller()222     public void testPackageInstaller() throws Exception {
223         if (!hasAdoptable()) return;
224         final String diskId = getAdoptionDisk();
225         try {
226             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
227             final LocalVolumeInfo vol = getAdoptionVolume();
228 
229             // Install directly onto adopted volume
230             new InstallMultiple().locationAuto().forceUuid(vol.uuid)
231                     .addApk(APK).addApk(APK_mdpi).run();
232             runDeviceTests(PKG, CLASS, "testDataNotInternal");
233             runDeviceTests(PKG, CLASS, "testDensityBest1");
234 
235             // Now splice in an additional split which offers better resources
236             new InstallMultiple().locationAuto().inheritFrom(PKG)
237                     .addApk(APK_xxhdpi).run();
238             runDeviceTests(PKG, CLASS, "testDataNotInternal");
239             runDeviceTests(PKG, CLASS, "testDensityBest2");
240 
241         } finally {
242             cleanUp(diskId);
243         }
244     }
245 
246     /**
247      * Verify behavior when changes occur while adopted device is ejected and
248      * returned at a later time.
249      */
testEjected()250     public void testEjected() throws Exception {
251         if (!hasAdoptable()) return;
252         final String diskId = getAdoptionDisk();
253         try {
254             assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
255             final LocalVolumeInfo vol = getAdoptionVolume();
256 
257             // Install directly onto adopted volume, and write data there
258             new InstallMultiple().locationAuto().forceUuid(vol.uuid).addApk(APK).run();
259             runDeviceTests(PKG, CLASS, "testDataNotInternal");
260             runDeviceTests(PKG, CLASS, "testDataWrite");
261             runDeviceTests(PKG, CLASS, "testDataRead");
262 
263             // Now unmount and uninstall; leaving stale package on adopted volume
264             getDevice().executeShellCommand("sm unmount " + vol.volId);
265             getDevice().uninstallPackage(PKG);
266 
267             // Install second copy on internal, but don't write anything
268             new InstallMultiple().locationInternalOnly().addApk(APK).run();
269             runDeviceTests(PKG, CLASS, "testDataInternal");
270 
271             // Kick through a remount cycle, which should purge the adopted app
272             getDevice().executeShellCommand("sm mount " + vol.volId);
273             runDeviceTests(PKG, CLASS, "testDataInternal");
274             try {
275                 runDeviceTests(PKG, CLASS, "testDataRead");
276                 fail("Unexpected data from adopted volume picked up");
277             } catch (AssertionError expected) {
278             }
279             getDevice().executeShellCommand("sm unmount " + vol.volId);
280 
281             // Uninstall the internal copy and remount; we should have no record of app
282             getDevice().uninstallPackage(PKG);
283             getDevice().executeShellCommand("sm mount " + vol.volId);
284 
285             assertEmpty(getDevice().executeShellCommand("pm list packages " + PKG));
286         } finally {
287             cleanUp(diskId);
288         }
289     }
290 
hasAdoptable()291     private boolean hasAdoptable() throws Exception {
292         return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
293     }
294 
getAdoptionDisk()295     private String getAdoptionDisk() throws Exception {
296         final String disks = getDevice().executeShellCommand("sm list-disks adoptable");
297         if (disks == null || disks.length() == 0) {
298             throw new AssertionError("Devices that claim to support adoptable storage must have "
299                     + "adoptable media inserted during CTS to verify correct behavior");
300         }
301         return disks.split("\n")[0].trim();
302     }
303 
getAdoptionVolume()304     private LocalVolumeInfo getAdoptionVolume() throws Exception {
305         String[] lines = null;
306         int attempt = 0;
307         while (attempt++ < 15) {
308             lines = getDevice().executeShellCommand("sm list-volumes private").split("\n");
309             for (String line : lines) {
310                 final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
311                 if (!"private".equals(info.volId) && "mounted".equals(info.state)) {
312                     return info;
313                 }
314             }
315             Thread.sleep(1000);
316         }
317         throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
318     }
319 
cleanUp(String diskId)320     private void cleanUp(String diskId) throws Exception {
321         getDevice().executeShellCommand("sm partition " + diskId + " public");
322         getDevice().executeShellCommand("sm forget all");
323     }
324 
runDeviceTests(String packageName, String testClassName, String testMethodName)325     private void runDeviceTests(String packageName, String testClassName, String testMethodName)
326             throws DeviceNotAvailableException {
327         Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
328     }
329 
assertSuccess(String str)330     private static void assertSuccess(String str) {
331         if (str == null || !str.startsWith("Success")) {
332             throw new AssertionError("Expected success string but found " + str);
333         }
334     }
335 
assertEmpty(String str)336     private static void assertEmpty(String str) {
337         if (str != null && str.trim().length() > 0) {
338             throw new AssertionError("Expected empty string but found " + str);
339         }
340     }
341 
342     private static class LocalVolumeInfo {
343         public String volId;
344         public String state;
345         public String uuid;
346 
LocalVolumeInfo(String line)347         public LocalVolumeInfo(String line) {
348             final String[] split = line.split(" ");
349             volId = split[0];
350             state = split[1];
351             uuid = split[2];
352         }
353     }
354 
355     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple()356         public InstallMultiple() {
357             super(getDevice(), mCtsBuild, mAbi);
358         }
359     }
360 }
361