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 android.app.Activity;
     20 import android.app.UiAutomation;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.UriPermission;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.ResolveInfo;
     27 import android.media.ExifInterface;
     28 import android.media.MediaScannerConnection;
     29 import android.net.Uri;
     30 import android.os.Environment;
     31 import android.os.ParcelFileDescriptor;
     32 import android.os.SystemClock;
     33 import android.os.storage.StorageManager;
     34 import android.os.storage.StorageVolume;
     35 import android.provider.MediaStore;
     36 import android.provider.cts.GetResultActivity.Result;
     37 import android.support.test.InstrumentationRegistry;
     38 import android.support.test.uiautomator.By;
     39 import android.support.test.uiautomator.BySelector;
     40 import android.support.test.uiautomator.UiDevice;
     41 import android.support.test.uiautomator.UiObject2;
     42 import android.support.test.uiautomator.UiSelector;
     43 import android.support.test.uiautomator.Until;
     44 import androidx.core.content.FileProvider;
     45 import android.test.InstrumentationTestCase;
     46 import android.text.format.DateUtils;
     47 import android.util.Log;
     48 import android.view.KeyEvent;
     49 
     50 import java.io.BufferedReader;
     51 import java.io.File;
     52 import java.io.FileInputStream;
     53 import java.io.FileOutputStream;
     54 import java.io.FileReader;
     55 import java.io.OutputStream;
     56 import java.util.concurrent.CountDownLatch;
     57 import java.util.concurrent.TimeUnit;
     58 
     59 public class MediaStoreUiTest extends InstrumentationTestCase {
     60     private static final String TAG = "MediaStoreUiTest";
     61 
     62     private static final int REQUEST_CODE = 42;
     63     private static final String CONTENT = "Test";
     64 
     65     private UiDevice mDevice;
     66     private GetResultActivity mActivity;
     67 
     68     private File mFile;
     69     private Uri mMediaStoreUri;
     70 
     71     @Override
     72     public void setUp() throws Exception {
     73         mDevice = UiDevice.getInstance(getInstrumentation());
     74 
     75         final Context context = getInstrumentation().getContext();
     76         mActivity = launchActivity(context.getPackageName(), GetResultActivity.class, null);
     77         mActivity.clearResult();
     78     }
     79 
     80     @Override
     81     public void tearDown() throws Exception {
     82         if (mFile != null) {
     83             mFile.delete();
     84         }
     85 
     86         final ContentResolver resolver = mActivity.getContentResolver();
     87         for (UriPermission permission : resolver.getPersistedUriPermissions()) {
     88             mActivity.revokeUriPermission(
     89                     permission.getUri(),
     90                     Intent.FLAG_GRANT_READ_URI_PERMISSION
     91                         | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
     92         }
     93 
     94         mActivity.finish();
     95     }
     96 
     97     public void testGetDocumentUri() throws Exception {
     98         if (!supportsHardware()) return;
     99 
    100         prepareFile();
    101 
    102         final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
    103         assertNotNull(treeUri);
    104 
    105         final Uri docUri = MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
    106         assertNotNull(docUri);
    107 
    108         final ContentResolver resolver = mActivity.getContentResolver();
    109         try (ParcelFileDescriptor fd = resolver.openFileDescriptor(docUri, "rw")) {
    110             // Test reading
    111             try (final BufferedReader reader =
    112                          new BufferedReader(new FileReader(fd.getFileDescriptor()))) {
    113                 assertEquals(CONTENT, reader.readLine());
    114             }
    115 
    116             // Test writing
    117             try (final OutputStream out = new FileOutputStream(fd.getFileDescriptor())) {
    118                 out.write(CONTENT.getBytes());
    119             }
    120         }
    121     }
    122 
    123     public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception {
    124         if (!supportsHardware()) return;
    125 
    126         prepareFile();
    127 
    128         try {
    129             MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
    130             fail("Expecting SecurityException.");
    131         } catch (SecurityException e) {
    132             // Expected
    133         }
    134     }
    135 
    136     private void maybeClick(UiSelector sel) {
    137         try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
    138     }
    139 
    140     private void maybeClick(BySelector sel) {
    141         try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
    142     }
    143 
    144     /**
    145      * Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
    146      * correctly write the contents into a passed {@code content://} Uri.
    147      */
    148     public void testImageCapture() throws Exception {
    149         final Context context = getInstrumentation().getContext();
    150         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
    151             Log.d(TAG, "Skipping due to lack of camera");
    152             return;
    153         }
    154 
    155         final File targetDir = new File(context.getFilesDir(), "debug");
    156         final File target = new File(targetDir, "capture.jpg");
    157 
    158         targetDir.mkdirs();
    159         assertFalse(target.exists());
    160 
    161         final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    162         intent.putExtra(MediaStore.EXTRA_OUTPUT,
    163                 FileProvider.getUriForFile(context, "android.provider.cts.fileprovider", target));
    164 
    165         // Figure out who is going to answer the phone
    166         final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
    167         final String pkg = ri.activityInfo.packageName;
    168         Log.d(TAG, "We're probably launching " + ri);
    169 
    170         // Grant them all the permissions they might want
    171         final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
    172         ui.grantRuntimePermission(pkg, android.Manifest.permission.CAMERA);
    173         ui.grantRuntimePermission(pkg, android.Manifest.permission.ACCESS_COARSE_LOCATION);
    174         ui.grantRuntimePermission(pkg, android.Manifest.permission.ACCESS_FINE_LOCATION);
    175         ui.grantRuntimePermission(pkg, android.Manifest.permission.RECORD_AUDIO);
    176         ui.grantRuntimePermission(pkg, android.Manifest.permission.READ_EXTERNAL_STORAGE);
    177         ui.grantRuntimePermission(pkg, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    178         SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
    179 
    180         mActivity.startActivityForResult(intent, REQUEST_CODE);
    181         mDevice.waitForIdle();
    182 
    183         // To ensure camera app is launched
    184         SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
    185 
    186         // Try a couple different strategies for taking a photo: first take a
    187         // photo and confirm using hardware keys
    188         mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
    189         mDevice.waitForIdle();
    190         SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
    191         mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
    192         mDevice.waitForIdle();
    193 
    194         // Maybe that gave us a result?
    195         Result result = mActivity.getResult(15, TimeUnit.SECONDS);
    196         Log.d(TAG, "First pass result was " + result);
    197 
    198         // Hrm, that didn't work; let's try an alternative approach of digging
    199         // around for a shutter button
    200         if (result == null) {
    201             maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
    202             mDevice.waitForIdle();
    203             SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
    204             maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
    205             mDevice.waitForIdle();
    206             maybeClick(new UiSelector().resourceId(pkg + ":id/done_button"));
    207             mDevice.waitForIdle();
    208 
    209             result = mActivity.getResult(15, TimeUnit.SECONDS);
    210             Log.d(TAG, "Second pass result was " + result);
    211         }
    212 
    213         // Grr, let's try hunting around even more
    214         if (result == null) {
    215             maybeClick(By.pkg(pkg).descContains("Capture"));
    216             mDevice.waitForIdle();
    217             SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
    218             maybeClick(By.pkg(pkg).descContains("Done"));
    219             mDevice.waitForIdle();
    220 
    221             result = mActivity.getResult(15, TimeUnit.SECONDS);
    222             Log.d(TAG, "Third pass result was " + result);
    223         }
    224 
    225         assertNotNull("Expected to get a IMAGE_CAPTURE result; your camera app should "
    226                 + "respond to the CAMERA and DPAD_CENTER keycodes", result);
    227 
    228         assertTrue("exists", target.exists());
    229         assertTrue("has data", target.length() > 65536);
    230 
    231         // At the very least we expect photos generated by the device to have
    232         // sane baseline EXIF data
    233         final ExifInterface exif = new ExifInterface(new FileInputStream(target));
    234         assertAttribute(exif, ExifInterface.TAG_MAKE);
    235         assertAttribute(exif, ExifInterface.TAG_MODEL);
    236         assertAttribute(exif, ExifInterface.TAG_DATETIME);
    237     }
    238 
    239     private static void assertAttribute(ExifInterface exif, String tag) {
    240         final String res = exif.getAttribute(tag);
    241         if (res == null || res.length() == 0) {
    242             Log.d(TAG, "Expected valid EXIF tag for tag " + tag);
    243         }
    244     }
    245 
    246     private boolean supportsHardware() {
    247         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
    248         return !pm.hasSystemFeature("android.hardware.type.television")
    249                 && !pm.hasSystemFeature("android.hardware.type.watch");
    250     }
    251 
    252     private void prepareFile() throws Exception {
    253         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
    254 
    255         final File documents =
    256                 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
    257         documents.mkdirs();
    258         assertTrue(documents.isDirectory());
    259 
    260         mFile = new File(documents, "test.txt");
    261         try (OutputStream os = new FileOutputStream(mFile)) {
    262             os.write(CONTENT.getBytes());
    263         }
    264 
    265         final CountDownLatch latch = new CountDownLatch(1);
    266         MediaScannerConnection.scanFile(
    267                 mActivity,
    268                 new String[]{ mFile.getAbsolutePath() },
    269                 new String[]{ "plain/text" },
    270                 (String path, Uri uri) -> onScanCompleted(uri, latch)
    271         );
    272         assertTrue(
    273                 "MediaScanner didn't finish scanning in 30s.", latch.await(30, TimeUnit.SECONDS));
    274     }
    275 
    276     private void onScanCompleted(Uri uri, CountDownLatch latch) {
    277         mMediaStoreUri = uri;
    278         latch.countDown();
    279     }
    280 
    281     private Uri acquireAccess(File file, String directoryName) {
    282         StorageManager storageManager =
    283                 (StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
    284 
    285         // Request access from DocumentsUI
    286         final StorageVolume volume = storageManager.getStorageVolume(file);
    287         final Intent intent = volume.createAccessIntent(directoryName);
    288         mActivity.startActivityForResult(intent, REQUEST_CODE);
    289 
    290         // Granting the access
    291         BySelector buttonPanelSelector = By.pkg("com.android.documentsui")
    292                 .res("android:id/buttonPanel");
    293         mDevice.wait(Until.hasObject(buttonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
    294         final UiObject2 buttonPanel = mDevice.findObject(buttonPanelSelector);
    295         final UiObject2 allowButton = buttonPanel.findObject(By.res("android:id/button1"));
    296         allowButton.click();
    297 
    298         mDevice.waitForIdle();
    299 
    300         // Check granting result and take persistent permission
    301         final Result result = mActivity.getResult();
    302         assertEquals(Activity.RESULT_OK, result.resultCode);
    303 
    304         final Intent resultIntent = result.data;
    305         final Uri resultUri = resultIntent.getData();
    306         final int flags = resultIntent.getFlags()
    307                 & (Intent.FLAG_GRANT_READ_URI_PERMISSION
    308                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    309         mActivity.getContentResolver().takePersistableUriPermission(resultUri, flags);
    310         return resultUri;
    311     }
    312 }
    313