1 /*
2  * Copyright (C) 2023 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 
17 package android.service.assist.classification;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.NonNull;
21 import android.annotation.SdkConstant;
22 import android.annotation.SystemApi;
23 import android.app.Service;
24 import android.content.ComponentName;
25 import android.content.Intent;
26 import android.os.BaseBundle;
27 import android.os.Build;
28 import android.os.CancellationSignal;
29 import android.os.IBinder;
30 import android.os.ICancellationSignal;
31 import android.os.OutcomeReceiver;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 /**
36  * A service using {@link android.app.assist.AssistStructure} to detect fields on the screen.
37  * Service may use classifiers to look at the un-stripped AssistStructure to make informed decision
38  * and classify the fields.
39  *
40  * Currently, it's used to detect the field types for the Autofill Framework to provide relevant
41  * autofill suggestions to the user.
42  *
43  *
44  * The methods are invoked on the binder threads.
45  *
46  * @hide
47  */
48 @SystemApi
49 public abstract class FieldClassificationService extends Service {
50 
51     private static final String TAG = FieldClassificationService.class.getSimpleName();
52 
53     static boolean sDebug = Build.IS_USER ? false : true;
54     static boolean sVerbose = false;
55 
56     /**
57      * The {@link Intent} that must be declared as handled by the service.
58      * To be supported, the service must also require the
59      * {@link android.Manifest.permission#BIND_FIELD_CLASSIFICATION_SERVICE} permission so
60      * that other applications can not abuse it.
61      */
62     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
63     public static final String SERVICE_INTERFACE =
64             "android.service.assist.classification.FieldClassificationService";
65 
66     // Used for metrics / debug only
67     private ComponentName mServiceComponentName;
68 
69     private final class FieldClassificationServiceImpl
70             extends IFieldClassificationService.Stub {
71 
72         @Override
onConnected(boolean debug, boolean verbose)73         public void onConnected(boolean debug, boolean verbose) {
74             handleOnConnected(debug, verbose);
75         }
76 
77         @Override
onDisconnected()78         public void onDisconnected() {
79             handleOnDisconnected();
80         }
81 
82         @Override
onFieldClassificationRequest( FieldClassificationRequest request, IFieldClassificationCallback callback)83         public void onFieldClassificationRequest(
84                 FieldClassificationRequest request, IFieldClassificationCallback callback) {
85             handleOnClassificationRequest(request, callback);
86         }
87     };
88 
89     @CallSuper
90     @Override
onCreate()91     public void onCreate() {
92         super.onCreate();
93         BaseBundle.setShouldDefuse(true);
94     }
95 
96     /** @hide */
97     @Override
onBind(Intent intent)98     public final IBinder onBind(Intent intent) {
99         if (SERVICE_INTERFACE.equals(intent.getAction())) {
100             mServiceComponentName = intent.getComponent();
101             return new FieldClassificationServiceImpl().asBinder();
102         }
103         Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
104         return null;
105     }
106 
107     /**
108      * Called when the Android system connects to service.
109      *
110      * <p>You should generally do initialization here rather than in {@link #onCreate}.
111      */
onConnected()112     public void onConnected() {
113     }
114 
115     /**
116      * Requests the service to handle field classification request.
117      * @param cancellationSignal signal for observing cancellation requests. The system will use
118      *     this to notify you that the detection result is no longer needed and the service should
119      *     stop handling this detection request in order to save resources.
120      * @param outcomeReceiver object used to notify the result of the request. Service <b>must</b>
121      *     call {@link OutcomeReceiver<>#onResult(FieldClassificationResponse)}.
122      */
onClassificationRequest( @onNull FieldClassificationRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<FieldClassificationResponse, Exception> outcomeReceiver)123     public abstract void onClassificationRequest(
124             @NonNull FieldClassificationRequest request,
125             @NonNull CancellationSignal cancellationSignal,
126             @NonNull OutcomeReceiver<FieldClassificationResponse, Exception> outcomeReceiver);
127 
128     /**
129      * Called when the Android system disconnects from the service.
130      *
131      * <p> At this point this service may no longer be an active
132      * {@link FieldClassificationService}.
133      */
onDisconnected()134     public void onDisconnected() {
135     }
136 
handleOnConnected(boolean debug, boolean verbose)137     private void handleOnConnected(boolean debug, boolean verbose) {
138         if (sDebug || debug) {
139             Log.d(TAG, "handleOnConnected(): debug=" + debug + ", verbose=" + verbose);
140         }
141         sDebug = debug;
142         sVerbose = verbose;
143         onConnected();
144     }
145 
handleOnDisconnected()146     private void handleOnDisconnected() {
147         onDisconnected();
148     }
149 
handleOnClassificationRequest( FieldClassificationRequest request, @NonNull IFieldClassificationCallback callback)150     private void handleOnClassificationRequest(
151             FieldClassificationRequest request, @NonNull IFieldClassificationCallback callback) {
152 
153         final ICancellationSignal transport = CancellationSignal.createTransport();
154         final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
155         onClassificationRequest(
156                 request,
157                 cancellationSignal,
158                 new OutcomeReceiver<FieldClassificationResponse, Exception>() {
159                     @Override
160                     public void onResult(FieldClassificationResponse result) {
161                         try {
162                             callback.onSuccess(result);
163                         } catch (RemoteException e) {
164                             e.rethrowFromSystemServer();
165                         }
166                     }
167                     @Override
168                     public void onError(Exception e) {
169                         try {
170                             callback.onFailure();
171                         } catch (RemoteException ex) {
172                             ex.rethrowFromSystemServer();
173                         }
174                     }
175                 });
176     }
177 }
178 
179