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 android.app.PendingIntent; 19 import android.app.RemoteAction; 20 import android.content.Intent; 21 import android.graphics.drawable.Icon; 22 import android.net.Uri; 23 import android.os.CancellationSignal; 24 import android.service.textclassifier.TextClassifierService; 25 import android.util.ArrayMap; 26 import android.view.textclassifier.ConversationActions; 27 import android.view.textclassifier.SelectionEvent; 28 import android.view.textclassifier.TextClassification; 29 import android.view.textclassifier.TextClassificationContext; 30 import android.view.textclassifier.TextClassificationSessionId; 31 import android.view.textclassifier.TextClassifier; 32 import android.view.textclassifier.TextClassifierEvent; 33 import android.view.textclassifier.TextLanguage; 34 import android.view.textclassifier.TextLinks; 35 import android.view.textclassifier.TextSelection; 36 37 import androidx.core.os.BuildCompat; 38 import androidx.annotation.NonNull; 39 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.TimeUnit; 46 47 /** 48 * Implementation of {@link TextClassifierService} used in the tests. 49 */ 50 public final class CtsTextClassifierService extends TextClassifierService { 51 52 private static final String TAG = "CtsTextClassifierService"; 53 public static final String MY_PACKAGE = "android.view.textclassifier.cts"; 54 55 public static final int DEFAULT_API_WAIT_TIMEOUT = 1_000; 56 57 private static final Icon ICON_RES = 58 Icon.createWithResource("android", android.R.drawable.btn_star); 59 static final Icon ICON_URI = 60 Icon.createWithContentUri(new Uri.Builder() 61 .scheme("content") 62 .authority("com.android.textclassifier.icons") 63 .path("android") 64 .appendPath("" + android.R.drawable.btn_star) 65 .build()); 66 67 private final Map<String, List<TextClassificationSessionId>> mRequestSessions = 68 new ArrayMap<>(); 69 private CountDownLatch mRequestLatch = new CountDownLatch(1); 70 71 /** 72 * Returns the TestWatcher that was used for the testing. 73 */ 74 @NonNull getTestWatcher()75 public static TextClassifierTestWatcher getTestWatcher() { 76 return new TextClassifierTestWatcher(); 77 } 78 79 /** 80 * Returns all incoming session IDs, keyed by API name. 81 */ 82 @NonNull getRequestSessions()83 Map<String, List<TextClassificationSessionId>> getRequestSessions() { 84 return Collections.unmodifiableMap(mRequestSessions); 85 } 86 resetRequestLatch(int count)87 void resetRequestLatch(int count) { 88 mRequestLatch = new CountDownLatch(count); 89 } 90 awaitQuery()91 void awaitQuery() { 92 awaitQuery(DEFAULT_API_WAIT_TIMEOUT); 93 } 94 awaitQuery(long timeoutMillis)95 void awaitQuery(long timeoutMillis) { 96 try { 97 mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 98 } catch (InterruptedException e) { 99 // do nothing 100 } 101 } 102 103 @Override onSuggestSelection(TextClassificationSessionId sessionId, TextSelection.Request request, CancellationSignal cancellationSignal, Callback<TextSelection> callback)104 public void onSuggestSelection(TextClassificationSessionId sessionId, 105 TextSelection.Request request, CancellationSignal cancellationSignal, 106 Callback<TextSelection> callback) { 107 handleRequest(sessionId, "onSuggestSelection"); 108 TextSelection.Builder textSelection = 109 new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()); 110 if (BuildCompat.isAtLeastS() && request.shouldIncludeTextClassification()) { 111 textSelection.setTextClassification(createTextClassification()); 112 } 113 callback.onSuccess(textSelection.build()); 114 } 115 116 @Override onClassifyText(TextClassificationSessionId sessionId, TextClassification.Request request, CancellationSignal cancellationSignal, Callback<TextClassification> callback)117 public void onClassifyText(TextClassificationSessionId sessionId, 118 TextClassification.Request request, CancellationSignal cancellationSignal, 119 Callback<TextClassification> callback) { 120 handleRequest(sessionId, "onClassifyText"); 121 callback.onSuccess(createTextClassification()); 122 } 123 createTextClassification()124 private TextClassification createTextClassification() { 125 return new TextClassification.Builder() 126 .addAction(new RemoteAction( 127 ICON_RES, 128 "Test Action", 129 "Test Action", 130 PendingIntent.getActivity( 131 this, 132 0, 133 new Intent(), 134 PendingIntent.FLAG_IMMUTABLE))) 135 .build(); 136 } 137 138 @Override onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, CancellationSignal cancellationSignal, Callback<TextLinks> callback)139 public void onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, 140 CancellationSignal cancellationSignal, Callback<TextLinks> callback) { 141 handleRequest(sessionId, "onGenerateLinks"); 142 callback.onSuccess(TextClassifier.NO_OP.generateLinks(request)); 143 } 144 145 @Override onDetectLanguage(TextClassificationSessionId sessionId, TextLanguage.Request request, CancellationSignal cancellationSignal, Callback<TextLanguage> callback)146 public void onDetectLanguage(TextClassificationSessionId sessionId, 147 TextLanguage.Request request, CancellationSignal cancellationSignal, 148 Callback<TextLanguage> callback) { 149 handleRequest(sessionId, "onDetectLanguage"); 150 callback.onSuccess(TextClassifier.NO_OP.detectLanguage(request)); 151 } 152 153 @Override onSuggestConversationActions(TextClassificationSessionId sessionId, ConversationActions.Request request, CancellationSignal cancellationSignal, Callback<ConversationActions> callback)154 public void onSuggestConversationActions(TextClassificationSessionId sessionId, 155 ConversationActions.Request request, CancellationSignal cancellationSignal, 156 Callback<ConversationActions> callback) { 157 handleRequest(sessionId, "onSuggestConversationActions"); 158 callback.onSuccess(TextClassifier.NO_OP.suggestConversationActions(request)); 159 } 160 161 @Override onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event)162 public void onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event) { 163 handleRequest(sessionId, "onSelectionEvent"); 164 } 165 166 @Override onTextClassifierEvent(TextClassificationSessionId sessionId, TextClassifierEvent event)167 public void onTextClassifierEvent(TextClassificationSessionId sessionId, 168 TextClassifierEvent event) { 169 handleRequest(sessionId, "onTextClassifierEvent"); 170 } 171 172 @Override onCreateTextClassificationSession(TextClassificationContext context, TextClassificationSessionId sessionId)173 public void onCreateTextClassificationSession(TextClassificationContext context, 174 TextClassificationSessionId sessionId) { 175 handleRequest(sessionId, "onCreateTextClassificationSession"); 176 } 177 178 @Override onDestroyTextClassificationSession(TextClassificationSessionId sessionId)179 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) { 180 handleRequest(sessionId, "onDestroyTextClassificationSession"); 181 } 182 handleRequest(TextClassificationSessionId sessionId, String methodName)183 private void handleRequest(TextClassificationSessionId sessionId, String methodName) { 184 mRequestSessions.computeIfAbsent(methodName, key -> new ArrayList<>()).add(sessionId); 185 mRequestLatch.countDown(); 186 } 187 188 @Override onConnected()189 public void onConnected() { 190 TextClassifierTestWatcher.ServiceWatcher.onConnected(/* service */ this); 191 } 192 193 @Override onDisconnected()194 public void onDisconnected() { 195 TextClassifierTestWatcher.ServiceWatcher.onDisconnected(); 196 } 197 } 198