1 /*
2  * Copyright (C) 2014 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.compatibility.common.util.AbiUtils;
20 import com.android.cts.migration.MigrationHelper;
21 import com.android.tradefed.build.IBuildInfo;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.device.ITestDevice;
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 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 
35 /**
36  * Tests that verify installing of various split APKs from host side.
37  */
38 public class SplitTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
39     static final String PKG_NO_RESTART = "com.android.cts.norestart";
40     static final String APK_NO_RESTART_BASE = "CtsNoRestartBase.apk";
41     static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
42 
43     static final String PKG = "com.android.cts.splitapp";
44     static final String CLASS = ".SplitAppTest";
45 
46     static final String APK = "CtsSplitApp.apk";
47 
48     static final String APK_mdpi = "CtsSplitApp_mdpi-v4.apk";
49     static final String APK_hdpi = "CtsSplitApp_hdpi-v4.apk";
50     static final String APK_xhdpi = "CtsSplitApp_xhdpi-v4.apk";
51     static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
52 
53     private static final String APK_v7 = "CtsSplitApp_v7.apk";
54     private static final String APK_fr = "CtsSplitApp_fr.apk";
55     private static final String APK_de = "CtsSplitApp_de.apk";
56 
57     private static final String APK_x86 = "CtsSplitApp_x86.apk";
58     private static final String APK_x86_64 = "CtsSplitApp_x86_64.apk";
59     private static final String APK_armeabi_v7a = "CtsSplitApp_armeabi-v7a.apk";
60     private static final String APK_armeabi = "CtsSplitApp_armeabi.apk";
61     private static final String APK_arm64_v8a = "CtsSplitApp_arm64-v8a.apk";
62     private static final String APK_mips64 = "CtsSplitApp_mips64.apk";
63     private static final String APK_mips = "CtsSplitApp_mips.apk";
64 
65     private static final String APK_DIFF_REVISION = "CtsSplitAppDiffRevision.apk";
66     private static final String APK_DIFF_REVISION_v7 = "CtsSplitAppDiffRevision_v7.apk";
67 
68     private static final String APK_DIFF_VERSION = "CtsSplitAppDiffVersion.apk";
69     private static final String APK_DIFF_VERSION_v7 = "CtsSplitAppDiffVersion_v7.apk";
70 
71     private static final String APK_DIFF_CERT = "CtsSplitAppDiffCert.apk";
72     private static final String APK_DIFF_CERT_v7 = "CtsSplitAppDiffCert_v7.apk";
73 
74     private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
75     private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
76 
77     static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
78 
79     static {
80         ABI_TO_APK.put("x86", APK_x86);
81         ABI_TO_APK.put("x86_64", APK_x86_64);
82         ABI_TO_APK.put("armeabi-v7a", APK_armeabi_v7a);
83         ABI_TO_APK.put("armeabi", APK_armeabi);
84         ABI_TO_APK.put("arm64-v8a", APK_arm64_v8a);
85         ABI_TO_APK.put("mips64", APK_mips64);
86         ABI_TO_APK.put("mips", APK_mips);
87     }
88 
89     private IAbi mAbi;
90     private IBuildInfo mCtsBuild;
91 
92     @Override
setAbi(IAbi abi)93     public void setAbi(IAbi abi) {
94         mAbi = abi;
95     }
96 
97     @Override
setBuild(IBuildInfo buildInfo)98     public void setBuild(IBuildInfo buildInfo) {
99         mCtsBuild = buildInfo;
100     }
101 
102     @Override
setUp()103     protected void setUp() throws Exception {
104         super.setUp();
105 
106         assertNotNull(mAbi);
107         assertNotNull(mCtsBuild);
108 
109         getDevice().uninstallPackage(PKG);
110     }
111 
112     @Override
tearDown()113     protected void tearDown() throws Exception {
114         super.tearDown();
115 
116         getDevice().uninstallPackage(PKG);
117         getDevice().uninstallPackage(PKG_NO_RESTART);
118     }
119 
testSingleBase()120     public void testSingleBase() throws Exception {
121         new InstallMultiple().addApk(APK).run();
122         runDeviceTests(PKG, CLASS, "testSingleBase");
123     }
124 
testDensitySingle()125     public void testDensitySingle() throws Exception {
126         new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
127         runDeviceTests(PKG, CLASS, "testDensitySingle");
128     }
129 
testDensityAll()130     public void testDensityAll() throws Exception {
131         new InstallMultiple().addApk(APK).addApk(APK_mdpi).addApk(APK_hdpi).addApk(APK_xhdpi)
132                 .addApk(APK_xxhdpi).run();
133         runDeviceTests(PKG, CLASS, "testDensityAll");
134     }
135 
136     /**
137      * Install first with low-resolution resources, then add a split that offers
138      * higher-resolution resources.
139      */
testDensityBest()140     public void testDensityBest() throws Exception {
141         new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
142         runDeviceTests(PKG, CLASS, "testDensityBest1");
143 
144         // Now splice in an additional split which offers better resources
145         new InstallMultiple().inheritFrom(PKG).addApk(APK_xxhdpi).run();
146         runDeviceTests(PKG, CLASS, "testDensityBest2");
147     }
148 
149     /**
150      * Verify that an API-based split can change enabled/disabled state of
151      * manifest elements.
152      */
testApi()153     public void testApi() throws Exception {
154         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
155         runDeviceTests(PKG, CLASS, "testApi");
156     }
157 
testLocale()158     public void testLocale() throws Exception {
159         new InstallMultiple().addApk(APK).addApk(APK_de).addApk(APK_fr).run();
160         runDeviceTests(PKG, CLASS, "testLocale");
161     }
162 
163     /**
164      * Install test app with <em>single</em> split that exactly matches the
165      * currently active ABI. This also explicitly forces ABI when installing.
166      */
testNativeSingle()167     public void testNativeSingle() throws Exception {
168         final String abi = mAbi.getName();
169         final String apk = ABI_TO_APK.get(abi);
170         assertNotNull("Failed to find APK for ABI " + abi, apk);
171 
172         new InstallMultiple().addApk(APK).addApk(apk).run();
173         runDeviceTests(PKG, CLASS, "testNative");
174     }
175 
176     /**
177      * Install test app with <em>single</em> split that exactly matches the
178      * currently active ABI. This variant <em>does not</em> force the ABI when
179      * installing, instead exercising the system's ability to choose the ABI
180      * through inspection of the installed app.
181      */
testNativeSingleNatural()182     public void testNativeSingleNatural() throws Exception {
183         final String abi = mAbi.getName();
184         final String apk = ABI_TO_APK.get(abi);
185         assertNotNull("Failed to find APK for ABI " + abi, apk);
186 
187         new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
188         runDeviceTests(PKG, CLASS, "testNative");
189     }
190 
191     /**
192      * Install test app with <em>all</em> possible ABI splits. This also
193      * explicitly forces ABI when installing.
194      */
testNativeAll()195     public void testNativeAll() throws Exception {
196         final InstallMultiple inst = new InstallMultiple().addApk(APK);
197         for (String apk : ABI_TO_APK.values()) {
198             inst.addApk(apk);
199         }
200         inst.run();
201         runDeviceTests(PKG, CLASS, "testNative");
202     }
203 
204     /**
205      * Install test app with <em>all</em> possible ABI splits. This variant
206      * <em>does not</em> force the ABI when installing, instead exercising the
207      * system's ability to choose the ABI through inspection of the installed
208      * app.
209      */
testNativeAllNatural()210     public void testNativeAllNatural() throws Exception {
211         final InstallMultiple inst = new InstallMultiple().useNaturalAbi().addApk(APK);
212         for (String apk : ABI_TO_APK.values()) {
213             inst.addApk(apk);
214         }
215         inst.run();
216         runDeviceTests(PKG, CLASS, "testNative");
217     }
218 
testDuplicateBase()219     public void testDuplicateBase() throws Exception {
220         new InstallMultiple().addApk(APK).addApk(APK).runExpectingFailure();
221     }
222 
testDuplicateSplit()223     public void testDuplicateSplit() throws Exception {
224         new InstallMultiple().addApk(APK).addApk(APK_v7).addApk(APK_v7).runExpectingFailure();
225     }
226 
testDiffCert()227     public void testDiffCert() throws Exception {
228         new InstallMultiple().addApk(APK).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
229     }
230 
testDiffCertInherit()231     public void testDiffCertInherit() throws Exception {
232         new InstallMultiple().addApk(APK).run();
233         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
234     }
235 
testDiffVersion()236     public void testDiffVersion() throws Exception {
237         new InstallMultiple().addApk(APK).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
238     }
239 
testDiffVersionInherit()240     public void testDiffVersionInherit() throws Exception {
241         new InstallMultiple().addApk(APK).run();
242         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
243     }
244 
testDiffRevision()245     public void testDiffRevision() throws Exception {
246         new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
247         runDeviceTests(PKG, CLASS, "testRevision0_12");
248     }
249 
testDiffRevisionInheritBase()250     public void testDiffRevisionInheritBase() throws Exception {
251         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
252         runDeviceTests(PKG, CLASS, "testRevision0_0");
253         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
254         runDeviceTests(PKG, CLASS, "testRevision0_12");
255     }
256 
testDiffRevisionInheritSplit()257     public void testDiffRevisionInheritSplit() throws Exception {
258         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
259         runDeviceTests(PKG, CLASS, "testRevision0_0");
260         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
261         runDeviceTests(PKG, CLASS, "testRevision12_0");
262     }
263 
testDiffRevisionDowngrade()264     public void testDiffRevisionDowngrade() throws Exception {
265         new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
266         new InstallMultiple().inheritFrom(PKG).addApk(APK_v7).runExpectingFailure();
267     }
268 
testFeatureBase()269     public void testFeatureBase() throws Exception {
270         new InstallMultiple().addApk(APK).addApk(APK_FEATURE).run();
271         runDeviceTests(PKG, CLASS, "testFeatureBase");
272     }
273 
testFeatureApi()274     public void testFeatureApi() throws Exception {
275         new InstallMultiple().addApk(APK).addApk(APK_FEATURE).addApk(APK_FEATURE_v7).run();
276         runDeviceTests(PKG, CLASS, "testFeatureApi");
277     }
278 
testInheritUpdatedBase()279     public void testInheritUpdatedBase() throws Exception {
280         // TODO: flesh out this test
281     }
282 
testInheritUpdatedSplit()283     public void testInheritUpdatedSplit() throws Exception {
284         // TODO: flesh out this test
285     }
286 
testFeatureWithoutRestart()287     public void testFeatureWithoutRestart() throws Exception {
288         new InstallMultiple().addApk(APK).run();
289         new InstallMultiple().addApk(APK_NO_RESTART_BASE).run();
290         runDeviceTests(PKG, CLASS, "testBaseInstalled");
291         new InstallMultiple()
292                 .addArg("--dont-kill")
293                 .inheritFrom(PKG_NO_RESTART)
294                 .addApk(APK_NO_RESTART_FEATURE)
295                 .run();
296         runDeviceTests(PKG, CLASS, "testFeatureInstalled");
297     }
298 
299     /**
300      * Verify that installing a new version of app wipes code cache.
301      */
testClearCodeCache()302     public void testClearCodeCache() throws Exception {
303         new InstallMultiple().addApk(APK).run();
304         runDeviceTests(PKG, CLASS, "testCodeCacheWrite");
305         new InstallMultiple().addArg("-r").addApk(APK_DIFF_VERSION).run();
306         runDeviceTests(PKG, CLASS, "testCodeCacheRead");
307     }
308 
309     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple()310         public InstallMultiple() {
311             super(getDevice(), mCtsBuild, mAbi);
312         }
313     }
314 
315     public static class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
316         private final ITestDevice mDevice;
317         private final IBuildInfo mBuild;
318         private final IAbi mAbi;
319 
320         private final List<String> mArgs = new ArrayList<>();
321         private final List<File> mApks = new ArrayList<>();
322         private boolean mUseNaturalAbi;
323 
BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi)324         public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
325             mDevice = device;
326             mBuild = buildInfo;
327             mAbi = abi;
328             addArg("-g");
329         }
330 
addArg(String arg)331         T addArg(String arg) {
332             mArgs.add(arg);
333             return (T) this;
334         }
335 
addApk(String apk)336         T addApk(String apk) throws FileNotFoundException {
337             mApks.add(MigrationHelper.getTestFile(mBuild, apk));
338             return (T) this;
339         }
340 
inheritFrom(String packageName)341         T inheritFrom(String packageName) {
342             addArg("-r");
343             addArg("-p " + packageName);
344             return (T) this;
345         }
346 
useNaturalAbi()347         T useNaturalAbi() {
348             mUseNaturalAbi = true;
349             return (T) this;
350         }
351 
locationAuto()352         T locationAuto() {
353             addArg("--install-location 0");
354             return (T) this;
355         }
356 
locationInternalOnly()357         T locationInternalOnly() {
358             addArg("--install-location 1");
359             return (T) this;
360         }
361 
locationPreferExternal()362         T locationPreferExternal() {
363             addArg("--install-location 2");
364             return (T) this;
365         }
366 
forceUuid(String uuid)367         T forceUuid(String uuid) {
368             addArg("--force-uuid " + uuid);
369             return (T) this;
370         }
371 
run()372         void run() throws DeviceNotAvailableException {
373             run(true);
374         }
375 
runExpectingFailure()376         void runExpectingFailure() throws DeviceNotAvailableException {
377             run(false);
378         }
379 
run(boolean expectingSuccess)380         private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
381             final ITestDevice device = mDevice;
382 
383             // Create an install session
384             final StringBuilder cmd = new StringBuilder();
385             cmd.append("pm install-create");
386             for (String arg : mArgs) {
387                 cmd.append(' ').append(arg);
388             }
389             if (!mUseNaturalAbi) {
390                 cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName()));
391             }
392 
393             String result = device.executeShellCommand(cmd.toString());
394             assertTrue(result, result.startsWith("Success"));
395 
396             final int start = result.lastIndexOf("[");
397             final int end = result.lastIndexOf("]");
398             int sessionId = -1;
399             try {
400                 if (start != -1 && end != -1 && start < end) {
401                     sessionId = Integer.parseInt(result.substring(start + 1, end));
402                 }
403             } catch (NumberFormatException e) {
404             }
405             if (sessionId == -1) {
406                 throw new IllegalStateException("Failed to create install session: " + result);
407             }
408 
409             // Push our files into session. Ideally we'd use stdin streaming,
410             // but ddmlib doesn't support it yet.
411             for (int i = 0; i < mApks.size(); i++) {
412                 final File apk = mApks.get(i);
413                 final String remotePath = "/data/local/tmp/" + i + "_" + apk.getName();
414                 if (!device.pushFile(apk, remotePath)) {
415                     throw new IllegalStateException("Failed to push " + apk);
416                 }
417 
418                 cmd.setLength(0);
419                 cmd.append("pm install-write");
420                 cmd.append(' ').append(sessionId);
421                 cmd.append(' ').append(i + "_" + apk.getName());
422                 cmd.append(' ').append(remotePath);
423 
424                 result = device.executeShellCommand(cmd.toString());
425                 assertTrue(result, result.startsWith("Success"));
426             }
427 
428             // Everything staged; let's pull trigger
429             cmd.setLength(0);
430             cmd.append("pm install-commit");
431             cmd.append(' ').append(sessionId);
432 
433             result = device.executeShellCommand(cmd.toString());
434             if (expectingSuccess) {
435                 assertTrue(result, result.startsWith("Success"));
436             } else {
437                 assertFalse(result, result.startsWith("Success"));
438             }
439         }
440     }
441 
runDeviceTests(String packageName, String testClassName, String testMethodName)442     public void runDeviceTests(String packageName, String testClassName, String testMethodName)
443             throws DeviceNotAvailableException {
444         Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
445     }
446 }
447