Merge "Implemented getAlgorithm() and getDefaultAlgorithm() using manifest metadata."
diff --git a/api/system-current.txt b/api/system-current.txt
index d33b030..49bd885 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3875,8 +3875,6 @@
 
   public abstract class AutofillFieldClassificationService extends android.app.Service {
     method public android.os.IBinder onBind(android.content.Intent);
-    method public java.util.List<java.lang.String> onGetAvailableAlgorithms();
-    method public java.lang.String onGetDefaultAlgorithm();
     method public android.service.autofill.AutofillFieldClassificationService.Scores onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService";
   }
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
index 18f6dab..78e2bd1 100644
--- a/core/java/android/service/autofill/AutofillFieldClassificationService.java
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -15,9 +15,6 @@
  */
 package android.service.autofill;
 
-import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
-import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -58,9 +55,7 @@
 
     private static final String TAG = "AutofillFieldClassificationService";
 
-    private static final int MSG_GET_AVAILABLE_ALGORITHMS = 1;
-    private static final int MSG_GET_DEFAULT_ALGORITHM = 2;
-    private static final int MSG_GET_SCORES = 3;
+    private static final int MSG_GET_SCORES = 1;
 
     /**
      * The {@link Intent} action that must be declared as handled by a service
@@ -79,21 +74,6 @@
         final Bundle data = new Bundle();
         final RemoteCallback callback;
         switch (action) {
-            case MSG_GET_AVAILABLE_ALGORITHMS:
-                callback = (RemoteCallback) msg.obj;
-                final List<String> availableAlgorithms = onGetAvailableAlgorithms();
-                String[] asArray = null;
-                if (availableAlgorithms != null) {
-                    asArray = new String[availableAlgorithms.size()];
-                    availableAlgorithms.toArray(asArray);
-                }
-                data.putStringArray(EXTRA_AVAILABLE_ALGORITHMS, asArray);
-                break;
-            case MSG_GET_DEFAULT_ALGORITHM:
-                callback = (RemoteCallback) msg.obj;
-                final String defaultAlgorithm = onGetDefaultAlgorithm();
-                data.putString(EXTRA_DEFAULT_ALGORITHM, defaultAlgorithm);
-                break;
             case MSG_GET_SCORES:
                 final SomeArgs args = (SomeArgs) msg.obj;
                 callback = (RemoteCallback) args.arg1;
@@ -134,27 +114,6 @@
     }
 
     /**
-     * Gets the name of all available algorithms.
-     *
-     * @throws UnsupportedOperationException if not implemented by service.
-     */
-    // TODO(b/70939974): rename to onGetAvailableAlgorithms if not removed
-    @NonNull
-    public List<String> onGetAvailableAlgorithms() {
-        throw new UnsupportedOperationException("Must be implemented by external service");
-    }
-
-    /**
-     * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
-     *
-     * @throws UnsupportedOperationException if not implemented by service.
-     */
-    @NonNull
-    public String onGetDefaultAlgorithm() {
-        throw new UnsupportedOperationException("Must be implemented by external service");
-    }
-
-    /**
      * Calculates field classification scores in a batch.
      *
      * <p>See {@link AutofillFieldClassificationService} for more info about field classification
@@ -179,17 +138,6 @@
 
     private final class AutofillFieldClassificationServiceWrapper
             extends IAutofillFieldClassificationService.Stub {
-
-        @Override
-        public void getAvailableAlgorithms(RemoteCallback callback) throws RemoteException {
-            mHandlerCaller.obtainMessageO(MSG_GET_AVAILABLE_ALGORITHMS, callback).sendToTarget();
-        }
-
-        @Override
-        public void getDefaultAlgorithm(RemoteCallback callback) throws RemoteException {
-            mHandlerCaller.obtainMessageO(MSG_GET_DEFAULT_ALGORITHM, callback).sendToTarget();
-        }
-
         @Override
         public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
                 List<AutofillValue> actualValues, String[] userDataValues)
diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
index d8e829d..398557d 100644
--- a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
+++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl
@@ -27,8 +27,6 @@
  * @hide
  */
 oneway interface IAutofillFieldClassificationService {
-    void getAvailableAlgorithms(in RemoteCallback callback);
-    void getDefaultAlgorithm(in RemoteCallback callback);
     void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs,
-                  in List<AutofillValue> actualValues, in String[] userDataValues);
+                   in List<AutofillValue> actualValues, in String[] userDataValues);
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f54561b..4b24a71 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -33,7 +33,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
-import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
 import android.service.autofill.FillEventHistory;
