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