1 /*
2  * Copyright (C) 2012 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 com.android.framework.tests;
18 
19 import com.android.tradefed.device.DeviceNotAvailableException;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.log.LogUtil.CLog;
22 
23 import org.junit.Assert;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Node;
26 
27 import java.io.File;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathConstants;
35 import javax.xml.xpath.XPathExpression;
36 import javax.xml.xpath.XPathFactory;
37 /**
38  * Utility method used for PackageMangerOTATests. Requires adb root.
39  */
40 public class PackageManagerOTATestUtils {
41     public static final String USERDATA_PARTITION = "userdata";
42     public static final String PACKAGE_XML_FILE = "/data/system/packages.xml";
43 
44     private ITestDevice mDevice = null;
45 
46     /**
47      * Constructor.
48      *
49      * @param device the {@link ITestDevice} to use when performing operations.
50      * @throws DeviceNotAvailableException
51      */
PackageManagerOTATestUtils(ITestDevice device)52     public PackageManagerOTATestUtils(ITestDevice device)
53             throws DeviceNotAvailableException {
54         mDevice = device;
55     }
56 
57     /**
58      * Wipe userdata partition on device.
59      *
60      * @throws DeviceNotAvailableException
61      */
wipeDevice()62     public void wipeDevice() throws DeviceNotAvailableException {
63         // Make sure to keep the local.prop file for testing purposes.
64         File prop = mDevice.pullFile("/data/local.prop");
65         mDevice.rebootIntoBootloader();
66         mDevice.fastbootWipePartition(USERDATA_PARTITION);
67         mDevice.rebootUntilOnline();
68         if (prop != null) {
69             mDevice.pushFile(prop, "/data/local.prop");
70             mDevice.executeShellCommand("chmod 644 /data/local.prop");
71             mDevice.reboot();
72         }
73     }
74 
75     /**
76      * Remove a system app.
77      * @param systemApp {@link String} name for the application in the /system/app folder
78      * @param reboot set to <code>true</code> to optionally reboot device after app removal
79      *
80      * @throws DeviceNotAvailableException
81      */
removeSystemApp(String systemApp, boolean reboot)82     public void removeSystemApp(String systemApp, boolean reboot)
83             throws DeviceNotAvailableException {
84         mDevice.remountSystemWritable();
85         String cmd = String.format("rm %s", systemApp);
86         mDevice.executeShellCommand(cmd);
87         if (reboot) {
88             mDevice.reboot();
89         }
90         mDevice.waitForDeviceAvailable();
91     }
92 
93     /**
94      * Remove a system app and wipe the device.
95      * @param systemApp {@link String} name for the application in the /system/app folder
96      *
97      * @throws DeviceNotAvailableException
98      */
removeAndWipe(String systemApp)99     public void removeAndWipe(String systemApp)
100             throws DeviceNotAvailableException {
101         removeSystemApp(systemApp, false);
102         wipeDevice();
103     }
104 
105     /**
106      * Expect that a given xpath exists in a given xml file.
107      * @param xmlFile {@link File} xml file to process
108      * @param xPathString {@link String} Xpath to look for
109      *
110      * @return true if the xpath is found
111      */
expectExists(File xmlFile, String xPathString)112     public boolean expectExists(File xmlFile, String xPathString) {
113         Node n = getNodeForXPath(xmlFile, xPathString);
114         if (n != null) {
115             CLog.d("Found node %s for xpath %s", n.getNodeName(), xPathString);
116             return true;
117         }
118         return false;
119     }
120 
121     /**
122      * Expect that the value of a given xpath starts with a given string.
123      *
124      * @param xmlFile {@link File} the xml file in question
125      * @param xPathString {@link String} the xpath to look for
126      * @param value {@link String} the expected start string of the xpath
127      *
128      * @return true if the value for the xpath starts with value, false otherwise
129      */
expectStartsWith(File xmlFile, String xPathString, String value)130     public boolean expectStartsWith(File xmlFile, String xPathString, String value) {
131         Node n = getNodeForXPath(xmlFile, xPathString);
132         if ( n==null ) {
133             CLog.d("Failed to find node for xpath %s", xPathString);
134             return false;
135         }
136         CLog.d("Value of node %s: %s", xPathString, n.getNodeValue());
137         return n.getNodeValue().toLowerCase().startsWith(value.toLowerCase());
138     }
139 
140     /**
141      * Expect that the value of a given xpath matches.
142      *
143      * @param xmlFile {@link File} the xml file in question
144      * @param xPathString {@link String} the xpath to look for
145      * @param value {@link String} the expected string value
146      *
147      * @return true if the value for the xpath matches, false otherwise
148      */
expectEquals(File xmlFile, String xPathString, String value)149     public boolean expectEquals(File xmlFile, String xPathString, String value) {
150         Node n = getNodeForXPath(xmlFile, xPathString);
151         if ( n==null ) {
152             CLog.d("Failed to find node for xpath %s", xPathString);
153             return false;
154         }
155         boolean result = n.getNodeValue().equalsIgnoreCase(value);
156         if (!result) {
157             CLog.v("Value of node %s: \"%s\", expected: \"%s\"",
158                     xPathString, n.getNodeValue(), value);
159         }
160         return result;
161     }
162 
getNodeForXPath(File xmlFile, String xPathString)163     public Node getNodeForXPath(File xmlFile, String xPathString) {
164         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
165         try {
166             DocumentBuilder documentBuilder = factory.newDocumentBuilder();
167             Document doc = documentBuilder.parse(xmlFile);
168             XPathFactory xpFactory = XPathFactory.newInstance();
169             XPath xpath = xpFactory.newXPath();
170             XPathExpression expr = xpath.compile(xPathString);
171             Node node = (Node) expr.evaluate(doc, XPathConstants.NODE);
172             return node;
173         } catch (Exception e) {
174             CLog.e(e);
175         }
176         return null;
177     }
178 
179     /**
180      * Check if a given package has the said permission.
181      * @param packageName {@link String} the package in question
182      * @param permission {@link String} the permission to look for
183      *
184      * @return true if the permission exists, false otherwise
185      *
186      * @throws DeviceNotAvailableException
187      */
packageHasPermission(String packageName, String permission)188     public boolean packageHasPermission(String packageName, String permission)
189             throws DeviceNotAvailableException {
190         String cmd = "dumpsys package " + packageName;
191         String res = mDevice.executeShellCommand(cmd);
192         if (res != null) {
193             if (res.contains("grantedPermissions:")) {
194                 return res.contains(permission);
195             } else {
196                 Pattern perm = Pattern.compile(String.format("^.*%s.*granted=true.*$", permission),
197                         Pattern.MULTILINE);
198                 Matcher m = perm.matcher(res);
199                 return m.find();
200             }
201         }
202         CLog.d("Failed to execute shell command: %s", cmd);
203         return false;
204     }
205 
206     /**
207      * Check if a given package has the said permission.
208      * @param packageName {@link String} the package in question
209      * @param flag {@link String} the permission to look for
210      *
211      * @return true if the permission exists, false otherwise
212      *
213      * @throws DeviceNotAvailableException
214      */
packageHasFlag(String packageName, String flag)215     public boolean packageHasFlag(String packageName, String flag)
216             throws DeviceNotAvailableException {
217         String cmd = "dumpsys package " + packageName;
218         String res = mDevice.executeShellCommand(cmd);
219         if (res != null) {
220             Pattern flags = Pattern.compile("^.*flags=\\[(.*?)\\]$", Pattern.MULTILINE);
221             Matcher m = flags.matcher(res);
222             if (m.find()) {
223                 return m.group(1).contains(flag);
224             } else {
225                 CLog.d("Failed to find package flags record in dumpsys package output");
226             }
227         }
228         CLog.d("Failed to execute shell command: %s", cmd);
229         return false;
230     }
231 
232     /**
233      * Helper method to install a file
234      *
235      * @param localFile the {@link File} to install
236      * @param replace set to <code>true</code> if re-install of app should be performed
237      * @param extraArgs optional extra arguments to pass. See 'adb shell pm install --help' for
238      *            available options.
239      *
240      * @throws DeviceNotAvailableException
241      */
installFile(final File localFile, final boolean replace, String... extraArgs)242     public void installFile(final File localFile, final boolean replace, String... extraArgs)
243             throws DeviceNotAvailableException {
244         String result = mDevice.installPackage(localFile, replace, extraArgs);
245         Assert.assertNull(String.format("Failed to install file %s with result %s",
246                 localFile.getAbsolutePath(), result), result);
247     }
248 
249     /**
250      * Helper method to stop system shell.
251      *
252      * @throws DeviceNotAvailableException
253      */
stopSystem()254     public void stopSystem() throws DeviceNotAvailableException {
255         mDevice.executeShellCommand("stop");
256     }
257 
258     /**
259      * Helper method to start system shell. It also reset the flag dev.bootcomplete to 0 to ensure
260      * that the package manager had a chance to finish.
261      *
262      * @throws DeviceNotAvailableException
263      */
startSystem()264     public void startSystem() throws DeviceNotAvailableException {
265         mDevice.executeShellCommand("setprop dev.bootcomplete 0");
266         mDevice.executeShellCommand("start");
267         mDevice.waitForDeviceAvailable();
268     }
269 
270     /**
271      * Convenience method to stop, then start the runtime.
272      *
273      * @throws DeviceNotAvailableException
274      */
restartSystem()275     public void restartSystem() throws DeviceNotAvailableException {
276         stopSystem();
277         startSystem();
278     }
279 
280     /**
281      * Push apk to system app directory on the device.
282      *
283      * @param localFile {@link File} the local file to install
284      * @param deviceFilePath {@link String} the remote device path where to install the application
285      *
286      * @throws DeviceNotAvailableException
287      */
pushSystemApp(final File localFile, final String deviceFilePath)288     public void pushSystemApp(final File localFile, final String deviceFilePath)
289             throws DeviceNotAvailableException {
290         mDevice.remountSystemWritable();
291         stopSystem();
292         mDevice.pushFile(localFile, deviceFilePath);
293         startSystem();
294     }
295 
296     /**
297      * Pulls packages xml file from the device.
298      * @return {@link File} xml file for all packages on device.
299      *
300      * @throws DeviceNotAvailableException
301      */
pullPackagesXML()302     public File pullPackagesXML() throws DeviceNotAvailableException {
303         return mDevice.pullFile(PACKAGE_XML_FILE);
304     }
305 }
306