@@ -58,8 +57,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 // TODO: use java.lang.ref.Cleaner once Android supports Java 9
 import sun.misc.Cleaner;
@@ -177,11 +174,6 @@
     public static final String EXTRA_RESTORE_SESSION_TOKEN =
             "android.view.autofill.extra.RESTORE_SESSION_TOKEN";
 
-    /** @hide */
-    public static final String EXTRA_AVAILABLE_ALGORITHMS = "available_algorithms";
-    /** @hide */
-    public static final String EXTRA_DEFAULT_ALGORITHM = "default_algorithm";
-
     private static final String SESSION_ID_TAG = "android:sessionId";
     private static final String STATE_TAG = "android:state";
     private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
@@ -1174,22 +1166,10 @@
      * and it's ignored if the caller currently doesn't have an enabled autofill service for
      * the user.
      */
-    // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
-    // the ExtService manifest (instead of calling the service)
     @Nullable
     public String getDefaultFieldClassificationAlgorithm() {
-        final SyncRemoteCallbackListener<String> listener =
-                new SyncRemoteCallbackListener<String>() {
-
-            @Override
-            String getResult(Bundle result) {
-                return result == null ? null : result.getString(EXTRA_DEFAULT_ALGORITHM);
-            }
-        };
-
         try {
-            mService.getDefaultFieldClassificationAlgorithm(new RemoteCallback(listener));
-            return listener.getResult(FC_SERVICE_TIMEOUT);
+            return mService.getDefaultFieldClassificationAlgorithm();
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -1204,29 +1184,12 @@
      * and it returns an empty list if the caller currently doesn't have an enabled autofill service
      * for the user.
      */
-    // TODO(b/70939974): refactor this method to be "purely" sync by getting the info from the
-    // the ExtService manifest (instead of calling the service)
     @NonNull
     public List<String> getAvailableFieldClassificationAlgorithms() {
-        final SyncRemoteCallbackListener<List<String>> listener =
-                new SyncRemoteCallbackListener<List<String>>() {
-
-            @Override
-            List<String> getResult(Bundle result) {
-                List<String> algorithms = null;
-                if (result != null) {
-                    final String[] asArray = result.getStringArray(EXTRA_AVAILABLE_ALGORITHMS);
-                    if (asArray != null) {
-                        algorithms = Arrays.asList(asArray);
-                    }
-                }
-                return algorithms != null ? algorithms : Collections.emptyList();
-            }
-        };
-
+        final String[] algorithms;
         try {
-            mService.getAvailableFieldClassificationAlgorithms(new RemoteCallback(listener));
-            return listener.getResult(FC_SERVICE_TIMEOUT);
+            algorithms = mService.getAvailableFieldClassificationAlgorithms();
+            return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return null;
@@ -2322,36 +2285,4 @@
             }
         }
     }
-
-    private abstract static class SyncRemoteCallbackListener<T>
-            implements RemoteCallback.OnResultListener {
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-        private T mResult;
-
-        @Override
-        public void onResult(Bundle result) {
-            if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener.onResult(): " + result);
-            mResult = getResult(result);
-            mLatch.countDown();
-        }
-
-        T getResult(int timeoutMs) {
-            T result = null;
-            try {
-                if (mLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
-                    result = mResult;
-                } else {
-                    Log.w(TAG, "SyncRemoteCallbackListener not called in " + timeoutMs + "ms");
-                }
-            } catch (InterruptedException e) {
-                Log.w(TAG, "SyncRemoteCallbackListener interrupted: " + e);
-                Thread.currentThread().interrupt();
-            }
-            if (sVerbose) Log.w(TAG, "SyncRemoteCallbackListener: returning " + result);
-            return result;
-        }
-
-        abstract T getResult(Bundle result);
-    }
 }
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 41672e7..1a11fbb 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -59,6 +59,6 @@
     void setUserData(in UserData userData);
     boolean isFieldClassificationEnabled();
     ComponentName getAutofillServiceComponentName();
