1 /* 2 * Copyright (C) 2011 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.content.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assume.assumeTrue; 27 28 import android.Manifest; 29 import android.app.Activity; 30 import android.app.UiAutomation; 31 import android.content.ClipData; 32 import android.content.ClipData.Item; 33 import android.content.ClipDescription; 34 import android.content.ClipboardManager; 35 import android.content.ClipboardManager.OnPrimaryClipChangedListener; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.PackageManager; 40 import android.net.Uri; 41 import android.platform.test.annotations.AppModeNonSdkSandbox; 42 import android.platform.test.annotations.DisabledOnRavenwood; 43 import android.platform.test.ravenwood.RavenwoodRule; 44 45 import androidx.test.InstrumentationRegistry; 46 import androidx.test.runner.AndroidJUnit4; 47 import androidx.test.uiautomator.By; 48 import androidx.test.uiautomator.UiDevice; 49 import androidx.test.uiautomator.Until; 50 51 import com.android.compatibility.common.util.SystemUtil; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 import java.util.concurrent.CountDownLatch; 59 import java.util.concurrent.TimeUnit; 60 61 @RunWith(AndroidJUnit4.class) 62 //@AppModeFull // TODO(Instant) Should clip board data be visible? 63 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") 64 public class ClipboardManagerTest { 65 private Context mContext; 66 private ClipboardManager mClipboardManager; 67 private UiDevice mUiDevice; 68 69 @Before setUp()70 public void setUp() throws Exception { 71 assumeTrue("Skipping Test: Wear-Os does not support ClipboardService", hasAutoFillFeature()); 72 73 mContext = InstrumentationRegistry.getTargetContext(); 74 mClipboardManager = mContext.getSystemService(ClipboardManager.class); 75 76 if (!RavenwoodRule.isOnRavenwood()) { 77 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 78 mUiDevice.wakeUp(); 79 80 // Clear any dialogs and launch an activity as focus is needed to access clipboard. 81 mUiDevice.pressHome(); 82 mUiDevice.pressBack(); 83 launchActivity(MockActivity.class); 84 } 85 } 86 87 @After cleanUp()88 public void cleanUp() { 89 if (mClipboardManager != null) { 90 mClipboardManager.clearPrimaryClip(); 91 } 92 dropShellPermissionIdentity(); 93 } 94 95 @Test testSetGetText()96 public void testSetGetText() { 97 ClipboardManager clipboardManager = mClipboardManager; 98 clipboardManager.setText("Test Text 1"); 99 assertEquals("Test Text 1", clipboardManager.getText()); 100 101 clipboardManager.setText("Test Text 2"); 102 assertEquals("Test Text 2", clipboardManager.getText()); 103 } 104 105 @Test testHasPrimaryClip()106 public void testHasPrimaryClip() { 107 ClipboardManager clipboardManager = mClipboardManager; 108 if (clipboardManager.hasPrimaryClip()) { 109 assertNotNull(clipboardManager.getPrimaryClip()); 110 assertNotNull(clipboardManager.getPrimaryClipDescription()); 111 } else { 112 assertNull(clipboardManager.getPrimaryClip()); 113 assertNull(clipboardManager.getPrimaryClipDescription()); 114 } 115 116 clipboardManager.setPrimaryClip(ClipData.newPlainText("Label", "Text")); 117 assertTrue(clipboardManager.hasPrimaryClip()); 118 } 119 120 @Test testSetPrimaryClip_plainText()121 public void testSetPrimaryClip_plainText() { 122 ClipData textData = ClipData.newPlainText("TextLabel", "Text"); 123 assertSetPrimaryClip(textData, "TextLabel", 124 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 125 new ExpectedClipItem("Text", null, null)); 126 } 127 128 @Test testSetPrimaryClip_intent()129 public void testSetPrimaryClip_intent() { 130 Intent intent = new Intent(mContext, ClipboardManagerTest.class); 131 ClipData intentData = ClipData.newIntent("IntentLabel", intent); 132 assertSetPrimaryClip(intentData, "IntentLabel", 133 new String[] {ClipDescription.MIMETYPE_TEXT_INTENT}, 134 new ExpectedClipItem(null, intent, null)); 135 } 136 137 @Test testSetPrimaryClip_rawUri()138 public void testSetPrimaryClip_rawUri() { 139 Uri uri = Uri.parse("http://www.google.com"); 140 ClipData uriData = ClipData.newRawUri("UriLabel", uri); 141 assertSetPrimaryClip(uriData, "UriLabel", 142 new String[] {ClipDescription.MIMETYPE_TEXT_URILIST}, 143 new ExpectedClipItem(null, null, uri)); 144 } 145 146 @Test 147 @DisabledOnRavenwood(blockedBy = ContentResolver.class) testSetPrimaryClip_contentUri()148 public void testSetPrimaryClip_contentUri() { 149 Uri contentUri = Uri.parse("content://cts/test/for/clipboardmanager"); 150 ClipData contentUriData = ClipData.newUri(mContext.getContentResolver(), 151 "ContentUriLabel", contentUri); 152 assertSetPrimaryClip(contentUriData, "ContentUriLabel", 153 new String[] {ClipDescription.MIMETYPE_TEXT_URILIST}, 154 new ExpectedClipItem(null, null, contentUri)); 155 } 156 157 @Test testSetPrimaryClip_complexItem()158 public void testSetPrimaryClip_complexItem() { 159 Intent intent = new Intent(mContext, ClipboardManagerTest.class); 160 Uri uri = Uri.parse("http://www.google.com"); 161 ClipData multiData = new ClipData(new ClipDescription("ComplexItemLabel", 162 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN, 163 ClipDescription.MIMETYPE_TEXT_INTENT, 164 ClipDescription.MIMETYPE_TEXT_URILIST}), 165 new Item("Text", intent, uri)); 166 assertSetPrimaryClip(multiData, "ComplexItemLabel", 167 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN, 168 ClipDescription.MIMETYPE_TEXT_INTENT, 169 ClipDescription.MIMETYPE_TEXT_URILIST}, 170 new ExpectedClipItem("Text", intent, uri)); 171 } 172 173 @Test testSetPrimaryClip_multipleItems()174 public void testSetPrimaryClip_multipleItems() { 175 Intent intent = new Intent(mContext, ClipboardManagerTest.class); 176 Uri uri = Uri.parse("http://www.google.com"); 177 ClipData textData = ClipData.newPlainText("TextLabel", "Text"); 178 textData.addItem(new Item("More Text")); 179 textData.addItem(new Item(intent)); 180 textData.addItem(new Item(uri)); 181 assertSetPrimaryClip(textData, "TextLabel", 182 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 183 new ExpectedClipItem("Text", null, null), 184 new ExpectedClipItem("More Text", null, null), 185 new ExpectedClipItem(null, intent, null), 186 new ExpectedClipItem(null, null, uri)); 187 } 188 189 @Test 190 @DisabledOnRavenwood(blockedBy = ContentResolver.class) testSetPrimaryClip_multipleMimeTypes()191 public void testSetPrimaryClip_multipleMimeTypes() { 192 ContentResolver contentResolver = mContext.getContentResolver(); 193 194 Intent intent = new Intent(mContext, ClipboardManagerTest.class); 195 Uri uri = Uri.parse("http://www.google.com"); 196 Uri contentUri1 = Uri.parse("content://ctstest/testtable1"); 197 Uri contentUri2 = Uri.parse("content://ctstest/testtable2"); 198 Uri contentUri3 = Uri.parse("content://ctstest/testtable1/0"); 199 Uri contentUri4 = Uri.parse("content://ctstest/testtable1/1"); 200 Uri contentUri5 = Uri.parse("content://ctstest/testtable2/0"); 201 Uri contentUri6 = Uri.parse("content://ctstest/testtable2/1"); 202 Uri contentUri7 = Uri.parse("content://ctstest/testtable2/2"); 203 Uri contentUri8 = Uri.parse("content://ctstest/testtable2/3"); 204 205 ClipData clipData = ClipData.newPlainText("TextLabel", "Text"); 206 clipData.addItem(contentResolver, new Item("More Text")); 207 clipData.addItem(contentResolver, new Item(intent)); 208 clipData.addItem(contentResolver, new Item(uri)); 209 clipData.addItem(contentResolver, new Item(contentUri1)); 210 clipData.addItem(contentResolver, new Item(contentUri2)); 211 clipData.addItem(contentResolver, new Item(contentUri3)); 212 clipData.addItem(contentResolver, new Item(contentUri4)); 213 clipData.addItem(contentResolver, new Item(contentUri5)); 214 clipData.addItem(contentResolver, new Item(contentUri6)); 215 clipData.addItem(contentResolver, new Item(contentUri7)); 216 clipData.addItem(contentResolver, new Item(contentUri8)); 217 218 assertClipData(clipData, "TextLabel", 219 new String[] { 220 ClipDescription.MIMETYPE_TEXT_PLAIN, 221 ClipDescription.MIMETYPE_TEXT_INTENT, 222 ClipDescription.MIMETYPE_TEXT_URILIST, 223 "vnd.android.cursor.dir/com.android.content.testtable1", 224 "vnd.android.cursor.dir/com.android.content.testtable2", 225 "vnd.android.cursor.item/com.android.content.testtable1", 226 "vnd.android.cursor.item/com.android.content.testtable2", 227 "image/jpeg", 228 "audio/mpeg", 229 "video/mpeg" 230 }, 231 new ExpectedClipItem("Text", null, null), 232 new ExpectedClipItem("More Text", null, null), 233 new ExpectedClipItem(null, intent, null), 234 new ExpectedClipItem(null, null, uri), 235 new ExpectedClipItem(null, null, contentUri1), 236 new ExpectedClipItem(null, null, contentUri2), 237 new ExpectedClipItem(null, null, contentUri3), 238 new ExpectedClipItem(null, null, contentUri4), 239 new ExpectedClipItem(null, null, contentUri5), 240 new ExpectedClipItem(null, null, contentUri6), 241 new ExpectedClipItem(null, null, contentUri7), 242 new ExpectedClipItem(null, null, contentUri8)); 243 } 244 245 @Test testPrimaryClipChangedListener()246 public void testPrimaryClipChangedListener() throws Exception { 247 final CountDownLatch latch = new CountDownLatch(1); 248 mClipboardManager.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() { 249 @Override 250 public void onPrimaryClipChanged() { 251 latch.countDown(); 252 } 253 }); 254 255 final ClipData clipData = ClipData.newPlainText("TextLabel", "Text"); 256 mClipboardManager.setPrimaryClip(clipData); 257 258 latch.await(5, TimeUnit.SECONDS); 259 } 260 261 @Test testClearPrimaryClip()262 public void testClearPrimaryClip() { 263 final ClipData clipData = ClipData.newPlainText("TextLabel", "Text"); 264 mClipboardManager.setPrimaryClip(clipData); 265 assertTrue(mClipboardManager.hasPrimaryClip()); 266 assertTrue(mClipboardManager.hasText()); 267 assertNotNull(mClipboardManager.getPrimaryClip()); 268 assertNotNull(mClipboardManager.getPrimaryClipDescription()); 269 270 mClipboardManager.clearPrimaryClip(); 271 assertFalse(mClipboardManager.hasPrimaryClip()); 272 assertFalse(mClipboardManager.hasText()); 273 assertNull(mClipboardManager.getPrimaryClip()); 274 assertNull(mClipboardManager.getPrimaryClipDescription()); 275 } 276 277 @Test 278 @DisabledOnRavenwood(blockedBy = UiAutomation.class) testPrimaryClipNotAvailableWithoutFocus()279 public void testPrimaryClipNotAvailableWithoutFocus() throws Exception { 280 ClipData textData = ClipData.newPlainText("TextLabel", "Text1"); 281 assertSetPrimaryClip(textData, "TextLabel", 282 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 283 new ExpectedClipItem("Text1", null, null)); 284 285 // Press the home button to unfocus the app. 286 mUiDevice.pressHome(); 287 mUiDevice.wait(Until.gone(By.pkg(MockActivity.class.getPackageName())), 5000); 288 289 // We should see an empty clipboard now. 290 assertFalse(mClipboardManager.hasPrimaryClip()); 291 assertFalse(mClipboardManager.hasText()); 292 assertNull(mClipboardManager.getPrimaryClip()); 293 assertNull(mClipboardManager.getPrimaryClipDescription()); 294 295 // We should be able to set the clipboard but not see the contents. 296 mClipboardManager.setPrimaryClip(ClipData.newPlainText("TextLabel", "Text2")); 297 assertFalse(mClipboardManager.hasPrimaryClip()); 298 assertFalse(mClipboardManager.hasText()); 299 assertNull(mClipboardManager.getPrimaryClip()); 300 assertNull(mClipboardManager.getPrimaryClipDescription()); 301 302 // Launch an activity to get back in focus. 303 launchActivity(MockActivity.class); 304 305 // Verify clipboard access is restored. 306 assertNotNull(mClipboardManager.getPrimaryClip()); 307 assertNotNull(mClipboardManager.getPrimaryClipDescription()); 308 309 // Verify we were unable to change the clipboard while out of focus. 310 assertClipData(mClipboardManager.getPrimaryClip(), 311 "TextLabel", 312 new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}, 313 new ExpectedClipItem("Text2", null, null)); 314 } 315 316 @Test 317 @DisabledOnRavenwood(blockedBy = UiAutomation.class) testReadInBackgroundRequiresPermission()318 public void testReadInBackgroundRequiresPermission() throws Exception { 319 ClipData clip = ClipData.newPlainText("TextLabel", "Text1"); 320 mClipboardManager.setPrimaryClip(clip); 321 322 // Press the home button to unfocus the app. 323 mUiDevice.pressHome(); 324 mUiDevice.wait(Until.gone(By.pkg(MockActivity.class.getPackageName())), 5000); 325 326 // Without the READ_CLIPBOARD_IN_BACKGROUND permission, we should see an empty clipboard. 327 assertThat(mClipboardManager.hasPrimaryClip()).isFalse(); 328 assertThat(mClipboardManager.hasText()).isFalse(); 329 assertThat(mClipboardManager.getPrimaryClip()).isNull(); 330 assertThat(mClipboardManager.getPrimaryClipDescription()).isNull(); 331 332 // Having the READ_CLIPBOARD_IN_BACKGROUND permission should allow us to read the clipboard 333 // even when we are not in the foreground. We use the shell identity to simulate holding 334 // this permission; in practice, only privileged system apps can hold this permission (e.g. 335 // an app that has the SYSTEM_TEXT_INTELLIGENCE role). 336 ClipData actual = SystemUtil.callWithShellPermissionIdentity( 337 () -> mClipboardManager.getPrimaryClip(), 338 android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND); 339 assertThat(actual).isNotNull(); 340 assertThat(actual.getItemAt(0).getText()).isEqualTo("Text1"); 341 } 342 343 @Test testClipSourceRecordedWhenClipSet()344 public void testClipSourceRecordedWhenClipSet() { 345 String myPackageName = mContext.getPackageName(); 346 347 ClipData clipData = ClipData.newPlainText("TextLabel", "Text1"); 348 mClipboardManager.setPrimaryClip(clipData); 349 350 adoptShellPermissionIdentity(Manifest.permission.SET_CLIP_SOURCE); 351 assertThat(mClipboardManager.getPrimaryClipSource()).isEqualTo(myPackageName); 352 } 353 354 @Test testSetPrimaryClipAsPackage()355 public void testSetPrimaryClipAsPackage() { 356 adoptShellPermissionIdentity(Manifest.permission.SET_CLIP_SOURCE); 357 358 ClipData clipData = ClipData.newPlainText("TextLabel", "Text1"); 359 mClipboardManager.setPrimaryClipAsPackage(clipData, "test.package"); 360 361 assertThat( 362 mClipboardManager.getPrimaryClipSource()).isEqualTo("test.package"); 363 } 364 launchActivity(Class<? extends Activity> clazz)365 private void launchActivity(Class<? extends Activity> clazz) { 366 Intent intent = new Intent(Intent.ACTION_MAIN); 367 intent.setClassName(mContext.getPackageName(), clazz.getName()); 368 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 369 mContext.startActivity(intent); 370 mUiDevice.wait(Until.hasObject(By.pkg(clazz.getPackageName())), 15000); 371 } 372 373 private class ExpectedClipItem { 374 CharSequence mText; 375 Intent mIntent; 376 Uri mUri; 377 ExpectedClipItem(CharSequence text, Intent intent, Uri uri)378 ExpectedClipItem(CharSequence text, Intent intent, Uri uri) { 379 mText = text; 380 mIntent = intent; 381 mUri = uri; 382 } 383 } 384 assertSetPrimaryClip(ClipData clipData, String expectedLabel, String[] expectedMimeTypes, ExpectedClipItem... expectedClipItems)385 private void assertSetPrimaryClip(ClipData clipData, 386 String expectedLabel, 387 String[] expectedMimeTypes, 388 ExpectedClipItem... expectedClipItems) { 389 ClipboardManager clipboardManager = mClipboardManager; 390 391 clipboardManager.setPrimaryClip(clipData); 392 assertTrue(clipboardManager.hasPrimaryClip()); 393 394 if (expectedClipItems != null 395 && expectedClipItems.length > 0 396 && expectedClipItems[0].mText != null) { 397 assertTrue(clipboardManager.hasText()); 398 } else { 399 assertFalse(clipboardManager.hasText()); 400 } 401 402 assertNotNull(clipboardManager.getPrimaryClip()); 403 assertNotNull(clipboardManager.getPrimaryClipDescription()); 404 405 assertClipData(clipboardManager.getPrimaryClip(), 406 expectedLabel, expectedMimeTypes, expectedClipItems); 407 408 assertClipDescription(clipboardManager.getPrimaryClipDescription(), 409 expectedLabel, expectedMimeTypes); 410 } 411 assertClipData(ClipData actualData, String expectedLabel, String[] expectedMimeTypes, ExpectedClipItem... expectedClipItems)412 private static void assertClipData(ClipData actualData, String expectedLabel, 413 String[] expectedMimeTypes, ExpectedClipItem... expectedClipItems) { 414 if (expectedClipItems != null) { 415 assertEquals(expectedClipItems.length, actualData.getItemCount()); 416 for (int i = 0; i < expectedClipItems.length; i++) { 417 assertClipItem(expectedClipItems[i], actualData.getItemAt(i)); 418 } 419 } else { 420 throw new IllegalArgumentException("Should have at least one expectedClipItem..."); 421 } 422 423 assertClipDescription(actualData.getDescription(), expectedLabel, expectedMimeTypes); 424 } 425 assertClipDescription(ClipDescription description, String expectedLabel, String... mimeTypes)426 private static void assertClipDescription(ClipDescription description, String expectedLabel, 427 String... mimeTypes) { 428 assertEquals(expectedLabel, description.getLabel()); 429 assertEquals(mimeTypes.length, description.getMimeTypeCount()); 430 int mimeTypeCount = description.getMimeTypeCount(); 431 for (int i = 0; i < mimeTypeCount; i++) { 432 assertEquals(mimeTypes[i], description.getMimeType(i)); 433 } 434 } 435 assertClipItem(ExpectedClipItem expectedItem, Item item)436 private static void assertClipItem(ExpectedClipItem expectedItem, Item item) { 437 assertEquals(expectedItem.mText, item.getText()); 438 if (expectedItem.mIntent != null) { 439 assertNotNull(item.getIntent()); 440 } else { 441 assertNull(item.getIntent()); 442 } 443 if (expectedItem.mUri != null) { 444 assertEquals(expectedItem.mUri.toString(), item.getUri().toString()); 445 } else { 446 assertNull(item.getUri()); 447 } 448 } 449 hasAutoFillFeature()450 private boolean hasAutoFillFeature() { 451 if (RavenwoodRule.isOnRavenwood()) { 452 // These tests awkwardly depend on FEATURE_AUTOFILL to detect clipboard support; 453 // even though Ravenwood doesn't support autofill feature, we know we support 454 // clipboard, so we return true so tests are executed 455 return true; 456 } else { 457 return InstrumentationRegistry.getTargetContext().getPackageManager() 458 .hasSystemFeature(PackageManager.FEATURE_AUTOFILL); 459 } 460 } 461 adoptShellPermissionIdentity(String permission)462 private static void adoptShellPermissionIdentity(String permission) { 463 if (RavenwoodRule.isOnRavenwood()) { 464 // TODO: define what "shell permissions" mean on Ravenwood, and offer 465 // a general adoptShellPermissionIdentity implementation; ignored for now 466 } else { 467 InstrumentationRegistry.getInstrumentation().getUiAutomation() 468 .adoptShellPermissionIdentity(permission); 469 } 470 } 471 dropShellPermissionIdentity()472 private static void dropShellPermissionIdentity() { 473 if (RavenwoodRule.isOnRavenwood()) { 474 // TODO: define what "shell permissions" mean on Ravenwood, and offer 475 // a general adoptShellPermissionIdentity implementation; ignored for now 476 } else { 477 InstrumentationRegistry.getInstrumentation().getUiAutomation() 478 .dropShellPermissionIdentity(); 479 } 480 } 481 } 482