| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.server.autofill; |
| |
| import static com.android.server.autofill.Helper.sDebug; |
| import static com.android.server.autofill.Helper.sVerbose; |
| import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS; |
| import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM; |
| |
| import android.Manifest; |
| import android.annotation.MainThread; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.content.res.Resources; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteCallback; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.service.autofill.AutofillFieldClassificationService; |
| import android.service.autofill.IAutofillFieldClassificationService; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.autofill.AutofillValue; |
| |
| import com.android.internal.annotations.GuardedBy; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Strategy used to bridge the field classification algorithms provided by a service in an external |
| * package. |
| */ |
| //TODO(b/70291841): add unit tests ? |
| final class FieldClassificationStrategy { |
| |
| private static final String TAG = "FieldClassificationStrategy"; |
| |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| private final int mUserId; |
| |
| @GuardedBy("mLock") |
| private ServiceConnection mServiceConnection; |
| |
| @GuardedBy("mLock") |
| private IAutofillFieldClassificationService mRemoteService; |
| |
| @GuardedBy("mLock") |
| private ArrayList<Command> mQueuedCommands; |
| |
| public FieldClassificationStrategy(Context context, int userId) { |
| mContext = context; |
| mUserId = userId; |
| } |
| |
| @Nullable |
| ServiceInfo getServiceInfo() { |
| final String packageName = |
| mContext.getPackageManager().getServicesSystemSharedLibraryPackageName(); |
| if (packageName == null) { |
| Slog.w(TAG, "no external services package!"); |
| return null; |
| } |
| |
| final Intent intent = new Intent(AutofillFieldClassificationService.SERVICE_INTERFACE); |
| intent.setPackage(packageName); |
| final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, |
| PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); |
| if (resolveInfo == null || resolveInfo.serviceInfo == null) { |
| Slog.w(TAG, "No valid components found."); |
| return null; |
| } |
| return resolveInfo.serviceInfo; |
| } |
| |
| @Nullable |
| private ComponentName getServiceComponentName() { |
| final ServiceInfo serviceInfo = getServiceInfo(); |
| if (serviceInfo == null) return null; |
| |
| final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); |
| if (!Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE |
| .equals(serviceInfo.permission)) { |
| Slog.w(TAG, name.flattenToShortString() + " does not require permission " |
| + Manifest.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE); |
| return null; |
| } |
| |
| if (sVerbose) Slog.v(TAG, "getServiceComponentName(): " + name); |
| return name; |
| } |
| |
| void reset() { |
| synchronized (mLock) { |
| if (mServiceConnection != null) { |
| if (sDebug) Slog.d(TAG, "reset(): unbinding service."); |
| mContext.unbindService(mServiceConnection); |
| mServiceConnection = null; |
| } else { |
| if (sDebug) Slog.d(TAG, "reset(): service is not bound. Do nothing."); |
| } |
| } |
| } |
| |
| /** |
| * Run a command, starting the service connection if necessary. |
| */ |
| private void connectAndRun(@NonNull Command command) { |
| synchronized (mLock) { |
| if (mRemoteService != null) { |
| try { |
| if (sVerbose) Slog.v(TAG, "running command right away"); |
| command.run(mRemoteService); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "exception calling service: " + e); |
| } |
| return; |
| } else { |
| if (sDebug) Slog.d(TAG, "service is null; queuing command"); |
| if (mQueuedCommands == null) { |
| mQueuedCommands = new ArrayList<>(1); |
| } |
| mQueuedCommands.add(command); |
| // If we're already connected, don't create a new connection, just leave - the |
| // command will be run when the service connects |
| if (mServiceConnection != null) return; |
| } |
| |
| if (sVerbose) Slog.v(TAG, "creating connection"); |
| |
| // Create the connection |
| mServiceConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| if (sVerbose) Slog.v(TAG, "onServiceConnected(): " + name); |
| synchronized (mLock) { |
| mRemoteService = IAutofillFieldClassificationService.Stub |
| .asInterface(service); |
| if (mQueuedCommands != null) { |
| final int size = mQueuedCommands.size(); |
| if (sDebug) Slog.d(TAG, "running " + size + " queued commands"); |
| for (int i = 0; i < size; i++) { |
| final Command queuedCommand = mQueuedCommands.get(i); |
| try { |
| if (sVerbose) Slog.v(TAG, "running queued command #" + i); |
| queuedCommand.run(mRemoteService); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "exception calling " + name + ": " + e); |
| } |
| } |
| mQueuedCommands = null; |
| } else if (sDebug) Slog.d(TAG, "no queued commands"); |
| } |
| } |
| |
| @Override |
| @MainThread |
| public void onServiceDisconnected(ComponentName name) { |
| if (sVerbose) Slog.v(TAG, "onServiceDisconnected(): " + name); |
| synchronized (mLock) { |
| mRemoteService = null; |
| } |
| } |
| |
| @Override |
| public void onBindingDied(ComponentName name) { |
| if (sVerbose) Slog.v(TAG, "onBindingDied(): " + name); |
| synchronized (mLock) { |
| mRemoteService = null; |
| } |
| } |
| |
| @Override |
| public void onNullBinding(ComponentName name) { |
| if (sVerbose) Slog.v(TAG, "onNullBinding(): " + name); |
| synchronized (mLock) { |
| mRemoteService = null; |
| } |
| } |
| }; |
| |
| final ComponentName component = getServiceComponentName(); |
| if (sVerbose) Slog.v(TAG, "binding to: " + component); |
| if (component != null) { |
| final Intent intent = new Intent(); |
| intent.setComponent(component); |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE, |
| UserHandle.of(mUserId)); |
| if (sVerbose) Slog.v(TAG, "bound"); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gets the name of all available algorithms. |
| */ |
| @Nullable |
| String[] getAvailableAlgorithms() { |
| return getMetadataValue(SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS, |
| (res, id) -> res.getStringArray(id)); |
| } |
| |
| /** |
| * Gets the default algorithm that's used when an algorithm is not specified or is invalid. |
| */ |
| @Nullable |
| String getDefaultAlgorithm() { |
| return getMetadataValue(SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM, (res, id) -> res.getString(id)); |
| } |
| |
| @Nullable |
| private <T> T getMetadataValue(String field, MetadataParser<T> parser) { |
| final ServiceInfo serviceInfo = getServiceInfo(); |
| if (serviceInfo == null) return null; |
| |
| final PackageManager pm = mContext.getPackageManager(); |
| |
| final Resources res; |
| try { |
| res = pm.getResourcesForApplication(serviceInfo.applicationInfo); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.e(TAG, "Error getting application resources for " + serviceInfo, e); |
| return null; |
| } |
| |
| final int resourceId = serviceInfo.metaData.getInt(field); |
| return parser.get(res, resourceId); |
| } |
| |
| //TODO(b/70291841): rename this method (and all others in the chain) to something like |
| // calculateScores() ? |
| void getScores(RemoteCallback callback, @Nullable String algorithmName, |
| @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues, |
| @NonNull String[] userDataValues) { |
| connectAndRun((service) -> service.getScores(callback, algorithmName, |
| algorithmArgs, actualValues, userDataValues)); |
| } |
| |
| void dump(String prefix, PrintWriter pw) { |
| final ComponentName impl = getServiceComponentName(); |
| pw.print(prefix); pw.print("User ID: "); pw.println(mUserId); |
| pw.print(prefix); pw.print("Queued commands: "); |
| if (mQueuedCommands == null) { |
| pw.println("N/A"); |
| } else { |
| pw.println(mQueuedCommands.size()); |
| } |
| pw.print(prefix); pw.print("Implementation: "); |
| if (impl == null) { |
| pw.println("N/A"); |
| return; |
| } |
| pw.println(impl.flattenToShortString()); |
| |
| try { |
| pw.print(prefix); pw.print("Available algorithms: "); |
| pw.println(Arrays.toString(getAvailableAlgorithms())); |
| pw.print(prefix); pw.print("Default algorithm: "); pw.println(getDefaultAlgorithm()); |
| } catch (Exception e) { |
| pw.print("ERROR CALLING SERVICE: " ); pw.println(e); |
| } |
| } |
| |
| private static interface Command { |
| void run(IAutofillFieldClassificationService service) throws RemoteException; |
| } |
| |
| private static interface MetadataParser<T> { |
| T get(Resources res, int resId); |
| } |
| } |