-    void getAvailableFieldClassificationAlgorithms(in RemoteCallback callback);
-    void getDefaultFieldClassificationAlgorithm(in RemoteCallback callback);
+    String[] getAvailableFieldClassificationAlgorithms();
+    String getDefaultFieldClassificationAlgorithm();
 }
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index 63d3623..45e557c 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -56,6 +56,12 @@
             <intent-filter>
                 <action android:name="android.service.autofill.AutofillFieldClassificationService" />
             </intent-filter>
+            <meta-data
+                android:name="android.autofill.field_classification.default_algorithm"
+                android:resource="@string/autofill_field_classification_default_algorithm" />
+            <meta-data
+                android:name="android.autofill.field_classification.available_algorithms"
+                android:resource="@array/autofill_field_classification_available_algorithms" />
         </service>
 
         <library android:name="android.ext.services"/>
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
index a2e65bc..72647ab 100644
--- a/packages/ExtServices/res/values/strings.xml
+++ b/packages/ExtServices/res/values/strings.xml
@@ -19,4 +19,9 @@
 
     <string name="notification_assistant">Notification Assistant</string>
     <string name="prompt_block_reason">Too many dismissals:views</string>
+
+    <string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string>
+    <string-array name="autofill_field_classification_available_algorithms">
+        <item>EDIT_DISTANCE</item>
+    </string-array>
 </resources>
diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
index ea516a1..068ff8b 100644
--- a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
+++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java
@@ -24,24 +24,13 @@
 
 import com.android.internal.util.ArrayUtils;
 
-import java.util.Arrays;
 import java.util.List;
 
 public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService {
 
     private static final String TAG = "AutofillFieldClassificationServiceImpl";
-    private static final boolean DEBUG = false;
-    private static final List<String> sAvailableAlgorithms = Arrays.asList(EditDistanceScorer.NAME);
-
-    @Override
-    public List<String> onGetAvailableAlgorithms() {
-        return sAvailableAlgorithms;
-    }
-
-    @Override
-    public String onGetDefaultAlgorithm() {
-        return EditDistanceScorer.NAME;
-    }
+    // TODO(b/70291841): set to false before launching
+    private static final boolean DEBUG = true;
 
     @Nullable
     @Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6d845f9..c062800 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -646,37 +646,35 @@
         }
 
         @Override
-        public void getDefaultFieldClassificationAlgorithm(RemoteCallback callback)
-                throws RemoteException {
+        public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.getDefaultFieldClassificationAlgorithm(getCallingUid(), callback);
+                    return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
                 } else {
                     if (sVerbose) {
                         Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
                     }
-                    callback.sendResult(null);
-                }
+                    return null;
+               }
             }
         }
 
         @Override
-        public void getAvailableFieldClassificationAlgorithms(RemoteCallback callback)
-                throws RemoteException {
+        public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException {
             final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.getAvailableFieldClassificationAlgorithms(getCallingUid(), callback);
+                    return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
                 } else {
                     if (sVerbose) {
                         Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
                     }
-                    callback.sendResult(null);
+                    return null;
                 }
             }
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index a5bd59a9..6bcfc4b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1153,22 +1153,22 @@
         return mFieldClassificationStrategy;
     }
 
-    void getAvailableFieldClassificationAlgorithms(int callingUid, RemoteCallback callback) {
+    String[] getAvailableFieldClassificationAlgorithms(int callingUid) {
         synchronized (mLock) {
             if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) {
-                return;
+                return null;
             }
         }
-        mFieldClassificationStrategy.getAvailableAlgorithms(callback);
+        return mFieldClassificationStrategy.getAvailableAlgorithms();
     }
 
-    void getDefaultFieldClassificationAlgorithm(int callingUid, RemoteCallback callback) {
+    String getDefaultFieldClassificationAlgorithm(int callingUid) {
         synchronized (mLock) {
             if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) {
-                return;
+                return null;
             }
         }
-        mFieldClassificationStrategy.getDefaultAlgorithm(callback);
+        return mFieldClassificationStrategy.getDefaultAlgorithm();
     }
 
     @Override
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
index 7228f1d..594032a 100644
--- a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.autofill;
 
