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