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 private static final Icon ICON_RES = 56 Icon.createWithResource("android", android.R.drawable.btn_star); 57 static final Icon ICON_URI = 58 Icon.createWithContentUri(new Uri.Builder() 59 .scheme("content") 60 .authority("com.android.textclassifier.icons") 61 .path("android") 62 .appendPath("" + android.R.drawable.btn_star) 63 .build()); 64 65 private final Map<String, List<TextClassificationSessionId>> mRequestSessions = 66 new ArrayMap<>(); 67 private final CountDownLatch mRequestLatch = new CountDownLatch(1); 68 69 /** 70 * Returns the TestWatcher that was used for the testing. 71 */ 72 @NonNull getTestWatcher()73 public static TextClassifierTestWatcher getTestWatcher() { 74 return new TextClassifierTestWatcher(); 75 } 76 77 /** 78 * Returns all incoming session IDs, keyed by API name. 79 */ 80 @NonNull getRequestSessions()81 Map<String, List<TextClassificationSessionId>> getRequestSessions() { 82 return Collections.unmodifiableMap(mRequestSessions); 83 } 84 awaitQuery(long timeoutMillis)85 void awaitQuery(long timeoutMillis) { 86 try { 87 mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 88 } catch (InterruptedException e) { 89 // do nothing 90 } 91 } 92 93 @Override onSuggestSelection(TextClassificationSessionId sessionId, TextSelection.Request request, CancellationSignal cancellationSignal, Callback<TextSelection> callback)94 public void onSuggestSelection(TextClassificationSessionId sessionId, 95 TextSelection.Request request, CancellationSignal cancellationSignal, 96 Callback<TextSelection> callback) { 97 handleRequest(sessionId, "onSuggestSelection"); 98 TextSelection.Builder textSelection = 99 new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()); 100 if (BuildCompat.isAtLeastS() && request.shouldIncludeTextClassification()) { 101 textSelection.setTextClassification(createTextClassification()); 102 } 103 callback.onSuccess(textSelection.build()); 104 } 105 106 @Override onClassifyText(TextClassificationSessionId sessionId, TextClassification.Request request, CancellationSignal cancellationSignal, Callback<TextClassification> callback)107 public void onClassifyText(TextClassificationSessionId sessionId, 108 TextClassification.Request request, CancellationSignal cancellationSignal, 109 Callback<TextClassification> callback) { 110 handleRequest(sessionId, "onClassifyText"); 111 callback.onSuccess(createTextClassification()); 112 } 113 createTextClassification()114 private TextClassification createTextClassification() { 115 return new TextClassification.Builder() 116 .addAction(new RemoteAction( 117 ICON_RES, 118 "Test Action", 119 "Test Action", 120 PendingIntent.getActivity( 121 this, 122 0, 123 new Intent(), 124 PendingIntent.FLAG_IMMUTABLE))) 125 .build(); 126 } 127 128 @Override onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, CancellationSignal cancellationSignal, Callback<TextLinks> callback)129 public void onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, 130 CancellationSignal cancellationSignal, Callback<TextLinks> callback) { 131 handleRequest(sessionId, "onGenerateLinks"); 132 callback.onSuccess(TextClassifier.NO_OP.generateLinks(request)); 133 } 134 135 @Override onDetectLanguage(TextClassificationSessionId sessionId, TextLanguage.Request request, CancellationSignal cancellationSignal, Callback<TextLanguage> callback)136 public void onDetectLanguage(TextClassificationSessionId sessionId, 137 TextLanguage.Request request, CancellationSignal cancellationSignal, 138 Callback<TextLanguage> callback) { 139 handleRequest(sessionId, "onDetectLanguage"); 140 callback.onSuccess(TextClassifier.NO_OP.detectLanguage(request)); 141 } 142 143 @Override onSuggestConversationActions(TextClassificationSessionId sessionId, ConversationActions.Request request, CancellationSignal cancellationSignal, Callback<ConversationActions> callback)144 public void onSuggestConversationActions(TextClassificationSessionId sessionId, 145 ConversationActions.Request request, CancellationSignal cancellationSignal, 146 Callback<ConversationActions> callback) { 147 handleRequest(sessionId, "onSuggestConversationActions"); 148 callback.onSuccess(TextClassifier.NO_OP.suggestConversationActions(request)); 149 } 150 151 @Override onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event)152 public void onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event) { 153 handleRequest(sessionId, "onSelectionEvent"); 154 } 155 156 @Override onTextClassifierEvent(TextClassificationSessionId sessionId, TextClassifierEvent event)157 public void onTextClassifierEvent(TextClassificationSessionId sessionId, 158 TextClassifierEvent event) { 159 handleRequest(sessionId, "onTextClassifierEvent"); 160 } 161 162 @Override onCreateTextClassificationSession(TextClassificationContext context, TextClassificationSessionId sessionId)163 public void onCreateTextClassificationSession(TextClassificationContext context, 164 TextClassificationSessionId sessionId) { 165 handleRequest(sessionId, "onCreateTextClassificationSession"); 166 } 167 168 @Override onDestroyTextClassificationSession(TextClassificationSessionId sessionId)169 public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) { 170 handleRequest(sessionId, "onDestroyTextClassificationSession"); 171 } 172 handleRequest(TextClassificationSessionId sessionId, String methodName)173 private void handleRequest(TextClassificationSessionId sessionId, String methodName) { 174 mRequestSessions.computeIfAbsent(methodName, key -> new ArrayList<>()).add(sessionId); 175 mRequestLatch.countDown(); 176 } 177 178 @Override onConnected()179 public void onConnected() { 180 TextClassifierTestWatcher.ServiceWatcher.onConnected(/* service */ this); 181 } 182 183 @Override onDisconnected()184 public void onDisconnected() { 185 TextClassifierTestWatcher.ServiceWatcher.onDisconnected(); 186 } 187 } 188