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 
DeviceFile(File localFile, File remoteFile, boolean addToInstallSession)53         private DeviceFile(File localFile, File remoteFile, boolean addToInstallSession) {
54             this.localFile = localFile;
55             this.remoteFile = remoteFile;
56             this.addToInstallSession = addToInstallSession;
57         }
58 
addToSession(File file)59         static DeviceFile addToSession(File file) {
60             return new DeviceFile(file, file, true);
61         }
62 
renameAndAddToSession(File localFile, File remoteFile)63         static DeviceFile renameAndAddToSession(File localFile, File remoteFile) {
64             return new DeviceFile(localFile, remoteFile, true);
65         }
66 
pushOnly(File file)67         static DeviceFile pushOnly(File file) {
68             return new DeviceFile(file, file, false);
69         }
70 
renameAndPushOnly(File localFile, File remoteFile)71         static DeviceFile renameAndPushOnly(File localFile, File remoteFile) {
72             return new DeviceFile(localFile, remoteFile, false);
73         }
74     }
75 
76     private final List<String> mArgs = new ArrayList<>();
77     private final List<DeviceFile> mFilesToAdd = new ArrayList<>();
78     private final List<String> mSplitsToRemove = new ArrayList<>();
79     private boolean mUseNaturalAbi = false;
80     private boolean mUseIncremental = false;
81 
BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi)82     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
83         this(device, buildInfo, abi, true);
84     }
85 
BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi, boolean grantPermissions)86     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi,
87             boolean grantPermissions) {
88         mDevice = device;
89         mBuild = buildInfo;
90         mAbi = abi;
91         if (grantPermissions) {
92             addArg("-g");
93         }
94     }
95 
addArg(String arg)96     T addArg(String arg) {
97         mArgs.add(arg);
98         return (T) this;
99     }
100 
addFile(String file)101     T addFile(String file) throws FileNotFoundException {
102         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
103         mFilesToAdd.add(DeviceFile.addToSession(buildHelper.getTestFile(file, mAbi)));
104         return (T) this;
105     }
106 
renameAndAddFile(String localFile, String remoteFile)107     T renameAndAddFile(String localFile, String remoteFile) throws FileNotFoundException {
108         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
109         mFilesToAdd.add(DeviceFile.renameAndAddToSession(buildHelper.getTestFile(localFile, mAbi),
110                 buildHelper.getTestFile(remoteFile, mAbi)));
111         return (T) this;
112     }
113 
pushFile(String file)114     T pushFile(String file) throws FileNotFoundException {
115         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
116         mFilesToAdd.add(DeviceFile.pushOnly(buildHelper.getTestFile(file, mAbi)));
117         return (T) this;
118     }
119 
renameAndPushFile(String localFile, String remoteFile)120     T renameAndPushFile(String localFile, String remoteFile) throws FileNotFoundException {
121         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
122         mFilesToAdd.add(DeviceFile.renameAndPushOnly(buildHelper.getTestFile(localFile, mAbi),
123                 buildHelper.getTestFile(remoteFile, mAbi)));
124         return (T) this;
125     }
126 
removeSplit(String split)127     T removeSplit(String split) {
128         mSplitsToRemove.add(split);
129         return (T) this;
130     }
131 
inheritFrom(String packageName)132     T inheritFrom(String packageName) {
133         addArg("-r");
134         addArg("-p " + packageName);
135         return (T) this;
136     }
137 
useNaturalAbi()138     T useNaturalAbi() {
139         mUseNaturalAbi = true;
140         return (T) this;
141     }
142 
useIncremental()143     T useIncremental() {
144         mUseIncremental = true;
145         return (T) this;
146     }
147 
allowTest()148     T allowTest() {
149         addArg("-t");
150         return (T) this;
151     }
152 
locationAuto()153     T locationAuto() {
154         addArg("--install-location 0");
155         return (T) this;
156     }
157 
locationInternalOnly()158     T locationInternalOnly() {
159         addArg("--install-location 1");
160         return (T) this;
161     }
162 
locationPreferExternal()163     T locationPreferExternal() {
164         addArg("--install-location 2");
165         return (T) this;
166     }
167 
forceUuid(String uuid)168     T forceUuid(String uuid) {
169         addArg("--force-uuid " + uuid);
170         return (T) this;
171     }
172 
forUser(int userId)173     T forUser(int userId) {
174         addArg("--user " + userId);
175         return (T) this;
176     }
177 
restrictPermissions()178     T restrictPermissions() {
179         addArg("--restrict-permissions");
180         return (T) this;
181     }
182 
deriveRemoteName(String originalName, int index)183     protected String deriveRemoteName(String originalName, int index) {
184         return index + "_" + originalName;
185     }
186 
run()187     void run() throws DeviceNotAvailableException {
188         run(true, null);
189     }
190 
run(boolean expectingSuccess)191     void run(boolean expectingSuccess) throws DeviceNotAvailableException {
192         run(expectingSuccess, null);
193     }
194 
runExpectingFailure()195     void runExpectingFailure() throws DeviceNotAvailableException {
196         run(false, null);
197     }
198 
runExpectingFailure(String failure)199     void runExpectingFailure(String failure) throws DeviceNotAvailableException {
200         run(false, failure);
201     }
202 
run(boolean expectingSuccess, String failure)203     private void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
204         if (mUseIncremental) {
205             runIncremental(expectingSuccess, failure);
206         } else {
207             runNonIncremental(expectingSuccess, failure);
208         }
209         cleanupDeviceFiles();
210     }
211 
runNonIncremental(boolean expectingSuccess, String failure)212     private void runNonIncremental(boolean expectingSuccess, String failure)
213             throws DeviceNotAvailableException {
214         final ITestDevice device = mDevice;
215 
216         // Create an install session
217         final StringBuilder cmd = new StringBuilder();
218         cmd.append("pm install-create");
219         for (String arg : mArgs) {
220             cmd.append(' ').append(arg);
221         }
222         if (!mUseNaturalAbi && mAbi != null) {
223             cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName()));
224         }
225 
226         String result = device.executeShellCommand(cmd.toString());
227         TestCase.assertTrue(result, result.startsWith("Success"));
228 
229         final int start = result.lastIndexOf("[");
230         final int end = result.lastIndexOf("]");
231         int sessionId = -1;
232         try {
233             if (start != -1 && end != -1 && start < end) {
234                 sessionId = Integer.parseInt(result.substring(start + 1, end));
235             }
236         } catch (NumberFormatException e) {
237         }
238         if (sessionId == -1) {
239             throw new IllegalStateException("Failed to create install session: " + result);
240         }
241 
242         // Push our files into session. Ideally we'd use stdin streaming,
243         // but ddmlib doesn't support it yet.
244         for (int i = 0; i < mFilesToAdd.size(); i++) {
245             final File localFile = mFilesToAdd.get(i).localFile;
246             final File remoteFile = mFilesToAdd.get(i).remoteFile;
247             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
248             final String remotePath = "/data/local/tmp/" + remoteName;
249             if (!device.pushFile(localFile, remotePath)) {
250                 throw new IllegalStateException("Failed to push " + localFile);
251             }
252 
253             if (!mFilesToAdd.get(i).addToInstallSession) {
254                 continue;
255             }
256 
257             cmd.setLength(0);
258             cmd.append("pm install-write");
259             cmd.append(' ').append(sessionId);
260             cmd.append(' ').append(remoteName);
261             cmd.append(' ').append(remotePath);
262 
263             result = device.executeShellCommand(cmd.toString());
264             TestCase.assertTrue(result, result.startsWith("Success"));
265         }
266 
267         for (int i = 0; i < mSplitsToRemove.size(); i++) {
268             final String split = mSplitsToRemove.get(i);
269 
270             cmd.setLength(0);
271             cmd.append("pm install-remove");
272             cmd.append(' ').append(sessionId);
273             cmd.append(' ').append(split);
274 
275             result = device.executeShellCommand(cmd.toString());
276             TestCase.assertTrue(result, result.startsWith("Success"));
277         }
278 
279         // Everything staged; let's pull trigger
280         cmd.setLength(0);
281         cmd.append("pm install-commit");
282         cmd.append(' ').append(sessionId);
283 
284         result = device.executeShellCommand(cmd.toString()).trim();
285         if (failure == null) {
286             if (expectingSuccess) {
287                 TestCase.assertTrue(result, result.startsWith("Success"));
288             } else {
289                 TestCase.assertFalse(result, result.startsWith("Success"));
290             }
291         } else {
292             TestCase.assertTrue(result, result.contains(failure));
293         }
294     }
295 
runIncremental(boolean expectingSuccess, String failure)296     private void runIncremental(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
297         final ITestDevice device = mDevice;
298 
299         if (!mSplitsToRemove.isEmpty()) {
300             throw new IllegalStateException("Incremental sessions can't remove splits");
301         }
302 
303         // Create an install session
304         final StringBuilder cmd = new StringBuilder();
305         cmd.append("pm install-incremental");
306         for (String arg : mArgs) {
307             cmd.append(' ').append(arg);
308         }
309         if (!mUseNaturalAbi && mAbi != null) {
310             cmd.append(' ').append(AbiUtils.createAbiFlag(mAbi.getName()));
311         }
312 
313         // Push our files into session. Ideally we'd use stdin streaming,
314         // but ddmlib doesn't support it yet.
315         for (int i = 0; i < mFilesToAdd.size(); i++) {
316             final File localFile = mFilesToAdd.get(i).localFile;
317             final File remoteFile = mFilesToAdd.get(i).remoteFile;
318             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
319             final String remotePath = "/data/local/tmp/" + remoteName;
320             if (!device.pushFile(localFile, remotePath)) {
321                 throw new IllegalStateException("Failed to push " + localFile);
322             }
323 
324             if (!mFilesToAdd.get(i).addToInstallSession) {
325                 continue;
326             }
327 
328             cmd.append(' ').append(remotePath);
329         }
330 
331         // Everything staged; let's pull trigger
332         String result = device.executeShellCommand(cmd.toString()).trim();
333         if (failure == null) {
334             if (expectingSuccess) {
335                 TestCase.assertTrue(result, result.startsWith("Success"));
336             } else {
337                 TestCase.assertFalse(result, result.startsWith("Success"));
338             }
339         } else {
340             TestCase.assertTrue(result, result.contains(failure));
341         }
342     }
343 
cleanupDeviceFiles()344     private void cleanupDeviceFiles() throws DeviceNotAvailableException {
345         final ITestDevice device = mDevice;
346         for (int i = 0; i < mFilesToAdd.size(); i++) {
347             final File remoteFile = mFilesToAdd.get(i).remoteFile;
348             final String remoteName = deriveRemoteName(remoteFile.getName(), i);
349             final String remotePath = "/data/local/tmp/" + remoteName;
350             device.deleteFile(remotePath);
351         }
352     }
353 }
354