1 /* 2 * Copyright (C) 2017 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.fail; 22 23 import android.app.Activity; 24 import android.content.ClipData; 25 import android.content.ClipDescription; 26 import android.content.ClipboardManager; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.net.Uri; 30 import android.os.Process; 31 import android.platform.test.annotations.AppModeNonSdkSandbox; 32 import android.platform.test.annotations.AppModeSdkSandbox; 33 import android.platform.test.annotations.DisabledOnRavenwood; 34 import android.platform.test.ravenwood.RavenwoodRule; 35 import android.text.SpannableString; 36 import android.text.style.UnderlineSpan; 37 import android.view.textclassifier.TextClassifier; 38 import android.view.textclassifier.TextLinks; 39 40 import androidx.test.InstrumentationRegistry; 41 import androidx.test.annotation.UiThreadTest; 42 import androidx.test.filters.SmallTest; 43 import androidx.test.runner.AndroidJUnit4; 44 import androidx.test.uiautomator.By; 45 import androidx.test.uiautomator.UiDevice; 46 import androidx.test.uiautomator.Until; 47 48 import com.google.common.collect.Range; 49 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.util.Calendar; 55 56 /** 57 * To run: 58 * cts-tradefed run singleCommand cts-dev -m CtsContentTestCases -t android.content.cts.ClipDescriptionTest 59 */ 60 @SmallTest 61 @RunWith(AndroidJUnit4.class) 62 //@AppModeFull // TODO(Instant) Should clip board data be visible? 63 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 64 public class ClipDescriptionTest { 65 private UiDevice mUiDevice; 66 private Context mContext; 67 68 private static final String SIXTY_CHARS = 69 "Hello this is a string of sixty characters in length exactly"; 70 71 @Before setUp()72 public void setUp() throws Exception { 73 if (RavenwoodRule.isOnRavenwood() || Process.isSdkSandbox()) return; 74 75 mContext = InstrumentationRegistry.getTargetContext(); 76 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 77 mUiDevice.wakeUp(); 78 79 // Clear any dialogs and launch an activity as focus is needed to access clipboard. 80 mUiDevice.pressHome(); 81 mUiDevice.pressBack(); 82 launchActivity(MockActivity.class); 83 } 84 85 @UiThreadTest 86 @Test 87 @DisabledOnRavenwood(blockedBy = ClipboardManager.class) 88 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") testGetTimestamp()89 public void testGetTimestamp() { 90 final ClipboardManager clipboardManager = (ClipboardManager) 91 InstrumentationRegistry.getTargetContext().getSystemService( 92 Context.CLIPBOARD_SERVICE); 93 final long timestampBeforeSet = System.currentTimeMillis(); 94 clipboardManager.setPrimaryClip(ClipData.newPlainText("Dummy text", "Text")); 95 final long timestampAfterSet = System.currentTimeMillis(); 96 final long timestamp = clipboardManager.getPrimaryClipDescription().getTimestamp(); 97 if (timestamp < timestampBeforeSet || timestamp > timestampAfterSet) { 98 fail("Value of timestamp is not as expected.\n" 99 + "timestamp before setting clip: " + logTime(timestampBeforeSet) + "\n" 100 + "timestamp after setting clip: " + logTime(timestampAfterSet) + "\n" 101 + "actual timestamp: " + logTime(timestamp) + "\n" 102 + "clipdata: " + clipboardManager.getPrimaryClip()); 103 } 104 } 105 106 @Test 107 @DisabledOnRavenwood(blockedBy = ClipboardManager.class) 108 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") testIsStyledText()109 public void testIsStyledText() { 110 ClipDescription clipDescription = new ClipDescription( 111 "label", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }); 112 113 // False as clip description is not attached to anything. 114 assertThat(clipDescription.isStyledText()).isFalse(); 115 116 SpannableString spannable = new SpannableString("Hello this is some text"); 117 spannable.setSpan(new UnderlineSpan(), 6, 10, 0); 118 ClipData clipData = new ClipData(clipDescription, new ClipData.Item(spannable)); 119 120 assertThat(clipDescription.isStyledText()).isTrue(); 121 122 ClipboardManager clipboardManager = (ClipboardManager) 123 InstrumentationRegistry.getTargetContext().getSystemService( 124 Context.CLIPBOARD_SERVICE); 125 clipboardManager.setPrimaryClip(clipData); 126 assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isTrue(); 127 128 ClipData clipData2 = ClipData.newPlainText("label", spannable); 129 assertThat(clipData2.getDescription().isStyledText()).isTrue(); 130 clipboardManager.setPrimaryClip(clipData2); 131 assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isTrue(); 132 } 133 134 @Test 135 @DisabledOnRavenwood(blockedBy = ClipboardManager.class) 136 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") testNotStyledText()137 public void testNotStyledText() { 138 ClipDescription clipDescription = new ClipDescription( 139 "label", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }); 140 ClipData clipData = new ClipData(clipDescription, new ClipData.Item("Just text")); 141 assertThat(clipDescription.isStyledText()).isFalse(); 142 143 ClipboardManager clipboardManager = (ClipboardManager) 144 InstrumentationRegistry.getTargetContext().getSystemService( 145 Context.CLIPBOARD_SERVICE); 146 clipboardManager.setPrimaryClip(clipData); 147 assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse(); 148 149 ClipData clipData2 = ClipData.newPlainText("label", "Just some text"); 150 assertThat(clipData2.getDescription().isStyledText()).isFalse(); 151 clipboardManager.setPrimaryClip(clipData2); 152 assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse(); 153 154 // Test that a URI is not considered styled text. 155 ClipData clipDataUri = ClipData.newRawUri("label", Uri.parse("content://test")); 156 assertThat(clipDataUri.getDescription().isStyledText()).isFalse(); 157 clipboardManager.setPrimaryClip(clipDataUri); 158 assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse(); 159 } 160 161 @Test testClassificationNotCompletedBeforeCopy()162 public void testClassificationNotCompletedBeforeCopy() { 163 ClipDescription clipDescription = new ClipDescription( 164 "label", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }); 165 assertThat(clipDescription.getClassificationStatus()) 166 .isEqualTo(ClipDescription.CLASSIFICATION_NOT_COMPLETE); 167 168 ClipData clipData = ClipData.newPlainText("label", "Just some text"); 169 assertThat(clipDescription.getClassificationStatus()) 170 .isEqualTo(ClipDescription.CLASSIFICATION_NOT_COMPLETE); 171 } 172 173 @Test 174 @DisabledOnRavenwood(blockedBy = ClipboardManager.class) 175 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") testClassificationNotPerformedForVeryLongText()176 public void testClassificationNotPerformedForVeryLongText() { 177 StringBuilder builder = new StringBuilder(); 178 for (int i = 0; i < 100; i++) { 179 builder.append(SIXTY_CHARS); 180 } 181 String longString = builder.toString(); 182 ClipData clipData = ClipData.newPlainText("label", longString); 183 184 ClipboardManager clipboardManager = (ClipboardManager) 185 InstrumentationRegistry.getTargetContext().getSystemService( 186 Context.CLIPBOARD_SERVICE); 187 clipboardManager.setPrimaryClip(clipData); 188 ClipDescription description = clipboardManager.getPrimaryClipDescription(); 189 assertThat(description.getClassificationStatus()) 190 .isEqualTo(ClipDescription.CLASSIFICATION_NOT_PERFORMED); 191 assertThat(clipboardManager.getPrimaryClip().getItemAt(0).getTextLinks()) 192 .isNull(); 193 } 194 195 @Test 196 @DisabledOnRavenwood(blockedBy = ClipboardManager.class) 197 @AppModeNonSdkSandbox(reason = "SDK sandboxes cannot access ClipboardManager.") testClassificationConfidenceValuesAreValid()198 public void testClassificationConfidenceValuesAreValid() throws InterruptedException { 199 ClipData clipData = ClipData.newPlainText( 200 "label", "Hi Sam, try www.android.com on 05/04/2021 then visit " 201 + "221B Baker Street, London and email test@example.com"); 202 ClipboardManager clipboardManager = (ClipboardManager) 203 InstrumentationRegistry.getTargetContext().getSystemService( 204 Context.CLIPBOARD_SERVICE); 205 clipboardManager.setPrimaryClip(clipData); 206 207 ClipDescription description = clipboardManager.getPrimaryClipDescription(); 208 for (int i = 0; i < 10; i++) { 209 description = clipboardManager.getPrimaryClipDescription(); 210 if (description.getClassificationStatus() 211 != ClipDescription.CLASSIFICATION_NOT_COMPLETE) { 212 break; 213 } 214 Thread.sleep(1000); 215 } 216 217 if (description.getClassificationStatus() == ClipDescription.CLASSIFICATION_NOT_PERFORMED) { 218 return; 219 } 220 221 float urlConfidence = description.getConfidenceScore(TextClassifier.TYPE_URL); 222 float dateConfidence = description.getConfidenceScore(TextClassifier.TYPE_DATE); 223 float addressConfidence = description.getConfidenceScore(TextClassifier.TYPE_ADDRESS); 224 float emailConfidence = description.getConfidenceScore(TextClassifier.TYPE_EMAIL); 225 226 assertThat(urlConfidence).isIn(Range.closed(0f, 1f)); 227 assertThat(dateConfidence).isIn(Range.closed(0f, 1f)); 228 assertThat(addressConfidence).isIn(Range.closed(0f, 1f)); 229 assertThat(emailConfidence).isIn(Range.closed(0f, 1f)); 230 231 if (urlConfidence > 0 || dateConfidence > 0 || addressConfidence > 0 232 || emailConfidence > 0) { 233 TextLinks textLinks = 234 clipboardManager.getPrimaryClip().getItemAt(0).getTextLinks(); 235 assertThat(getHighestConfidence(TextClassifier.TYPE_URL, textLinks)) 236 .isEqualTo(urlConfidence); 237 assertThat(getHighestConfidence(TextClassifier.TYPE_DATE, textLinks)) 238 .isEqualTo(dateConfidence); 239 assertThat(getHighestConfidence(TextClassifier.TYPE_ADDRESS, textLinks)) 240 .isEqualTo(addressConfidence); 241 assertThat(getHighestConfidence(TextClassifier.TYPE_EMAIL, textLinks)) 242 .isEqualTo(emailConfidence); 243 } 244 } 245 getHighestConfidence(String entity, TextLinks textLinks)246 private float getHighestConfidence(String entity, TextLinks textLinks) { 247 float result = 0; 248 for (TextLinks.TextLink textLink : textLinks.getLinks()) { 249 result = Math.max(result, textLink.getConfidenceScore(entity)); 250 } 251 return result; 252 } 253 254 /** 255 * Convert a System.currentTimeMillis() value to a time of day value like 256 * that printed in logs. MM-DD-YY HH:MM:SS.MMM 257 * 258 * @param millis since the epoch (1/1/1970) 259 * @return String representation of the time. 260 */ logTime(long millis)261 public static String logTime(long millis) { 262 Calendar c = Calendar.getInstance(); 263 if (millis >= 0) { 264 c.setTimeInMillis(millis); 265 return String.format("%tm-%td-%ty %tH:%tM:%tS.%tL", c, c, c, c, c, c, c); 266 } else { 267 return Long.toString(millis); 268 } 269 } 270 launchActivity(Class<? extends Activity> clazz)271 private void launchActivity(Class<? extends Activity> clazz) { 272 Intent intent = new Intent(Intent.ACTION_MAIN); 273 intent.setClassName(mContext.getPackageName(), clazz.getName()); 274 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 275 mContext.startActivity(intent); 276 mUiDevice.wait(Until.hasObject(By.pkg(clazz.getPackageName())), 15000); 277 } 278 } 279