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                 try {
125                     mContext.unbindService(mServiceConnection);
126                 } catch (IllegalArgumentException e) {
127                     // no-op, just log the error message.
128                     Slog.w(TAG, "reset(): " + e.getMessage());
129                 }
130                 mServiceConnection = null;
131             } else {
132                 if (sDebug) Slog.d(TAG, "reset(): service is not bound. Do nothing.");
133             }
134         }
135     }
136 
137     /**
138      * Run a command, starting the service connection if necessary.
139      */
connectAndRun(@onNull Command command)140     private void connectAndRun(@NonNull Command command) {
141         synchronized (mLock) {
142             if (mRemoteService != null) {
143                 try {
144                     if (sVerbose) Slog.v(TAG, "running command right away");
145                     command.run(mRemoteService);
146                 } catch (RemoteException e) {
147                     Slog.w(TAG, "exception calling service: " + e);
148                 }
149                 return;
150             } else {
151                 if (sDebug) Slog.d(TAG, "service is null; queuing command");
152                 if (mQueuedCommands == null) {
153                     mQueuedCommands = new ArrayList<>(1);
154                 }
155                 mQueuedCommands.add(command);
156                 // If we're already connected, don't create a new connection, just leave - the
157                 // command will be run when the service connects
158                 if (mServiceConnection != null) return;
159             }
160 
161             if (sVerbose) Slog.v(TAG, "creating connection");
162 
163             // Create the connection
164             mServiceConnection = new ServiceConnection() {
165                 @Override
166                 public void onServiceConnected(ComponentName name, IBinder service) {
167                     if (sVerbose) Slog.v(TAG, "onServiceConnected(): " + name);
168                     synchronized (mLock) {
169                         mRemoteService = IAutofillFieldClassificationService.Stub
170                                 .asInterface(service);
171                         if (mQueuedCommands != null) {
172                             final int size = mQueuedCommands.size();
173                             if (sDebug) Slog.d(TAG, "running " + size + " queued commands");
174                             for (int i = 0; i < size; i++) {
175                                 final Command queuedCommand = mQueuedCommands.get(i);
176                                 try {
177                                     if (sVerbose) Slog.v(TAG, "running queued command #" + i);
178                                     queuedCommand.run(mRemoteService);
179                                 } catch (RemoteException e) {
180                                     Slog.w(TAG, "exception calling " + name + ": " + e);
181                                 }
182                             }
183                             mQueuedCommands = null;
184                         } else if (sDebug) Slog.d(TAG, "no queued commands");
185                     }
186                 }
187 
188                 @Override
189                 @MainThread
190                 public void onServiceDisconnected(ComponentName name) {
191                     if (sVerbose) Slog.v(TAG, "onServiceDisconnected(): " + name);
192                     synchronized (mLock) {
193                         mRemoteService = null;
194                     }
195                 }
196 
197                 @Override
198                 public void onBindingDied(ComponentName name) {
199                     if (sVerbose) Slog.v(TAG, "onBindingDied(): " + name);
200                     synchronized (mLock) {
201                         mRemoteService = null;
202                     }
203                 }
204 
205                 @Override
206                 public void onNullBinding(ComponentName name) {
207                     if (sVerbose) Slog.v(TAG, "onNullBinding(): " + name);
208                     synchronized (mLock) {
209                         mRemoteService = null;
210                     }
211                 }
212             };
213 
214             final ComponentName component = getServiceComponentName();
215             if (sVerbose) Slog.v(TAG, "binding to: " + component);
216             if (component != null) {
217                 final Intent intent = new Intent();
218                 intent.setComponent(component);
219                 final long token = Binder.clearCallingIdentity();
220                 try {
221                     mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
222                             UserHandle.of(mUserId));
223                     if (sVerbose) Slog.v(TAG, "bound");
224                 } finally {
225                     Binder.restoreCallingIdentity(token);
226                 }
227             }
228         }
229     }
230 
231     /**
232      * Gets the name of all available algorithms.
233      */
234     @Nullable
getAvailableAlgorithms()235     String[] getAvailableAlgorithms() {
236         return getMetadataValue(SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS,
237                 (res, id) -> res.getStringArray(id));
238     }
239 
240     /**
241      * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
242      */
243     @Nullable
getDefaultAlgorithm()244     String getDefaultAlgorithm() {
245         return getMetadataValue(SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM, (res, id) -> res.getString(id));
246     }
247 
248     @Nullable
getMetadataValue(String field, MetadataParser<T> parser)249     private <T> T getMetadataValue(String field, MetadataParser<T> parser) {
250         final ServiceInfo serviceInfo = getServiceInfo();
251         if (serviceInfo == null) return null;
252 
253         final PackageManager pm = mContext.getPackageManager();
254 
255         final Resources res;
256         try {
257             res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
258         } catch (PackageManager.NameNotFoundException e) {
259             Log.e(TAG, "Error getting application resources for " + serviceInfo, e);
260             return null;
261         }
262 
263         final int resourceId = serviceInfo.metaData.getInt(field);
264         return parser.get(res, resourceId);
265     }
266 
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)267     void calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues,
268             @NonNull String[] userDataValues, @NonNull String[] categoryIds,
269             @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs,
270             @Nullable ArrayMap<String, String> algorithms,
271             @Nullable ArrayMap<String, Bundle> args) {
272         connectAndRun((service) -> service.calculateScores(callback, actualValues,
273                 userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args));
274     }
275 
dump(String prefix, PrintWriter pw)276     void dump(String prefix, PrintWriter pw) {
277         final ComponentName impl = getServiceComponentName();
278         pw.print(prefix); pw.print("User ID: "); pw.println(mUserId);
279         pw.print(prefix); pw.print("Queued commands: ");
280         if (mQueuedCommands == null) {
281             pw.println("N/A");
282         } else {
283             pw.println(mQueuedCommands.size());
284         }
285         pw.print(prefix); pw.print("Implementation: ");
286         if (impl == null) {
287             pw.println("N/A");
288             return;
289         }
290         pw.println(impl.flattenToShortString());
291 
292         try {
293             pw.print(prefix); pw.print("Available algorithms: ");
294             pw.println(Arrays.toString(getAvailableAlgorithms()));
295             pw.print(prefix); pw.print("Default algorithm: "); pw.println(getDefaultAlgorithm());
296         } catch (Exception e) {
297             pw.print("ERROR CALLING SERVICE: " ); pw.println(e);
298         }
299     }
300 
301     private static interface Command {
run(IAutofillFieldClassificationService service)302         void run(IAutofillFieldClassificationService service) throws RemoteException;
303     }
304 
305     private static interface MetadataParser<T> {
get(Resources res, int resId)306         T get(Resources res, int resId);
307     }
308 }
309