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