1 /*
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 package com.android.server.autofill;
17 
18 import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
19 import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM;
20 
21 import static com.android.server.autofill.Helper.sDebug;
22 import static com.android.server.autofill.Helper.sVerbose;
23 
24 import android.Manifest;
25 import android.annotation.MainThread;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.ServiceConnection;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ResolveInfo;
34 import android.content.pm.ServiceInfo;
35 import android.content.res.Resources;
36 import android.os.Binder;
37 import android.os.Bundle;
38 import android.os.IBinder;
39 import android.os.RemoteCallback;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.service.autofill.AutofillFieldClassificationService;
43 import android.service.autofill.IAutofillFieldClassificationService;
44 import android.util.ArrayMap;
45 import android.util.Log;
46 import android.util.Slog;
47 import android.view.autofill.AutofillValue;
48 
49 import com.android.internal.annotations.GuardedBy;
50 
51 import java.io.PrintWriter;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 
56 /**
57  * Strategy used to bridge the field classification algorithms provided by a service in an external
58  * package.
59  */
60 //TODO(b/70291841): add unit tests ?
61 final class FieldClassificationStrategy {
62 
63     private static final String TAG = "FieldClassificationStrategy";
64 
65     private final Context mContext;
66     private final Object mLock = new Object();
67     private final int mUserId;
68 
69     @GuardedBy("mLock")
70     private ServiceConnection mServiceConnection;
71 
72     @GuardedBy("mLock")
73     private IAutofillFieldClassificationService mRemoteService;
74 
75     @GuardedBy("mLock")
76     private ArrayList<Command> mQueuedCommands;
77 
FieldClassificationStrategy(Context context, int userId)78     public FieldClassificationStrategy(Context context, int userId) {
79         mContext = context;
80         mUserId = userId;
81     }
82 
83     @Nullable
getServiceInfo()84     ServiceInfo getServiceInfo() {
85         final String packageName =
86                 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
87         if (packageName == null) {
88             Slog.w(TAG, "no external services package!");
89             return null;
90         }
91 
92         final Intent intent = new Intent(AutofillFieldClassificationService.SERVICE_INTERFACE);
93         intent.setPackage(packageName);
94         final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
95                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
96         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
97             Slog.w(TAG, "No valid components found.");
98             return null;
99         }
100         return resolveInfo.serviceInfo;
101     }
102 
103     @Nullable
getServiceComponentName()104     private ComponentName getServiceComponentName() {
105         final ServiceInfo serviceInfo = getServiceInfo();
106         if (serviceInfo == null) return null;
107 
108         final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
109         if (!Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE
110                 .equals(serviceInfo.permission)) {
111             Slog.w(TAG, name.flattenToShortString() + " does not require permission "
112                     + Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE);
113             return null;
114         }
115 
116         if (sVerbose) Slog.v(TAG, "getServiceComponentName(): " + name);
117         return name;
118     }
119 
reset()120     void reset() {
121         synchronized (mLock) {
122             if (mServiceConnection != null) {
123                 if (sDebug) Slog.d(TAG, "reset(): unbinding service.");
124                 mContext.unbindService(mServiceConnection);
125                 mServiceConnection = null;
126             } else {
127                 if (sDebug) Slog.d(TAG, "reset(): service is not bound. Do nothing.");
128             }
129         }
130     }
131 
132     /**
133      * Run a command, starting the service connection if necessary.
134      */
connectAndRun(@onNull Command command)135     private void connectAndRun(@NonNull Command command) {
136         synchronized (mLock) {
137             if (mRemoteService != null) {
138                 try {
139                     if (sVerbose) Slog.v(TAG, "running command right away");
140                     command.run(mRemoteService);
141                 } catch (RemoteException e) {
142                     Slog.w(TAG, "exception calling service: " + e);
143                 }
144                 return;
145             } else {
146                 if (sDebug) Slog.d(TAG, "service is null; queuing command");
147                 if (mQueuedCommands == null) {
148                     mQueuedCommands = new ArrayList<>(1);
149                 }
150                 mQueuedCommands.add(command);
151                 // If we're already connected, don't create a new connection, just leave - the
152                 // command will be run when the service connects
153                 if (mServiceConnection != null) return;
154             }
155 
156             if (sVerbose) Slog.v(TAG, "creating connection");
157 
158             // Create the connection
159             mServiceConnection = new ServiceConnection() {
160                 @Override
161                 public void onServiceConnected(ComponentName name, IBinder service) {
162                     if (sVerbose) Slog.v(TAG, "onServiceConnected(): " + name);
163                     synchronized (mLock) {
164                         mRemoteService = IAutofillFieldClassificationService.Stub
165                                 .asInterface(service);
166                         if (mQueuedCommands != null) {
167                             final int size = mQueuedCommands.size();
168                             if (sDebug) Slog.d(TAG, "running " + size + " queued commands");
169                             for (int i = 0; i < size; i++) {
170                                 final Command queuedCommand = mQueuedCommands.get(i);
171                                 try {
172                                     if (sVerbose) Slog.v(TAG, "running queued command #" + i);
173                                     queuedCommand.run(mRemoteService);
174                                 } catch (RemoteException e) {
175                                     Slog.w(TAG, "exception calling " + name + ": " + e);
176                                 }
177                             }
178                             mQueuedCommands = null;
179                         } else if (sDebug) Slog.d(TAG, "no queued commands");
180                     }
181                 }
182 
183                 @Override
184                 @MainThread
185                 public void onServiceDisconnected(ComponentName name) {
186                     if (sVerbose) Slog.v(TAG, "onServiceDisconnected(): " + name);
187                     synchronized (mLock) {
188                         mRemoteService = null;
189                     }
190                 }
191 
192                 @Override
193                 public void onBindingDied(ComponentName name) {
194                     if (sVerbose) Slog.v(TAG, "onBindingDied(): " + name);
195                     synchronized (mLock) {
196                         mRemoteService = null;
197                     }
198                 }
199 
200                 @Override
201                 public void onNullBinding(ComponentName name) {
202                     if (sVerbose) Slog.v(TAG, "onNullBinding(): " + name);
203                     synchronized (mLock) {
204                         mRemoteService = null;
205                     }
206                 }
207             };
208 
209             final ComponentName component = getServiceComponentName();
210             if (sVerbose) Slog.v(TAG, "binding to: " + component);
211             if (component != null) {
212                 final Intent intent = new Intent();
213                 intent.setComponent(component);
214                 final long token = Binder.clearCallingIdentity();
215                 try {
216                     mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
217                             UserHandle.of(mUserId));
218                     if (sVerbose) Slog.v(TAG, "bound");
219                 } finally {
220                     Binder.restoreCallingIdentity(token);
221                 }
222             }
223         }
224     }
225 
226     /**
227      * Gets the name of all available algorithms.
228      */
229     @Nullable
getAvailableAlgorithms()230     String[] getAvailableAlgorithms() {
231         return getMetadataValue(SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS,
232                 (res, id) -> res.getStringArray(id));
233     }
234 
235     /**
236      * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
237      */
238     @Nullable
getDefaultAlgorithm()239     String getDefaultAlgorithm() {
240         return getMetadataValue(SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM, (res, id) -> res.getString(id));
241     }
242 
243     @Nullable
getMetadataValue(String field, MetadataParser<T> parser)244     private <T> T getMetadataValue(String field, MetadataParser<T> parser) {
245         final ServiceInfo serviceInfo = getServiceInfo();
246         if (serviceInfo == null) return null;
247 
248         final PackageManager pm = mContext.getPackageManager();
249 
250         final Resources res;
251         try {
252             res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
253         } catch (PackageManager.NameNotFoundException e) {
254             Log.e(TAG, "Error getting application resources for " + serviceInfo, e);
255             return null;
256         }
257 
258         final int resourceId = serviceInfo.metaData.getInt(field);
259         return parser.get(res, resourceId);
260     }
261 
calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues, @NonNull String[] userDataValues, @NonNull String[] categoryIds, @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, @Nullable ArrayMap<String, String> algorithms, @Nullable ArrayMap<String, Bundle> args)262     void calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues,
263             @NonNull String[] userDataValues, @NonNull String[] categoryIds,
264             @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
265             @Nullable ArrayMap<String, String> algorithms,
266             @Nullable ArrayMap<String, Bundle> args) {
267         connectAndRun((service) -> service.calculateScores(callback, actualValues,
268                 userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args));
269     }
270 
dump(String prefix, PrintWriter pw)271     void dump(String prefix, PrintWriter pw) {
272         final ComponentName impl = getServiceComponentName();
273         pw.print(prefix); pw.print("User ID: "); pw.println(mUserId);
274         pw.print(prefix); pw.print("Queued commands: ");
275         if (mQueuedCommands == null) {
276             pw.println("N/A");
277         } else {
278             pw.println(mQueuedCommands.size());
279         }
280         pw.print(prefix); pw.print("Implementation: ");
281         if (impl == null) {
282             pw.println("N/A");
283             return;
284         }
285         pw.println(impl.flattenToShortString());
286 
287         try {
288             pw.print(prefix); pw.print("Available algorithms: ");
289             pw.println(Arrays.toString(getAvailableAlgorithms()));
290             pw.print(prefix); pw.print("Default algorithm: "); pw.println(getDefaultAlgorithm());
291         } catch (Exception e) {
292             pw.print("ERROR CALLING SERVICE: " ); pw.println(e);
293         }
294     }
295 
296     private static interface Command {
run(IAutofillFieldClassificationService service)297         void run(IAutofillFieldClassificationService service) throws RemoteException;
298     }
299 
300     private static interface MetadataParser<T> {
get(Resources res, int resId)301         T get(Resources res, int resId);
302     }
303 }
304