Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 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 android.content.ContentResolver; |
| 20 | import android.content.Intent; |
| 21 | import android.content.pm.PackageManager; |
| 22 | import android.database.Cursor; |
| 23 | import android.net.Uri; |
| 24 | import android.os.SystemClock; |
| 25 | import android.provider.DocumentsContract; |
| 26 | import android.provider.DocumentsContract.Document; |
| 27 | import android.provider.DocumentsProvider; |
| 28 | import android.support.test.uiautomator.UiDevice; |
| 29 | import android.support.test.uiautomator.UiObject; |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 30 | import android.support.test.uiautomator.UiObjectNotFoundException; |
| 31 | import android.support.test.uiautomator.UiScrollable; |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 32 | import android.support.test.uiautomator.UiSelector; |
| 33 | import android.test.InstrumentationTestCase; |
| 34 | import android.test.MoreAsserts; |
Jeff Sharkey | 5daa849 | 2014-10-21 10:53:06 -0700 | [diff] [blame] | 35 | import android.text.format.DateUtils; |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 36 | |
| 37 | import com.android.cts.documentclient.MyActivity.Result; |
| 38 | |
| 39 | import java.io.ByteArrayOutputStream; |
| 40 | import java.io.FileNotFoundException; |
| 41 | import java.io.IOException; |
| 42 | import java.io.InputStream; |
| 43 | import java.io.OutputStream; |
| 44 | |
| 45 | /** |
| 46 | * Tests for {@link DocumentsProvider} and interaction with platform intents |
| 47 | * like {@link Intent#ACTION_OPEN_DOCUMENT}. |
| 48 | */ |
| 49 | public class DocumentsClientTest extends InstrumentationTestCase { |
| 50 | private UiDevice mDevice; |
| 51 | private MyActivity mActivity; |
| 52 | |
Jeff Sharkey | 5daa849 | 2014-10-21 10:53:06 -0700 | [diff] [blame] | 53 | private static final long TIMEOUT = 10 * DateUtils.SECOND_IN_MILLIS; |
| 54 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 55 | @Override |
| 56 | public void setUp() throws Exception { |
| 57 | super.setUp(); |
| 58 | |
| 59 | mDevice = UiDevice.getInstance(getInstrumentation()); |
| 60 | mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(), |
| 61 | MyActivity.class, null); |
Jeff Sharkey | 69a1ee2 | 2014-10-15 16:52:29 -0700 | [diff] [blame] | 62 | mDevice.waitForIdle(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | @Override |
| 66 | public void tearDown() throws Exception { |
| 67 | super.tearDown(); |
| 68 | mActivity.finish(); |
| 69 | } |
| 70 | |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 71 | private UiObject findRoot(String label) throws UiObjectNotFoundException { |
| 72 | final UiSelector rootsList = new UiSelector().resourceId( |
| 73 | "com.android.documentsui:id/container_roots").childSelector( |
| 74 | new UiSelector().resourceId("android:id/list")); |
| 75 | |
| 76 | // Wait for the first list item to appear |
| 77 | assertTrue("First list item", |
| 78 | new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(TIMEOUT)); |
| 79 | |
| 80 | // Now scroll around to find our item |
| 81 | new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label)); |
| 82 | return new UiObject(rootsList.childSelector(new UiSelector().text(label))); |
| 83 | } |
| 84 | |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 85 | private UiObject findDocument(String label) throws UiObjectNotFoundException { |
| 86 | final UiSelector docList = new UiSelector().resourceId( |
| 87 | "com.android.documentsui:id/container_directory").childSelector( |
| 88 | new UiSelector().resourceId("com.android.documentsui:id/list")); |
| 89 | |
| 90 | // Wait for the first list item to appear |
| 91 | assertTrue("First list item", |
| 92 | new UiObject(docList.childSelector(new UiSelector())).waitForExists(TIMEOUT)); |
| 93 | |
| 94 | // Now scroll around to find our item |
| 95 | new UiScrollable(docList).scrollIntoView(new UiSelector().text(label)); |
| 96 | return new UiObject(docList.childSelector(new UiSelector().text(label))); |
| 97 | } |
| 98 | |
| 99 | private UiObject findSaveButton() throws UiObjectNotFoundException { |
| 100 | return new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save") |
| 101 | .childSelector(new UiSelector().resourceId("android:id/button1"))); |
| 102 | } |
| 103 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 104 | public void testOpenSimple() throws Exception { |
| 105 | if (!supportedHardware()) return; |
| 106 | |
| 107 | try { |
| 108 | // Opening without permission should fail |
| 109 | readFully(Uri.parse("content://com.android.cts.documentprovider/document/doc:file1")); |
| 110 | fail("Able to read data before opened!"); |
| 111 | } catch (SecurityException expected) { |
| 112 | } |
| 113 | |
| 114 | final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); |
| 115 | intent.addCategory(Intent.CATEGORY_OPENABLE); |
| 116 | intent.setType("*/*"); |
| 117 | mActivity.startActivityForResult(intent, 42); |
| 118 | |
| 119 | // Ensure that we see both of our roots |
Jeff Sharkey | 69a1ee2 | 2014-10-15 16:52:29 -0700 | [diff] [blame] | 120 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 121 | assertTrue("CtsLocal root", findRoot("CtsLocal").exists()); |
| 122 | assertTrue("CtsCreate root", findRoot("CtsCreate").exists()); |
| 123 | assertFalse("CtsGetContent root", findRoot("CtsGetContent").exists()); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 124 | |
| 125 | // Pick a specific file from our test provider |
| 126 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 127 | findRoot("CtsLocal").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 128 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 129 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 130 | findDocument("FILE1").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 131 | |
| 132 | final Result result = mActivity.getResult(); |
| 133 | final Uri uri = result.data.getData(); |
| 134 | |
| 135 | // We should now have permission to read/write |
| 136 | MoreAsserts.assertEquals("fileone".getBytes(), readFully(uri)); |
| 137 | |
| 138 | writeFully(uri, "replaced!".getBytes()); |
| 139 | SystemClock.sleep(500); |
| 140 | MoreAsserts.assertEquals("replaced!".getBytes(), readFully(uri)); |
| 141 | } |
| 142 | |
| 143 | public void testCreateNew() throws Exception { |
| 144 | if (!supportedHardware()) return; |
| 145 | |
| 146 | final String DISPLAY_NAME = "My New Awesome Document Title"; |
| 147 | final String MIME_TYPE = "image/png"; |
| 148 | |
| 149 | final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); |
| 150 | intent.addCategory(Intent.CATEGORY_OPENABLE); |
| 151 | intent.putExtra(Intent.EXTRA_TITLE, DISPLAY_NAME); |
| 152 | intent.setType(MIME_TYPE); |
| 153 | mActivity.startActivityForResult(intent, 42); |
| 154 | |
| 155 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 156 | findRoot("CtsCreate").click(); |
| 157 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 158 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 159 | findSaveButton().click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 160 | |
| 161 | final Result result = mActivity.getResult(); |
| 162 | final Uri uri = result.data.getData(); |
| 163 | |
| 164 | writeFully(uri, "meow!".getBytes()); |
| 165 | |
| 166 | assertEquals(DISPLAY_NAME, getColumn(uri, Document.COLUMN_DISPLAY_NAME)); |
| 167 | assertEquals(MIME_TYPE, getColumn(uri, Document.COLUMN_MIME_TYPE)); |
| 168 | } |
| 169 | |
| 170 | public void testCreateExisting() throws Exception { |
| 171 | if (!supportedHardware()) return; |
| 172 | |
| 173 | final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); |
| 174 | intent.addCategory(Intent.CATEGORY_OPENABLE); |
| 175 | intent.putExtra(Intent.EXTRA_TITLE, "NEVERUSED"); |
| 176 | intent.setType("mime2/file2"); |
| 177 | mActivity.startActivityForResult(intent, 42); |
| 178 | |
| 179 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 180 | findRoot("CtsCreate").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 181 | |
| 182 | // Pick file2, which should be selected since MIME matches, then try |
| 183 | // picking a non-matching MIME, which should leave file2 selected. |
| 184 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 185 | findDocument("FILE2").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 186 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 187 | findDocument("FILE1").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 188 | |
Jeff Sharkey | 69a1ee2 | 2014-10-15 16:52:29 -0700 | [diff] [blame] | 189 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 190 | findSaveButton().click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 191 | |
| 192 | final Result result = mActivity.getResult(); |
| 193 | final Uri uri = result.data.getData(); |
| 194 | |
| 195 | MoreAsserts.assertEquals("filetwo".getBytes(), readFully(uri)); |
| 196 | } |
| 197 | |
| 198 | public void testTree() throws Exception { |
| 199 | if (!supportedHardware()) return; |
| 200 | |
| 201 | final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); |
| 202 | mActivity.startActivityForResult(intent, 42); |
| 203 | |
| 204 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 205 | findRoot("CtsCreate").click(); |
| 206 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 207 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 208 | findDocument("DIR2").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 209 | mDevice.waitForIdle(); |
Jeff Sharkey | 4e9b7b5 | 2014-12-10 09:51:11 -0800 | [diff] [blame] | 210 | findSaveButton().click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 211 | |
| 212 | final Result result = mActivity.getResult(); |
| 213 | final Uri uri = result.data.getData(); |
| 214 | |
| 215 | // We should have selected DIR2 |
| 216 | Uri doc = DocumentsContract.buildDocumentUriUsingTree(uri, |
| 217 | DocumentsContract.getTreeDocumentId(uri)); |
| 218 | Uri children = DocumentsContract.buildChildDocumentsUriUsingTree(uri, |
| 219 | DocumentsContract.getTreeDocumentId(uri)); |
| 220 | |
| 221 | assertEquals("DIR2", getColumn(doc, Document.COLUMN_DISPLAY_NAME)); |
| 222 | |
| 223 | // Look around and make sure we can see children |
| 224 | final ContentResolver resolver = getInstrumentation().getContext().getContentResolver(); |
| 225 | Cursor cursor = resolver.query(children, new String[] { |
| 226 | Document.COLUMN_DISPLAY_NAME }, null, null, null); |
| 227 | try { |
| 228 | assertEquals(1, cursor.getCount()); |
| 229 | assertTrue(cursor.moveToFirst()); |
| 230 | assertEquals("FILE4", cursor.getString(0)); |
| 231 | } finally { |
| 232 | cursor.close(); |
| 233 | } |
| 234 | |
| 235 | // Create some documents |
| 236 | Uri pic = DocumentsContract.createDocument(resolver, doc, "image/png", "pic.png"); |
| 237 | Uri dir = DocumentsContract.createDocument(resolver, doc, Document.MIME_TYPE_DIR, "my dir"); |
| 238 | Uri dirPic = DocumentsContract.createDocument(resolver, dir, "image/png", "pic2.png"); |
| 239 | |
| 240 | writeFully(pic, "pic".getBytes()); |
| 241 | writeFully(dirPic, "dirPic".getBytes()); |
| 242 | |
| 243 | // Read then delete existing doc |
| 244 | final Uri file4 = DocumentsContract.buildDocumentUriUsingTree(uri, "doc:file4"); |
| 245 | MoreAsserts.assertEquals("filefour".getBytes(), readFully(file4)); |
| 246 | assertTrue("delete", DocumentsContract.deleteDocument(resolver, file4)); |
| 247 | try { |
| 248 | MoreAsserts.assertEquals("filefour".getBytes(), readFully(file4)); |
| 249 | fail("Expected file to be gone"); |
| 250 | } catch (FileNotFoundException expected) { |
| 251 | } |
| 252 | |
| 253 | // And rename something |
| 254 | dirPic = DocumentsContract.renameDocument(resolver, dirPic, "wow"); |
| 255 | assertNotNull("rename", dirPic); |
| 256 | |
| 257 | // We should only see single child |
| 258 | assertEquals("wow", getColumn(dirPic, Document.COLUMN_DISPLAY_NAME)); |
| 259 | MoreAsserts.assertEquals("dirPic".getBytes(), readFully(dirPic)); |
| 260 | |
| 261 | try { |
| 262 | // Make sure we can't see files outside selected dir |
| 263 | getColumn(DocumentsContract.buildDocumentUriUsingTree(uri, "doc:file1"), |
| 264 | Document.COLUMN_DISPLAY_NAME); |
| 265 | fail("Somehow read document outside tree!"); |
| 266 | } catch (SecurityException expected) { |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | public void testGetContent() throws Exception { |
| 271 | if (!supportedHardware()) return; |
| 272 | |
| 273 | final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
| 274 | intent.addCategory(Intent.CATEGORY_OPENABLE); |
| 275 | intent.setType("*/*"); |
| 276 | mActivity.startActivityForResult(intent, 42); |
| 277 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 278 | // Look around, we should be able to see both DocumentsProviders and |
| 279 | // other GET_CONTENT sources. |
Jeff Sharkey | 69a1ee2 | 2014-10-15 16:52:29 -0700 | [diff] [blame] | 280 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 281 | assertTrue("CtsLocal root", findRoot("CtsLocal").exists()); |
| 282 | assertTrue("CtsCreate root", findRoot("CtsCreate").exists()); |
| 283 | assertTrue("CtsGetContent root", findRoot("CtsGetContent").exists()); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 284 | |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 285 | mDevice.waitForIdle(); |
Jeff Sharkey | e837d82 | 2014-11-05 12:56:24 -0800 | [diff] [blame] | 286 | findRoot("CtsGetContent").click(); |
Jeff Sharkey | 3c4d2fc | 2014-10-10 11:40:22 -0700 | [diff] [blame] | 287 | |
| 288 | final Result result = mActivity.getResult(); |
| 289 | assertEquals("ReSuLt", result.data.getAction()); |
| 290 | } |
| 291 | |
| 292 | private String getColumn(Uri uri, String column) { |
| 293 | final ContentResolver resolver = getInstrumentation().getContext().getContentResolver(); |
| 294 | final Cursor cursor = resolver.query(uri, new String[] { column }, null, null, null); |
| 295 | try { |
| 296 | assertTrue(cursor.moveToFirst()); |
| 297 | return cursor.getString(0); |
| 298 | } finally { |
| 299 | cursor.close(); |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | private byte[] readFully(Uri uri) throws IOException { |
| 304 | InputStream in = getInstrumentation().getContext().getContentResolver() |
| 305 | .openInputStream(uri); |
| 306 | try { |
| 307 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| 308 | byte[] buffer = new byte[1024]; |
| 309 | int count; |
| 310 | while ((count = in.read(buffer)) != -1) { |
| 311 | bytes.write(buffer, 0, count); |
| 312 | } |
| 313 | return bytes.toByteArray(); |
| 314 | } finally { |
| 315 | in.close(); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | private void writeFully(Uri uri, byte[] data) throws IOException { |
| 320 | OutputStream out = getInstrumentation().getContext().getContentResolver() |
| 321 | .openOutputStream(uri); |
| 322 | try { |
| 323 | out.write(data); |
| 324 | } finally { |
| 325 | out.close(); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | private boolean supportedHardware() { |
| 330 | final PackageManager pm = getInstrumentation().getContext().getPackageManager(); |
| 331 | if (pm.hasSystemFeature("android.hardware.type.television") |
| 332 | || pm.hasSystemFeature("android.hardware.type.watch")) { |
| 333 | return false; |
| 334 | } |
| 335 | return true; |
| 336 | } |
| 337 | } |