1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 package android.appsecurity.cts;
17 
18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.testtype.IAbi;
23 import com.android.tradefed.util.AbiUtils;
24 
25 import junit.framework.TestCase;
26 
27 import java.io.File;
28 import java.io.FileNotFoundException;
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
34  *
35  * <code>
36  *     private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; {
37  *         public InstallMultiple() {
38  *             super(getDevice(), null, null);
39  *         }
40  *     }
41  * </code>
42  */
43 public class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
44     private final ITestDevice mDevice;
45     private final IBuildInfo mBuild;
46     private final IAbi mAbi;
47 
48     static class DeviceFile {
49         public final File localFile;
50         public final File remoteFile;
51         public final boolean addToInstallSession;
52         public final boolean manageRemoteFile;
53 
DeviceFile(File localFile, File remoteFile, boolean addToInstallSession, boolean manageRemoteFile)54         private DeviceFile(File localFile, File remoteFile, boolean addToInstallSession,
55                 boolean manageRemoteFile) {
56             this.localFile = localFile;
57             this.remoteFile = remoteFile;
58             this.addToInstallSession = addToInstallSession;
59             this.manageRemoteFile = manageRemoteFile;
60         }
61 
addToSession(File file)62         static DeviceFile addToSession(File file) {
63             return new DeviceFile(file, file, true, true);
64         }
65 
renameAndAddToSession(File localFile, File remoteFile)66         static DeviceFile renameAndAddToSession(File localFile, File remoteFile) {
67             return new DeviceFile(localFile, remoteFile, true, true);
68         }
69 
pushOnly(File file)70         static DeviceFile pushOnly(File file) {
71             return new DeviceFile(file, file, false, true);
72         }
73 
renameAndPushOnly(File localFile, File remoteFile)74         static DeviceFile renameAndPushOnly(File localFile, File remoteFile) {
75             return new DeviceFile(localFile, remoteFile, false, true);
76         }
77 
addRemoteFileToSession(File remoteFile)78         static DeviceFile addRemoteFileToSession(File remoteFile) {
79             return new DeviceFile(null, remoteFile, true, false);
80         }
81     }
82 
83     private final List<String> mArgs = new ArrayList<>();
84     private final List<DeviceFile> mFilesToAdd = new ArrayList<>();
85     private final List<String> mSplitsToRemove = new ArrayList<>();
86     private boolean mUseNaturalAbi = false;
87     private boolean mUseIncremental = false;
88     private boolean mAddFileNamePrefix = true;
89 
BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi)90     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
91         this(device, buildInfo, abi, true);
92     }
93 
BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, boolean grantPermissions)94     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi,
95             boolean grantPermissions) {
96         mDevice = device;
97         mBuild = buildInfo;
98         mAbi = abi;
99         if (grantPermissions) {
100             addArg("-g");
101         }
102         // Allow the install of test apps
103         addArg("-t");
104     }
105 
addArg(String arg)106     T addArg(String arg) {
107         mArgs.add(arg);
108         return (T) this;
109     }
110 
addFile(String file)111     T addFile(String file) throws FileNotFoundException {
112         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
113         mFilesToAdd.add(DeviceFile.addToSession(buildHelper.getTestFile(file, mAbi)));
114         return (T) this;
115     }
116 
disableFileNamePrefix()117     T disableFileNamePrefix() {
118         mAddFileNamePrefix = false;
119         return (T) this;
120     }
121 
addRemoteFile(String remoteFile)122     T addRemoteFile(String remoteFile) {
123         mFilesToAdd.add(DeviceFile.addRemoteFileToSession(new File(remoteFile)));
124         return (T) this;
125     }
126 
renameAndAddFile(String localFile, String remoteFile)127     T renameAndAddFile(String localFile, String remoteFile) throws FileNotFoundException {
128         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
129         mFilesToAdd.add(DeviceFile.renameAndAddToSession(buildHelper.getTestFile(localFile, mAbi),
130                 buildHelper.getTestFile(remoteFile, mAbi)));
131         return (T) this;
132     }
133 
pushFile(String file)134     T pushFile(String file) throws FileNotFoundException {
135         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
136         mFilesToAdd.add(DeviceFile.pushOnly(buildHelper.getTestFile(file, mAbi)));
137         return (T) this;
138     }
139 
renameAndPushFile(String localFile, String remoteFile)140     T renameAndPushFile(String localFile, String remoteFile) throws FileNotFoundException {
141         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
142         mFilesToAdd.add(DeviceFile.renameAndPushOnly(buildHelper.getTestFile(localFile, mAbi),
143                 buildHelper.getTestFile(remoteFile, mAbi)));
144         return (T) this;
145     }
146 
removeSplit(String split)147     T removeSplit(String split) {
148         mSplitsToRemove.add(split);
149         return (T) this;
150     }
151 
inheritFrom(String packageName)152     T inheritFrom(String packageName) {
153         addArg("-r");
154         addArg("-p " + packageName);
155         return (T) this;
156     }
157 
useNaturalAbi()158     T useNaturalAbi() {
159         mUseNaturalAbi = true;
160         return (T) this;
161     }
162 
useIncremental()163     T useIncremental() {
164         mUseIncremental = true;
165         return (T) this;
166     }
167 
allowTest()168     T allowTest() {
169         addArg("-t");
170         return (T) this;
171     }
172 
locationAuto()173     T locationAuto() {
174         addArg("--install-location 0");
175         return (T) this;
176     }
177 
locationInternalOnly()178     T locationInternalOnly() {
179         addArg("--install-location 1");
180         return (T) this;
181     }
182 
locationPreferExternal()183     T locationPreferExternal() {
184         addArg("--install-location 2");
185         return (T) this;
186     }
187 
forceUuid(String uuid)188     T forceUuid(String uuid) {
189         addArg("--force-uuid " + uuid);
190         return (T) this;
191     }
192 
forUser(int userId)193     T forUser(int userId) {
194         addArg("--user " + userId);
195         return (T) this;
196     }
197 
restrictPermissions()198     T restrictPermissions() {
199         addArg("--restrict-permissions");
200         return (T) this;
201     }
202 
forceQueryable()203     T forceQueryable() {
204         addArg("--force-queryable");
205         return (T) this;
206     }
207 
bypassLowTargetSdkBlock()208     T bypassLowTargetSdkBlock() {
209         addArg("--bypass-low-target-sdk-block");
210         return (T) this;
211     }
212 
213 
deriveRemoteName(String originalName, int index)214     protected String deriveRemoteName(String originalName, int index) {
215         return (mAddFileNamePrefix ? index + "_" + originalName : originalName);
216     }
217 
run()218     void run() throws DeviceNotAvailableException {
219         run(true, null);
220     }
221 
run(boolean expectingSuccess)222     void run(boolean expectingSuccess) throws DeviceNotAvailableException {
223         run(expectingSuccess, null);
224     }
225 
runExpectingFailure()226     void runExpectingFailure() throws DeviceNotAvailableException {
227         run(false, null);
228     }
229 
runExpectingFailure(String failure)230     void runExpectingFailure(String failure) throws DeviceNotAvailableException {
231         run(false, failure);
232     }
233 
run(boolean expectingSuccess, String failure)234     void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
235         if (mUseIncremental) {
236             runIncremental(expectingSuccess, failure);
237         } else {
238             runNonIncremental(expectingSuccess, failure);
239         }
240         cleanupDeviceFiles();
241     }
242 
runForResult()243     String runForResult() throws DeviceNotAvailableException {
244         String result;
245         if (mUseIncremental) {
246             result = runIncrementalForResult();
247         } else {
248             result = runNonIncrementalForResult();
249         }
250         cleanupDeviceFiles();
251         return result;
252     }
253 
runNonIncrementalForResult()254     private String runNonIncrementalForResult() throws DeviceNotAvailableException {
255         final ITestDevice device = mDevice;
256 
257         // Create an install session
258         final StringBuilder cmd = new StringBuilder();
259         cmd.append("pm install-create");
260         for (String arg : mArgs) {
261             cmd.append(' ').append(arg);
262         }
263         if (!mUseNaturalAbi && mAbi != null) {
264             cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName()));
265         }
266 
267         String result = device.executeShellCommand(cmd.toString());
268         TestCase.assertTrue(result, result.startsWith("Success"));
269 
270         final int start = result.lastIndexOf("[");
271         final int end = result.lastIndexOf("]");
272         int sessionId = -1;
273         try {
274             if (start != -1 && end != -1 && start < end) {
275                 sessionId = Integer.parseInt(result.substring(start + 1, end));
276             }
277         } catch (NumberFormatException e) {
278         }
279         if (sessionId == -1) {
280             throw new IllegalStateException("Failed to create install session: " + result);
281         }
282 
283         // Push our files into session. Ideally we'd use stdin streaming,
284         // but ddmlib doesn't support it yet.
285         for (int i = 0; i < mFilesToAdd.size(); i++) {
286             boolean manageRemoteFile = mFilesToAdd.get(i).manageRemoteFile;
287             final File localFile = mFilesToAdd.get(i).localFile;
288             final File remoteFile = mFilesToAdd.get(i).remoteFile;
289             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
290             final String remotePath = "/data/local/tmp/" + remoteName;
291             if (manageRemoteFile && !device.pushFile(localFile, remotePath)) {
292                 throw new IllegalStateException("Failed to push " + localFile);
293             }
294 
295             if (!mFilesToAdd.get(i).addToInstallSession) {
296                 continue;
297             }
298 
299             cmd.setLength(0);
300             cmd.append("pm install-write");
301             cmd.append(' ').append(sessionId);
302             cmd.append(' ').append(remoteName);
303             cmd.append(' ').append(remotePath);
304 
305             result = device.executeShellCommand(cmd.toString());
306             TestCase.assertTrue(result, result.startsWith("Success"));
307         }
308 
309         for (int i = 0; i < mSplitsToRemove.size(); i++) {
310             final String split = mSplitsToRemove.get(i);
311 
312             cmd.setLength(0);
313             cmd.append("pm install-remove");
314             cmd.append(' ').append(sessionId);
315             cmd.append(' ').append(split);
316 
317             result = device.executeShellCommand(cmd.toString());
318             TestCase.assertTrue(result, result.startsWith("Success"));
319         }
320 
321         // Everything staged; let's pull trigger
322         cmd.setLength(0);
323         cmd.append("pm install-commit");
324         cmd.append(' ').append(sessionId);
325 
326         return device.executeShellCommand(cmd.toString()).trim();
327     }
328 
runNonIncremental(boolean expectingSuccess, String failure)329     private void runNonIncremental(boolean expectingSuccess, String failure)
330             throws DeviceNotAvailableException {
331         String result = runNonIncrementalForResult();
332         if (failure == null) {
333             if (expectingSuccess) {
334                 TestCase.assertTrue(result, result.startsWith("Success"));
335             } else {
336                 TestCase.assertFalse(result, result.startsWith("Success"));
337             }
338         } else {
339             TestCase.assertTrue(result, result.contains(failure));
340         }
341     }
342 
runIncrementalForResult()343     private String runIncrementalForResult() throws DeviceNotAvailableException {
344         final ITestDevice device = mDevice;
345 
346         if (!mSplitsToRemove.isEmpty()) {
347             throw new IllegalStateException("Incremental sessions can't remove splits");
348         }
349 
350         // Create an install session
351         final StringBuilder cmd = new StringBuilder();
352         cmd.append("pm install-incremental");
353         for (String arg : mArgs) {
354             cmd.append(' ').append(arg);
355         }
356         if (!mUseNaturalAbi && mAbi != null) {
357             cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName()));
358         }
359 
360         // Push our files into session. Ideally we'd use stdin streaming,
361         // but ddmlib doesn't support it yet.
362         for (int i = 0; i < mFilesToAdd.size(); i++) {
363             boolean manageRemoteFile = mFilesToAdd.get(i).manageRemoteFile;
364             final File localFile = mFilesToAdd.get(i).localFile;
365             final File remoteFile = mFilesToAdd.get(i).remoteFile;
366             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
367             final String remotePath = "/data/local/tmp/" + remoteName;
368             if (manageRemoteFile && !device.pushFile(localFile, remotePath)) {
369                 throw new IllegalStateException("Failed to push " + localFile);
370             }
371 
372             if (!mFilesToAdd.get(i).addToInstallSession) {
373                 continue;
374             }
375 
376             cmd.append(' ').append(remotePath);
377         }
378 
379         // Everything staged; let's pull trigger
380         return device.executeShellCommand(cmd.toString()).trim();
381     }
382 
runIncremental(boolean expectingSuccess, String failure)383     private void runIncremental(boolean expectingSuccess, String failure)
384             throws DeviceNotAvailableException {
385         String result = runIncrementalForResult();
386         if (failure == null) {
387             if (expectingSuccess) {
388                 TestCase.assertTrue(result, result.startsWith("Success"));
389             } else {
390                 TestCase.assertFalse(result, result.startsWith("Success"));
391             }
392         } else {
393             TestCase.assertTrue(result, result.contains(failure));
394         }
395     }
396 
cleanupDeviceFiles()397     private void cleanupDeviceFiles() throws DeviceNotAvailableException {
398         final ITestDevice device = mDevice;
399         for (int i = 0; i < mFilesToAdd.size(); i++) {
400             if (!mFilesToAdd.get(i).manageRemoteFile) {
401                 continue;
402             }
403             final File remoteFile = mFilesToAdd.get(i).remoteFile;
404             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
405             final String remotePath = "/data/local/tmp/" + remoteName;
406             device.deleteFile(remotePath);
407         }
408     }
409 }
410