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