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