blob: af55dcd0ed7251fb8fad6bcc06ff7a226255e9a8 [file] [log] [blame]
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -08001/*
2 * Copyright (C) 2018 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
17package android.view.textclassifier;
18
19import android.annotation.IntRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.WorkerThread;
23import android.content.Context;
24import android.os.Looper;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.service.textclassifier.ITextClassificationCallback;
28import android.service.textclassifier.ITextClassifierService;
29import android.service.textclassifier.ITextLinksCallback;
30import android.service.textclassifier.ITextSelectionCallback;
31
32import java.util.concurrent.CountDownLatch;
33import java.util.concurrent.TimeUnit;
34
35/**
36 * Proxy to the system's default TextClassifier.
37 */
38final class SystemTextClassifier implements TextClassifier {
39
40 private static final String LOG_TAG = "SystemTextClassifier";
41
42 private final ITextClassifierService mManagerService;
43 private final TextClassifier mFallback;
44
45 SystemTextClassifier(Context context) throws ServiceManager.ServiceNotFoundException {
46 mManagerService = ITextClassifierService.Stub.asInterface(
47 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
48 mFallback = new TextClassifierImpl(context);
49 }
50
51 /**
52 * @inheritDoc
53 */
54 @WorkerThread
55 public TextSelection suggestSelection(
56 @NonNull CharSequence text,
57 @IntRange(from = 0) int selectionStartIndex,
58 @IntRange(from = 0) int selectionEndIndex,
59 @Nullable TextSelection.Options options) {
60 Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */);
61 try {
62 final TextSelectionCallback callback = new TextSelectionCallback();
63 mManagerService.onSuggestSelection(
64 text, selectionStartIndex, selectionEndIndex, options, callback);
65 final TextSelection selection = callback.mReceiver.get();
66 if (selection != null) {
67 return selection;
68 }
69 } catch (RemoteException e) {
70 e.rethrowAsRuntimeException();
71 } catch (InterruptedException e) {
72 Log.d(LOG_TAG, e.getMessage());
73 }
74 return mFallback.suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
75 }
76
77 /**
78 * @inheritDoc
79 */
80 @WorkerThread
81 public TextClassification classifyText(
82 @NonNull CharSequence text,
83 @IntRange(from = 0) int startIndex,
84 @IntRange(from = 0) int endIndex,
85 @Nullable TextClassification.Options options) {
86 Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */);
87 try {
88 final TextClassificationCallback callback = new TextClassificationCallback();
89 mManagerService.onClassifyText(text, startIndex, endIndex, options, callback);
90 final TextClassification classification = callback.mReceiver.get();
91 if (classification != null) {
92 return classification;
93 }
94 } catch (RemoteException e) {
95 e.rethrowAsRuntimeException();
96 } catch (InterruptedException e) {
97 Log.d(LOG_TAG, e.getMessage());
98 }
99 return mFallback.classifyText(text, startIndex, endIndex, options);
100 }
101
102 /**
103 * @inheritDoc
104 */
105 @WorkerThread
106 public TextLinks generateLinks(
107 @NonNull CharSequence text, @Nullable TextLinks.Options options) {
108 Utils.validate(text, false /* allowInMainThread */);
109 try {
110 final TextLinksCallback callback = new TextLinksCallback();
111 mManagerService.onGenerateLinks(text, options, callback);
112 final TextLinks links = callback.mReceiver.get();
113 if (links != null) {
114 return links;
115 }
116 } catch (RemoteException e) {
117 e.rethrowAsRuntimeException();
118 } catch (InterruptedException e) {
119 Log.d(LOG_TAG, e.getMessage());
120 }
121 return mFallback.generateLinks(text, options);
122 }
123
124 private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
125
126 final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>();
127
128 @Override
129 public void onSuccess(TextSelection selection) {
130 mReceiver.onSuccess(selection);
131 }
132
133 @Override
134 public void onFailure() {
135 mReceiver.onFailure();
136 }
137 }
138
139 private static final class TextClassificationCallback extends ITextClassificationCallback.Stub {
140
141 final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>();
142
143 @Override
144 public void onSuccess(TextClassification classification) {
145 mReceiver.onSuccess(classification);
146 }
147
148 @Override
149 public void onFailure() {
150 mReceiver.onFailure();
151 }
152 }
153
154 private static final class TextLinksCallback extends ITextLinksCallback.Stub {
155
156 final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>();
157
158 @Override
159 public void onSuccess(TextLinks links) {
160 mReceiver.onSuccess(links);
161 }
162
163 @Override
164 public void onFailure() {
165 mReceiver.onFailure();
166 }
167 }
168
169 private static final class ResponseReceiver<T> {
170
171 private final CountDownLatch mLatch = new CountDownLatch(1);
172
173 private T mResponse;
174
175 public void onSuccess(T response) {
176 mResponse = response;
177 mLatch.countDown();
178 }
179
180 public void onFailure() {
181 Log.e(LOG_TAG, "Request failed.", null);
182 mLatch.countDown();
183 }
184
185 @Nullable
186 public T get() throws InterruptedException {
187 // If this is running on the main thread, do not block for a response.
188 // The response will unfortunately be null and the TextClassifier should depend on its
189 // fallback.
190 // NOTE that TextClassifier calls should preferably always be called on a worker thread.
191 if (Looper.myLooper() != Looper.getMainLooper()) {
192 mLatch.await(2, TimeUnit.SECONDS);
193 }
194 return mResponse;
195 }
196 }
197}