• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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