-import static android.view.autofill.AutofillManager.EXTRA_AVAILABLE_ALGORITHMS;
-import static android.view.autofill.AutofillManager.EXTRA_DEFAULT_ALGORITHM;
 import static android.view.autofill.AutofillManager.FC_SERVICE_TIMEOUT;
 
 import static com.android.server.autofill.Helper.sDebug;
@@ -33,6 +31,7 @@
 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;
@@ -49,6 +48,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -62,6 +62,11 @@
 
     private static final String TAG = "FieldClassificationStrategy";
 
+    private static final String METADATA_KEY_DEFAULT_ALGORITHM =
+            "android.autofill.field_classification.default_algorithm";
+    private static final String METADATA_KEY_AVAILABLE_ALGORITHMS =
+            "android.autofill.field_classification.available_algorithms";
+
     private final Context mContext;
     private final Object mLock = new Object();
     private final int mUserId;
@@ -80,7 +85,8 @@
         mUserId = userId;
     }
 
-    private ComponentName getServiceComponentName() {
+    @Nullable
+    private ServiceInfo getServiceInfo() {
         final String packageName =
                 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
         if (packageName == null) {
@@ -96,9 +102,15 @@
             Slog.w(TAG, "No valid components found.");
             return null;
         }
-        final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        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 "
@@ -204,12 +216,40 @@
         }
     }
 
-    void getAvailableAlgorithms(RemoteCallback callback) {
-        connectAndRun((service) -> service.getAvailableAlgorithms(callback));
+    /**
+     * Gets the name of all available algorithms.
+     */
+    @Nullable
+    String[] getAvailableAlgorithms() {
+        return getMetadataValue(METADATA_KEY_AVAILABLE_ALGORITHMS,
+                (res, id) -> res.getStringArray(id));
     }
 
-    void getDefaultAlgorithm(RemoteCallback callback) {
-        connectAndRun((service) -> service.getDefaultAlgorithm(callback));
+    /**
+     * Gets the default algorithm that's used when an algorithm is not specified or is invalid.
+     */
+    @Nullable
+    String getDefaultAlgorithm() {
+        return getMetadataValue(METADATA_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
@@ -237,43 +277,16 @@
         }
         pw.println(impl.flattenToShortString());
 
-        final CountDownLatch latch = new CountDownLatch(2);
-
-        // Lock used to make sure lines don't overlap
-        final Object lock = latch;
-
-        connectAndRun((service) -> service.getAvailableAlgorithms(new RemoteCallback((bundle) -> {
-            synchronized (lock) {
-                pw.print(prefix); pw.print("Available algorithms: ");
-                pw.println(bundle.getStringArrayList(EXTRA_AVAILABLE_ALGORITHMS));
-            }
-            latch.countDown();
-        })));
-
-        connectAndRun((service) -> service.getDefaultAlgorithm(new RemoteCallback((bundle) -> {
-            synchronized (lock) {
-                pw.print(prefix); pw.print("Default algorithm: ");
-                pw.println(bundle.getString(EXTRA_DEFAULT_ALGORITHM));
-            }
-            latch.countDown();
-        })));
-
-        try {
-            if (!latch.await(FC_SERVICE_TIMEOUT, TimeUnit.MILLISECONDS)) {
-                synchronized (lock) {
-                    pw.print(prefix); pw.print("timeout ("); pw.print(FC_SERVICE_TIMEOUT);
-                    pw.println("ms) waiting for service");
-                }
-            }
-        } catch (InterruptedException e) {
-            synchronized (lock) {
-                pw.print(prefix); pw.println("interrupted while waiting for service");
-            }
-            Thread.currentThread().interrupt();
-        }
+        pw.print(prefix); pw.print("Available algorithms: ");
+        pw.println(Arrays.toString(getAvailableAlgorithms()));
+        pw.print(prefix); pw.print("Default algorithm: "); pw.println(getDefaultAlgorithm());
     }
 
-    private interface Command {
+    private static interface Command {
         void run(IAutofillFieldClassificationService service) throws RemoteException;
     }
+
+    private static interface MetadataParser<T> {
+        T get(Resources res, int resId);
+    }
 }