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