Home | History | Annotate | Download | only in cts
      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 
     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 
     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 
     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 
     96     static String executeShellCommand(String command) throws IOException {
     97         return executeShellCommand(command,
     98                 InstrumentationRegistry.getInstrumentation().getUiAutomation());
     99     }
    100 
    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 
    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 
    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 
    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 
    163     static void runBackup(String packageName, UiAutomation uiAutomation) throws Exception {
    164         executeShellCommand("bmgr backupnow " + packageName, uiAutomation);
    165         Thread.sleep(BACKUP_TIMEOUT_MILLIS);
    166     }
    167 
    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 
    173     static void wipeBackup(String backupTransport, String packageName, UiAutomation uiAutomation)
    174             throws Exception {
    175         executeShellCommand("bmgr wipe " + backupTransport + " " + packageName, uiAutomation);
    176     }
    177 
    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 
    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 
    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 
    227     static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
    228         return stageMedia(resId, collectionUri, "image/png");
    229     }
    230 
    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 
    245     static Uri scanFile(File file) throws Exception {
    246         return MediaStore.scanFile(InstrumentationRegistry.getTargetContext(), file);
    247     }
    248 
    249     static Uri scanFileFromShell(File file) throws Exception {
    250         return MediaStore.scanFileFromShell(InstrumentationRegistry.getTargetContext(), file);
    251     }
    252 
    253     static void scanVolume(File file) throws Exception {
    254         MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(), file);
    255     }
    256 
    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 
    266     public static void assertExists(String path) throws IOException {
    267         assertExists(null, path);
    268     }
    269 
    270     public static void assertExists(File file) throws IOException {
    271         assertExists(null, file.getAbsolutePath());
    272     }
    273 
    274     public static void assertExists(String msg, String path) throws IOException {
    275         if (!access(path)) {
    276             fail(msg);
    277         }
    278     }
    279 
    280     public static void assertNotExists(String path) throws IOException {
    281         assertNotExists(null, path);
    282     }
    283 
    284     public static void assertNotExists(File file) throws IOException {
    285         assertNotExists(null, file.getAbsolutePath());
    286     }
    287 
    288     public static void assertNotExists(String msg, String path) throws IOException {
    289         if (access(path)) {
    290             fail(msg);
    291         }
    292     }
    293 
    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 
    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 
    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 
    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 
    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