• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.provider.cts;
18 
19 import static android.provider.cts.MediaStoreTest.TAG;
20 
21 import static org.junit.Assert.fail;
22 
23 import android.app.UiAutomation;
24 import android.content.Context;
25 import android.content.res.AssetFileDescriptor;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Environment;
29 import android.os.FileUtils;
30 import android.os.ParcelFileDescriptor;
31 import android.provider.MediaStore;
32 import android.provider.MediaStore.MediaColumns;
33 import android.provider.cts.MediaStoreUtils.PendingParams;
34 import android.provider.cts.MediaStoreUtils.PendingSession;
35 import android.system.ErrnoException;
36 import android.system.Os;
37 import android.system.OsConstants;
38 import android.util.Log;
39 
40 import androidx.test.InstrumentationRegistry;
41 
42 import java.io.BufferedReader;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.FileNotFoundException;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.OutputStream;
51 import java.nio.charset.StandardCharsets;
52 import java.security.DigestInputStream;
53 import java.security.MessageDigest;
54 import java.util.HashSet;
55 import java.util.Objects;
56 import java.util.regex.Matcher;
57 import java.util.regex.Pattern;
58 
59 /**
60  * Utility methods for provider cts tests.
61  */
62 public class ProviderTestUtils {
63 
64     private static final int BACKUP_TIMEOUT_MILLIS = 4000;
65     private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile(
66             "^Backup Manager currently (enabled|disabled)$");
67 
68     private static final Pattern PATTERN_STORAGE_PATH = Pattern.compile(
69             "(?i)^/storage/[^/]+/(?:[0-9]+/)?");
70 
getSharedVolumeNames()71     static Iterable<String> getSharedVolumeNames() {
72         // We test both new and legacy volume names
73         final HashSet<String> testVolumes = new HashSet<>();
74         testVolumes.addAll(
75                 MediaStore.getExternalVolumeNames(InstrumentationRegistry.getTargetContext()));
76         testVolumes.add(MediaStore.VOLUME_EXTERNAL);
77         return testVolumes;
78     }
79 
resolveVolumeName(String volumeName)80     static String resolveVolumeName(String volumeName) {
81         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
82             return MediaStore.VOLUME_EXTERNAL_PRIMARY;
83         } else {
84             return volumeName;
85         }
86     }
87 
setDefaultSmsApp(boolean setToSmsApp, String packageName, UiAutomation uiAutomation)88     static void setDefaultSmsApp(boolean setToSmsApp, String packageName, UiAutomation uiAutomation)
89             throws Exception {
90         String mode = setToSmsApp ? "allow" : "default";
91         String cmd = "appops set %s %s %s";
92         executeShellCommand(String.format(cmd, packageName, "WRITE_SMS", mode), uiAutomation);
93         executeShellCommand(String.format(cmd, packageName, "READ_SMS", mode), uiAutomation);
94     }
95 
executeShellCommand(String command)96     static String executeShellCommand(String command) throws IOException {
97         return executeShellCommand(command,
98                 InstrumentationRegistry.getInstrumentation().getUiAutomation());
99     }
100 
executeShellCommand(String command, UiAutomation uiAutomation)101     static String executeShellCommand(String command, UiAutomation uiAutomation)
102             throws IOException {
103         Log.v(TAG, "$ " + command);
104         ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
105         BufferedReader br = null;
106         try (InputStream in = new FileInputStream(pfd.getFileDescriptor());) {
107             br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
108             String str = null;
109             StringBuilder out = new StringBuilder();
110             while ((str = br.readLine()) != null) {
111                 Log.v(TAG, "> " + str);
112                 out.append(str);
113             }
114             return out.toString();
115         } finally {
116             if (br != null) {
117                 br.close();
118             }
119         }
120     }
121 
setBackupTransport(String transport, UiAutomation uiAutomation)122     static String setBackupTransport(String transport, UiAutomation uiAutomation) throws Exception {
123         String output = executeShellCommand("bmgr transport " + transport, uiAutomation);
124         Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
125         Matcher matcher = pattern.matcher(output);
126         if (matcher.find()) {
127             return matcher.group(1);
128         } else {
129             throw new Exception("non-parsable output setting bmgr transport: " + output);
130         }
131     }
132 
setBackupEnabled(boolean enable, UiAutomation uiAutomation)133     static boolean setBackupEnabled(boolean enable, UiAutomation uiAutomation) throws Exception {
134         // Check to see the previous state of the backup service
135         boolean previouslyEnabled = false;
136         String output = executeShellCommand("bmgr enabled", uiAutomation);
137         Matcher matcher = BMGR_ENABLED_PATTERN.matcher(output.trim());
138         if (matcher.find()) {
139             previouslyEnabled = "enabled".equals(matcher.group(1));
140         } else {
141             throw new RuntimeException("Backup output format changed.  No longer matches"
142                     + " expected regex: " + BMGR_ENABLED_PATTERN + "\nactual: '" + output + "'");
143         }
144 
145         executeShellCommand("bmgr enable " + enable, uiAutomation);
146         return previouslyEnabled;
147     }
148 
hasBackupTransport(String transport, UiAutomation uiAutomation)149     static boolean hasBackupTransport(String transport, UiAutomation uiAutomation)
150             throws Exception {
151         String output = executeShellCommand("bmgr list transports", uiAutomation);
152         for (String t : output.split(" ")) {
153             if ("*".equals(t)) {
154                 // skip the current selection marker.
155                 continue;
156             } else if (Objects.equals(transport, t)) {
157                 return true;
158             }
159         }
160         return false;
161     }
162 
runBackup(String packageName, UiAutomation uiAutomation)163     static void runBackup(String packageName, UiAutomation uiAutomation) throws Exception {
164         executeShellCommand("bmgr backupnow " + packageName, uiAutomation);
165         Thread.sleep(BACKUP_TIMEOUT_MILLIS);
166     }
167 
runRestore(String packageName, UiAutomation uiAutomation)168     static void runRestore(String packageName, UiAutomation uiAutomation) throws Exception {
169         executeShellCommand("bmgr restore 1 " + packageName, uiAutomation);
170         Thread.sleep(BACKUP_TIMEOUT_MILLIS);
171     }
172 
wipeBackup(String backupTransport, String packageName, UiAutomation uiAutomation)173     static void wipeBackup(String backupTransport, String packageName, UiAutomation uiAutomation)
174             throws Exception {
175         executeShellCommand("bmgr wipe " + backupTransport + " " + packageName, uiAutomation);
176     }
177 
stageDir(String volumeName)178     static File stageDir(String volumeName) throws IOException {
179         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
180             volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
181         }
182         return Environment.buildPath(MediaStore.getVolumePath(volumeName), "Android", "media",
183                 "android.provider.cts");
184     }
185 
stageDownloadDir(String volumeName)186     static File stageDownloadDir(String volumeName) throws IOException {
187         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
188             volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
189         }
190         return Environment.buildPath(MediaStore.getVolumePath(volumeName),
191                 Environment.DIRECTORY_DOWNLOADS, "android.provider.cts");
192     }
193 
stageFile(int resId, File file)194     static File stageFile(int resId, File file) throws IOException {
195         // The caller may be trying to stage into a location only available to
196         // the shell user, so we need to perform the entire copy as the shell
197         if (FileUtils.contains(Environment.getStorageDirectory(), file)) {
198             executeShellCommand("mkdir -p " + file.getParent());
199 
200             final Context context = InstrumentationRegistry.getTargetContext();
201             try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
202                 final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor());
203                 final long skip = afd.getStartOffset();
204                 final long count = afd.getLength();
205 
206                 executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s",
207                         source.getAbsolutePath(), skip, count, file.getAbsolutePath()));
208 
209                 // Force sync to try updating other views
210                 executeShellCommand("sync");
211             }
212         } else {
213             final File dir = file.getParentFile();
214             dir.mkdirs();
215             if (!dir.exists()) {
216                 throw new FileNotFoundException("Failed to create parent for " + file);
217             }
218             final Context context = InstrumentationRegistry.getTargetContext();
219             try (InputStream source = context.getResources().openRawResource(resId);
220                     OutputStream target = new FileOutputStream(file)) {
221                 FileUtils.copy(source, target);
222             }
223         }
224         return file;
225     }
226 
stageMedia(int resId, Uri collectionUri)227     static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
228         return stageMedia(resId, collectionUri, "image/png");
229     }
230 
stageMedia(int resId, Uri collectionUri, String mimeType)231     static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
232         final Context context = InstrumentationRegistry.getTargetContext();
233         final String displayName = "cts" + System.nanoTime();
234         final PendingParams params = new PendingParams(collectionUri, displayName, mimeType);
235         final Uri pendingUri = MediaStoreUtils.createPending(context, params);
236         try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
237             try (InputStream source = context.getResources().openRawResource(resId);
238                     OutputStream target = session.openOutputStream()) {
239                 FileUtils.copy(source, target);
240             }
241             return session.publish();
242         }
243     }
244 
scanFile(File file)245     static Uri scanFile(File file) throws Exception {
246         return MediaStore.scanFile(InstrumentationRegistry.getTargetContext(), file);
247     }
248 
scanFileFromShell(File file)249     static Uri scanFileFromShell(File file) throws Exception {
250         return MediaStore.scanFileFromShell(InstrumentationRegistry.getTargetContext(), file);
251     }
252 
scanVolume(File file)253     static void scanVolume(File file) throws Exception {
254         MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(), file);
255     }
256 
hash(InputStream in)257     public static byte[] hash(InputStream in) throws Exception {
258         try (DigestInputStream digestIn = new DigestInputStream(in,
259                 MessageDigest.getInstance("SHA-1"));
260                 OutputStream out = new FileOutputStream(new File("/dev/null"))) {
261             FileUtils.copy(digestIn, out);
262             return digestIn.getMessageDigest().digest();
263         }
264     }
265 
assertExists(String path)266     public static void assertExists(String path) throws IOException {
267         assertExists(null, path);
268     }
269 
assertExists(File file)270     public static void assertExists(File file) throws IOException {
271         assertExists(null, file.getAbsolutePath());
272     }
273 
assertExists(String msg, String path)274     public static void assertExists(String msg, String path) throws IOException {
275         if (!access(path)) {
276             fail(msg);
277         }
278     }
279 
assertNotExists(String path)280     public static void assertNotExists(String path) throws IOException {
281         assertNotExists(null, path);
282     }
283 
assertNotExists(File file)284     public static void assertNotExists(File file) throws IOException {
285         assertNotExists(null, file.getAbsolutePath());
286     }
287 
assertNotExists(String msg, String path)288     public static void assertNotExists(String msg, String path) throws IOException {
289         if (access(path)) {
290             fail(msg);
291         }
292     }
293 
access(String path)294     private static boolean access(String path) throws IOException {
295         // The caller may be trying to stage into a location only available to
296         // the shell user, so we need to perform the entire copy as the shell
297         if (FileUtils.contains(Environment.getStorageDirectory(), new File(path))) {
298             return executeShellCommand("ls -la " + path).contains(path);
299         } else {
300             try {
301                 Os.access(path, OsConstants.F_OK);
302                 return true;
303             } catch (ErrnoException e) {
304                 if (e.errno == OsConstants.ENOENT) {
305                     return false;
306                 } else {
307                     throw new IOException(e.getMessage());
308                 }
309             }
310         }
311     }
312 
containsId(Uri uri, long id)313     public static boolean containsId(Uri uri, long id) {
314         try (Cursor c = InstrumentationRegistry.getTargetContext().getContentResolver().query(uri,
315                 new String[] { MediaColumns._ID }, null, null)) {
316             while (c.moveToNext()) {
317                 if (c.getLong(0) == id) return true;
318             }
319         }
320         return false;
321     }
322 
getRawFile(Uri uri)323     public static File getRawFile(Uri uri) throws Exception {
324         final String res = ProviderTestUtils.executeShellCommand(
325                 "content query --uri " + uri + " --projection _data",
326                 InstrumentationRegistry.getInstrumentation().getUiAutomation());
327         final int i = res.indexOf("_data=");
328         if (i >= 0) {
329             return new File(res.substring(i + 6));
330         } else {
331             throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
332         }
333     }
334 
getRawFileHash(File file)335     public static String getRawFileHash(File file) throws Exception {
336         final String res = ProviderTestUtils.executeShellCommand(
337                 "sha1sum " + file.getAbsolutePath(),
338                 InstrumentationRegistry.getInstrumentation().getUiAutomation());
339         if (Pattern.matches("[0-9a-fA-F]{40}.+", res)) {
340             return res.substring(0, 40);
341         } else {
342             throw new FileNotFoundException("Failed to find hash for " + file + "; found " + res);
343         }
344     }
345 
getRelativeFile(Uri uri)346     public static File getRelativeFile(Uri uri) throws Exception {
347         final String path = getRawFile(uri).getAbsolutePath();
348         final Matcher matcher = PATTERN_STORAGE_PATH.matcher(path);
349         if (matcher.find()) {
350             return new File(path.substring(matcher.end()));
351         } else {
352             throw new IllegalArgumentException();
353         }
354     }
355 }
356