• 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 com.android.cts.documentclient;
18 
19 import static android.os.Environment.DIRECTORY_ALARMS;
20 import static android.os.Environment.DIRECTORY_DCIM;
21 import static android.os.Environment.DIRECTORY_DOCUMENTS;
22 import static android.os.Environment.DIRECTORY_DOWNLOADS;
23 import static android.os.Environment.DIRECTORY_MOVIES;
24 import static android.os.Environment.DIRECTORY_MUSIC;
25 import static android.os.Environment.DIRECTORY_NOTIFICATIONS;
26 import static android.os.Environment.DIRECTORY_PICTURES;
27 import static android.os.Environment.DIRECTORY_PODCASTS;
28 import static android.os.Environment.DIRECTORY_RINGTONES;
29 import static android.test.MoreAsserts.assertContainsRegex;
30 import static android.test.MoreAsserts.assertNotContainsRegex;
31 import static android.test.MoreAsserts.assertNotEqual;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.net.Uri;
36 import android.os.storage.StorageManager;
37 import android.os.storage.StorageVolume;
38 import android.provider.DocumentsContract;
39 import android.provider.DocumentsContract.Document;
40 import android.support.test.uiautomator.By;
41 import android.support.test.uiautomator.UiObject;
42 import android.support.test.uiautomator.UiObjectNotFoundException;
43 import android.support.test.uiautomator.UiSelector;
44 import android.support.test.uiautomator.Until;
45 import android.util.Log;
46 
47 import java.util.List;
48 
49 /**
50  * Set of tests that verify behavior of the Scoped Directory Access API.
51  */
52 public class ScopedDirectoryAccessClientTest extends DocumentsClientTestCase {
53     private static final String TAG = "ScopedDirectoryAccessClientTest";
54 
55     private static final String DIRECTORY_ROOT = null;
56 
57     private static final String[] STANDARD_DIRECTORIES = {
58         DIRECTORY_MUSIC,
59         DIRECTORY_PODCASTS,
60         DIRECTORY_RINGTONES,
61         DIRECTORY_ALARMS,
62         DIRECTORY_NOTIFICATIONS,
63         DIRECTORY_PICTURES,
64         DIRECTORY_MOVIES,
65         DIRECTORY_DOWNLOADS,
66         DIRECTORY_DCIM,
67         DIRECTORY_DOCUMENTS
68     };
69 
70     @Override
setUp()71     public void setUp() throws Exception {
72         super.setUp();
73 
74         // DocumentsUI caches some info like whether a user rejects a request, so we need to clear
75         // its data before each test.
76         clearDocumentsUi();
77     }
78 
testInvalidPath()79     public void testInvalidPath() throws Exception {
80         if (!supportedHardware()) return;
81 
82         for (StorageVolume volume : getVolumes()) {
83             openExternalDirectoryInvalidPath(volume, "");
84             openExternalDirectoryInvalidPath(volume, "/dev/null");
85             openExternalDirectoryInvalidPath(volume, "/../");
86             openExternalDirectoryInvalidPath(volume, "/HiddenStuff");
87         }
88         openExternalDirectoryInvalidPath(getPrimaryVolume(), DIRECTORY_ROOT);
89     }
90 
testUserRejects()91     public void testUserRejects() throws Exception {
92         if (!supportedHardware()) return;
93 
94         for (StorageVolume volume : getVolumes()) {
95             // Tests user clicking DENY button, for all valid directories.
96             for (String dir : STANDARD_DIRECTORIES) {
97                 userRejectsTest(volume, dir);
98             }
99             if (!volume.isPrimary()) {
100                 // Also test root
101                 userRejectsTest(volume, DIRECTORY_ROOT);
102             }
103             // Also test user clicking back button - one directory is enough.
104             openExternalDirectoryValidPath(volume, DIRECTORY_PICTURES);
105             mDevice.pressBack();
106             assertActivityFailed();
107         }
108     }
109 
userRejectsTest(StorageVolume volume, String dir)110     private void userRejectsTest(StorageVolume volume, String dir) throws Exception {
111         final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
112         dialog.noButton.click();
113         assertActivityFailed();
114     }
115 
testUserAccepts()116     public void testUserAccepts() throws Exception {
117         if (!supportedHardware()) return;
118 
119         for (StorageVolume volume : getVolumes()) {
120             userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
121             if (!volume.isPrimary()) {
122                 userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
123             }
124         }
125     }
126 
testUserAcceptsNewDirectory()127     public void testUserAcceptsNewDirectory() throws Exception {
128         if (!supportedHardware()) return;
129 
130         // TODO: figure out a better way to remove the directory.
131         final String command = "rm -rf /sdcard/" + DIRECTORY_PICTURES;
132         final String output = executeShellCommand(command);
133         if (!output.isEmpty()) {
134             fail("Command '" + command + "' failed: '" + output + "'");
135         }
136         userAcceptsOpenExternalDirectoryTest(getPrimaryVolume(), DIRECTORY_PICTURES);
137     }
138 
testNotAskedAgain()139     public void testNotAskedAgain() throws Exception {
140         if (!supportedHardware()) return;
141 
142         for (StorageVolume volume : getVolumes()) {
143             final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
144             final Uri grantedUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
145 
146             // Calls it again - since the permission has been granted, it should return right
147             // away, without popping up the permissions dialog.
148             sendOpenExternalDirectoryIntent(volume, DIRECTORY_PICTURES);
149             final Intent newData = assertActivitySucceeded("should have already granted "
150                     + "permission to " + volumeDesc + " and " + DIRECTORY_PICTURES);
151             assertEquals(grantedUri, newData.getData());
152 
153             // Make sure other directories still require user permission.
154             final Uri grantedUri2 = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ALARMS);
155             assertNotEqual(grantedUri, grantedUri2);
156         }
157     }
158 
testNotAskedAgainOnRoot()159     public void testNotAskedAgainOnRoot() throws Exception {
160         if (!supportedHardware()) return;
161 
162         for (StorageVolume volume : getVolumes()) {
163             if (volume.isPrimary()) continue;
164             final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
165             final Uri grantedRootUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
166 
167             // Calls it again - since the permission has been granted, it should return right
168             // away, without popping up the permissions dialog.
169             sendOpenExternalDirectoryIntent(volume, DIRECTORY_ROOT);
170             final Intent rootData = assertActivitySucceeded("should have already granted "
171                     + "permission to " + volumeDesc + " and root dir");
172             assertEquals(grantedRootUri, rootData.getData());
173 
174             // Make sure other directories don't permission neither.
175             for (String dir : STANDARD_DIRECTORIES) {
176                 sendOpenExternalDirectoryIntent(volume, dir);
177                 final Intent childData = assertActivitySucceeded("should have already granted "
178                         + "permission to " + volumeDesc + " and " + dir);
179                 assertNotNull(childData);
180                 final Uri grantedChildUri = childData.getData();
181                 assertFalse("received root URI (" + grantedRootUri + ") for child request",
182                         grantedRootUri.equals(grantedChildUri));
183             }
184         }
185     }
186 
testDeniesOnceButAllowsAskingAgain()187     public void testDeniesOnceButAllowsAskingAgain() throws Exception {
188         if (!supportedHardware())return;
189 
190         final String[] dirs = { DIRECTORY_DCIM, DIRECTORY_ROOT };
191         for (StorageVolume volume : getVolumes()) {
192             for (String dir : dirs) {
193                 if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
194                 // Rejects the first attempt...
195                 UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
196                 dialog.assertDoNotAskAgainVisibility(false);
197                 dialog.noButton.click();
198                 assertActivityFailed();
199 
200                 // ...and the second.
201                 dialog = openExternalDirectoryValidPath(volume, dir);
202                 dialog.assertDoNotAskAgainVisibility(true);
203                 dialog.noButton.click();
204                 assertActivityFailed();
205 
206                 // Third time is a charm...
207                 userAcceptsOpenExternalDirectoryTest(volume, dir);
208             }
209         }
210     }
211 
testDeniesOnceForAll()212     public void testDeniesOnceForAll() throws Exception {
213         if (!supportedHardware()) return;
214 
215         final String[] dirs = {DIRECTORY_PICTURES, DIRECTORY_ROOT};
216         for (StorageVolume volume : getVolumes()) {
217             for (String dir : dirs) {
218                 if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
219                 // Rejects the first attempt...
220                 UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
221                 dialog.assertDoNotAskAgainVisibility(false);
222                 dialog.noButton.click();
223                 assertActivityFailed();
224 
225                 // ...and the second, checking the box
226                 dialog = openExternalDirectoryValidPath(volume, dir);
227                 UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
228                 assertTrue("checkbox should not be checkable", checkbox.isCheckable());
229                 assertFalse("checkbox should not be checked", checkbox.isChecked());
230                 checkbox.click();
231                 assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
232                 assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
233 
234                 dialog.noButton.click();
235                 assertActivityFailed();
236 
237                 // Third strike out...
238                 sendOpenExternalDirectoryIntent(volume, dir);
239                 assertActivityFailed();
240             }
241         }
242     }
243 
userAcceptsOpenExternalDirectoryTest(StorageVolume volume, String directoryName)244     private Uri userAcceptsOpenExternalDirectoryTest(StorageVolume volume, String directoryName)
245             throws Exception {
246         // Asserts dialog contain the proper message.
247         final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, directoryName);
248         final String message = dialog.messageText.getText();
249         Log.v(TAG, "request permission message: " + message);
250         final Context context = getInstrumentation().getContext();
251         final String appLabel = context.getPackageManager().getApplicationLabel(
252                 context.getApplicationInfo()).toString();
253         assertContainsRegex("missing app label", appLabel, message);
254         final String volumeLabel = volume.getDescription(context);
255         if (volume.isPrimary()) {
256             assertNotContainsRegex("should not have volume label on primary", volumeLabel, message);
257         } else {
258             assertContainsRegex("missing volume label", volumeLabel, message);
259         }
260         if (directoryName != null) {
261             assertContainsRegex("missing folder", directoryName, message);
262         } else {
263             assertNotContainsRegex("should not have folder for root", "null", message);
264         }
265 
266         // Call API...
267         dialog.yesButton.click();
268 
269         // ...and get its response.
270         final String volumeDesc = volume.getDescription(context);
271         final Intent data = assertActivitySucceeded("should have already granted "
272                 + "permission to " + volumeDesc + " and " + directoryName);
273         final Uri grantedUri = data.getData();
274 
275         // Test granted permission directly by persisting it...
276         final ContentResolver resolver = context.getContentResolver();
277         final int modeFlags = data.getFlags()
278                 & (Intent.FLAG_GRANT_READ_URI_PERMISSION
279                 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
280         resolver.takePersistableUriPermission(grantedUri, modeFlags);
281 
282         // ...and indirectly by creating some documents
283         final Uri doc = DocumentsContract.buildDocumentUriUsingTree(grantedUri,
284                 DocumentsContract.getTreeDocumentId(grantedUri));
285         assertNotNull("could not get tree URI", doc);
286         final Uri pic = DocumentsContract.createDocument(resolver, doc, "image/png", "pic.png");
287         assertNotNull("could not create file (pic.png) on tree root", pic);
288         final Uri dir = DocumentsContract.createDocument(resolver, doc, Document.MIME_TYPE_DIR,
289                 "my dir");
290         assertNotNull("could not create child dir (my dir)", pic);
291         final Uri dirPic = DocumentsContract.createDocument(resolver, dir, "image/png", "pic2.png");
292         assertNotNull("could not create file (pic.png) on child dir (my dir)", dirPic);
293 
294         writeFully(pic, "pic".getBytes());
295         writeFully(dirPic, "dirPic".getBytes());
296 
297         // Clean up created documents.
298         assertTrue("delete", DocumentsContract.deleteDocument(resolver, pic));
299         assertTrue("delete", DocumentsContract.deleteDocument(resolver, dirPic));
300         assertTrue("delete", DocumentsContract.deleteDocument(resolver, dir));
301 
302         return grantedUri;
303     }
304 
openExternalDirectoryInvalidPath(StorageVolume volume, String directoryName)305     private void openExternalDirectoryInvalidPath(StorageVolume volume, String directoryName) {
306         final Intent intent = volume.createAccessIntent(directoryName);
307         assertNull("should not get intent for volume '" + volume + "' and directory '"
308                 + directoryName + "'", intent);
309     }
310 
openExternalDirectoryValidPath(StorageVolume volume, String path)311     private UiAlertDialog openExternalDirectoryValidPath(StorageVolume volume, String path)
312             throws UiObjectNotFoundException {
313         sendOpenExternalDirectoryIntent(volume, path);
314         return new UiAlertDialog(volume, path);
315     }
316 
sendOpenExternalDirectoryIntent(StorageVolume volume, String directoryName)317     private void sendOpenExternalDirectoryIntent(StorageVolume volume, String directoryName) {
318         final Intent intent = volume.createAccessIntent(directoryName);
319         assertNotNull("no intent for '" + volume + "' and directory " + directoryName, intent);
320         mActivity.startActivityForResult(intent, REQUEST_CODE);
321         mDevice.waitForIdle();
322     }
323 
getVolumes()324     private List<StorageVolume> getVolumes() {
325         final StorageManager sm = (StorageManager)
326                 getInstrumentation().getTargetContext().getSystemService(Context.STORAGE_SERVICE);
327         final List<StorageVolume> volumes = sm.getStorageVolumes();
328         assertTrue("empty volumes", !volumes.isEmpty());
329         return volumes;
330     }
331 
getPrimaryVolume()332     private StorageVolume getPrimaryVolume() {
333         final StorageManager sm = (StorageManager)
334                 getInstrumentation().getTargetContext().getSystemService(Context.STORAGE_SERVICE);
335         return sm.getPrimaryStorageVolume();
336     }
337 
338     private final class UiAlertDialog {
339         final UiObject dialog;
340         final UiObject messageText;
341         final UiObject yesButton;
342         final UiObject noButton;
343         final String volumeDesc;
344         final String directory;
345 
UiAlertDialog(StorageVolume volume, String path)346         UiAlertDialog(StorageVolume volume, String path) throws UiObjectNotFoundException {
347             volumeDesc = volume.getDescription(getInstrumentation().getContext());
348             directory = path;
349 
350             final String id = "android:id/parentPanel";
351             boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), TIMEOUT);
352             assertTrue("object with id '(" + id + "') not visible yet for "
353                     + volumeDesc + " and " + path, gotIt);
354             dialog = mDevice.findObject(new UiSelector().resourceId(id));
355             assertTrue("object with id '(" + id + "') doesn't exist", dialog.exists());
356             messageText = dialog.getChild(
357                     new UiSelector().resourceId("com.android.documentsui:id/message"));
358             yesButton = dialog.getChild(new UiSelector().resourceId("android:id/button1"));
359             noButton  = dialog.getChild(new UiSelector().resourceId("android:id/button2"));
360         }
361 
getDoNotAskAgainCheckBox()362         private UiObject getDoNotAskAgainCheckBox() throws UiObjectNotFoundException {
363             return dialog.getChild(
364                     new UiSelector().resourceId("com.android.documentsui:id/do_not_ask_checkbox"));
365         }
366 
assertDoNotAskAgainVisibility(boolean expectVisible)367         UiObject assertDoNotAskAgainVisibility(boolean expectVisible) {
368             UiObject checkbox = null;
369             try {
370                 checkbox = getDoNotAskAgainCheckBox();
371                 assertEquals("Wrong value for 'DoNotAskAgain.exists()",
372                         expectVisible, checkbox.exists());
373             } catch (UiObjectNotFoundException e) {
374                 if (expectVisible) {
375                     fail("'Do Not Ask Again' not found");
376                 }
377             }
378             return checkbox;
379         }
380     }
381 }
382