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