1 /* 2 * Copyright (C) 2019 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 package android.view.textclassifier.cts; 17 18 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 19 20 import static com.google.common.truth.Truth.assertThat; 21 22 import static org.junit.Assume.assumeTrue; 23 24 import android.app.PendingIntent; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.graphics.drawable.Icon; 29 import android.os.CancellationSignal; 30 import android.os.SystemClock; 31 import android.service.textclassifier.TextClassifierService; 32 import android.view.textclassifier.ConversationActions; 33 import android.view.textclassifier.SelectionEvent; 34 import android.view.textclassifier.TextClassification; 35 import android.view.textclassifier.TextClassificationContext; 36 import android.view.textclassifier.TextClassificationManager; 37 import android.view.textclassifier.TextClassificationSessionId; 38 import android.view.textclassifier.TextClassifier; 39 import android.view.textclassifier.TextSelection; 40 41 import androidx.core.os.BuildCompat; 42 import androidx.test.InstrumentationRegistry; 43 import androidx.test.core.app.ApplicationProvider; 44 import androidx.test.runner.AndroidJUnit4; 45 46 import com.android.compatibility.common.util.BlockingBroadcastReceiver; 47 import com.android.compatibility.common.util.RequiredServiceRule; 48 import com.android.compatibility.common.util.SafeCleanerRule; 49 50 import org.junit.Rule; 51 import org.junit.Test; 52 import org.junit.rules.RuleChain; 53 import org.junit.runner.RunWith; 54 55 import java.util.Collections; 56 import java.util.List; 57 import java.util.concurrent.CountDownLatch; 58 59 /** 60 * Tests for TextClassifierService query related functions. 61 * 62 * <p> 63 * We use a non-standard TextClassifierService for TextClassifierService-related CTS tests. A 64 * non-standard TextClassifierService that is set via device config. This non-standard 65 * TextClassifierService is not defined in the trust TextClassifierService, it should only receive 66 * queries from clients in the same package. 67 */ 68 @RunWith(AndroidJUnit4.class) 69 public class TextClassifierServiceSwapTest { 70 // TODO: Add more tests to verify all the TC APIs call between caller and TCS. 71 private static final String TAG = "TextClassifierServiceSwapTest"; 72 73 private final TextClassifierTestWatcher mTestWatcher = 74 CtsTextClassifierService.getTestWatcher(); 75 private final SafeCleanerRule mSafeCleanerRule = mTestWatcher.newSafeCleaner(); 76 private final TextClassificationContext mTextClassificationContext = 77 new TextClassificationContext.Builder( 78 ApplicationProvider.getApplicationContext().getPackageName(), 79 TextClassifier.WIDGET_TYPE_EDIT_WEBVIEW) 80 .build(); 81 private final RequiredServiceRule mRequiredServiceRule = 82 new RequiredServiceRule(Context.TEXT_CLASSIFICATION_SERVICE); 83 84 @Rule 85 public final RuleChain mAllRules = RuleChain 86 .outerRule(mRequiredServiceRule) 87 .around(mTestWatcher) 88 .around(mSafeCleanerRule); 89 90 @Test testOutsideOfPackageActivity_noRequestReceived()91 public void testOutsideOfPackageActivity_noRequestReceived() throws Exception { 92 // Start an Activity from another package to trigger a TextClassifier call 93 runQueryTextClassifierServiceActivity(); 94 95 // Wait for the TextClassifierService to connect. 96 // Note that the system requires a query to the TextClassifierService before it is 97 // first connected. 98 final CtsTextClassifierService service = mTestWatcher.getService(); 99 100 // Wait a delay for the query is delivered. 101 service.awaitQuery(); 102 103 // Verify the request was not passed to the service. 104 assertThat(service.getRequestSessions()).isEmpty(); 105 } 106 107 @Test testMultipleActiveSessions()108 public void testMultipleActiveSessions() throws Exception { 109 final TextClassification.Request request = 110 new TextClassification.Request.Builder("Hello World", 0, 1).build(); 111 final TextClassificationManager tcm = 112 ApplicationProvider.getApplicationContext().getSystemService( 113 TextClassificationManager.class); 114 final TextClassifier firstSession = 115 tcm.createTextClassificationSession(mTextClassificationContext); 116 final TextClassifier secondSessionSession = 117 tcm.createTextClassificationSession(mTextClassificationContext); 118 119 firstSession.classifyText(request); 120 final CtsTextClassifierService service = mTestWatcher.getService(); 121 service.awaitQuery(); 122 123 service.resetRequestLatch(1); 124 secondSessionSession.classifyText(request); 125 service.awaitQuery(); 126 127 final List<TextClassificationSessionId> sessionIds = 128 service.getRequestSessions().get("onClassifyText"); 129 assertThat(sessionIds).hasSize(2); 130 assertThat(sessionIds.get(0).getValue()).isNotEqualTo(sessionIds.get(1).getValue()); 131 132 service.resetRequestLatch(2); 133 firstSession.destroy(); 134 secondSessionSession.destroy(); 135 service.awaitQuery(); 136 137 final List<TextClassificationSessionId> destroyedSessionIds = 138 service.getRequestSessions().get("onDestroyTextClassificationSession"); 139 assertThat(destroyedSessionIds).hasSize(2); 140 firstSession.isDestroyed(); 141 secondSessionSession.isDestroyed(); 142 } 143 serviceOnSuggestConversationActions(CtsTextClassifierService service)144 private void serviceOnSuggestConversationActions(CtsTextClassifierService service) 145 throws Exception { 146 ConversationActions.Request conversationActionRequest = 147 new ConversationActions.Request.Builder(Collections.emptyList()).build(); 148 // TODO: add @TestApi for TextClassificationSessionId and use it 149 SelectionEvent event = 150 SelectionEvent.createSelectionStartedEvent( 151 SelectionEvent.INVOCATION_LINK, 1); 152 final CountDownLatch onSuccessLatch = new CountDownLatch(1); 153 service.onSuggestConversationActions(event.getSessionId(), 154 conversationActionRequest, new CancellationSignal(), 155 new TextClassifierService.Callback<ConversationActions>() { 156 @Override 157 public void onFailure(CharSequence charSequence) { 158 // do nothing 159 } 160 161 @Override 162 public void onSuccess(ConversationActions o) { 163 onSuccessLatch.countDown(); 164 } 165 }); 166 onSuccessLatch.await(); 167 final List<TextClassificationSessionId> sessionIds = 168 service.getRequestSessions().get("onSuggestConversationActions"); 169 assertThat(sessionIds).hasSize(1); 170 } 171 172 @Test testTextClassifierServiceApiCoverage()173 public void testTextClassifierServiceApiCoverage() throws Exception { 174 // Implemented for API test coverage only 175 // Any method that is better tested not be called here. 176 // We already have tests for the TC APIs 177 // We however need to directly query the TextClassifierService methods in the tests for 178 // code coverage. 179 CtsTextClassifierService service = new CtsTextClassifierService(); 180 181 service.onConnected(); 182 183 serviceOnSuggestConversationActions(service); 184 185 service.onDisconnected(); 186 } 187 188 @Test testResourceIconsRewrittenToContentUriIcons_classifyText()189 public void testResourceIconsRewrittenToContentUriIcons_classifyText() throws Exception { 190 final TextClassifier tc = ApplicationProvider.getApplicationContext() 191 .getSystemService(TextClassificationManager.class) 192 .getTextClassifier(); 193 final TextClassification.Request request = 194 new TextClassification.Request.Builder("0800 123 4567", 0, 12).build(); 195 196 final TextClassification classification = tc.classifyText(request); 197 final Icon icon = classification.getActions().get(0).getIcon(); 198 assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI); 199 assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri()); 200 } 201 202 @Test testResourceIconsRewrittenToContentUriIcons_suggestSelection()203 public void testResourceIconsRewrittenToContentUriIcons_suggestSelection() throws Exception { 204 assumeTrue(BuildCompat.isAtLeastS()); 205 206 final TextClassifier tc = ApplicationProvider.getApplicationContext() 207 .getSystemService(TextClassificationManager.class) 208 .getTextClassifier(); 209 final TextSelection.Request request = 210 new TextSelection.Request.Builder("0800 123 4567", 0, 12) 211 .setIncludeTextClassification(true) 212 .build(); 213 214 final TextSelection textSelection = tc.suggestSelection(request); 215 final Icon icon = textSelection.getTextClassification().getActions().get(0).getIcon(); 216 assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI); 217 assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri()); 218 } 219 220 /** 221 * Start an Activity from another package that queries the device's TextClassifierService when 222 * started and immediately terminates itself. When the Activity finishes, it sends broadcast, we 223 * check that whether the finish broadcast is received. 224 */ runQueryTextClassifierServiceActivity()225 private void runQueryTextClassifierServiceActivity() { 226 final String actionQueryActivityFinish = 227 "ACTION_QUERY_SERVICE_ACTIVITY_FINISH_" + SystemClock.uptimeMillis(); 228 final Context context = InstrumentationRegistry.getTargetContext(); 229 230 // register a activity finish receiver 231 final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context, 232 actionQueryActivityFinish); 233 receiver.register(); 234 235 // Start an Activity from another package 236 final Intent outsideActivity = new Intent(); 237 outsideActivity.setComponent(new ComponentName("android.textclassifier.cts2", 238 "android.textclassifier.cts2.QueryTextClassifierServiceActivity")); 239 outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK); 240 final Intent broadcastIntent = new Intent(actionQueryActivityFinish); 241 final PendingIntent pendingIntent = 242 PendingIntent.getBroadcast( 243 context, 244 0, 245 broadcastIntent, 246 PendingIntent.FLAG_IMMUTABLE); 247 outsideActivity.putExtra("finishBroadcast", pendingIntent); 248 context.startActivity(outsideActivity); 249 250 TextClassifierTestWatcher.waitForIdle(); 251 252 // Verify the finish broadcast is received. 253 final Intent intent = receiver.awaitForBroadcast(); 254 assertThat(intent).isNotNull(); 255 256 // unregister receiver 257 receiver.unregisterQuietly(); 258 } 259 } 260