import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
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;
final AutofillClient client = getClient();
if (client != null) {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- final boolean sessionWasRestored = mService.restoreSession(mSessionId,
- client.autofillClientGetActivityToken(),
- mServiceClient.asBinder());
+ mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
+ mServiceClient.asBinder(), receiver);
+ final boolean sessionWasRestored = receiver.getIntResult() == 1;
if (!sessionWasRestored) {
Log.w(TAG, "Session " + mSessionId + " could not be restored");
*/
@Nullable public FillEventHistory getFillEventHistory() {
try {
- return mService.getFillEventHistory();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getFillEventHistory(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
public boolean hasEnabledAutofillServices() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
+ mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
public ComponentName getAutofillServiceComponentName() {
if (mService == null) return null;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getAutofillServiceComponentName();
+ mService.getAutofillServiceComponentName(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
*/
@Nullable public String getUserDataId() {
try {
- return mService.getUserDataId();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserDataId(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
*/
@Nullable public UserData getUserData() {
try {
- return mService.getUserData();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserData(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
* the user.
*/
public boolean isFieldClassificationEnabled() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isFieldClassificationEnabled();
+ mService.isFieldClassificationEnabled(receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return false;
*/
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getDefaultFieldClassificationAlgorithm();
+ mService.getDefaultFieldClassificationAlgorithm(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
*/
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
- final String[] algorithms;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- algorithms = mService.getAvailableFieldClassificationAlgorithms();
+ mService.getAvailableFieldClassificationAlgorithms(receiver);
+ final String[] algorithms = receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
public boolean isAutofillSupported() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceSupported(mContext.getUserId());
+ mService.isServiceSupported(mContext.getUserId(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
final AutofillClient client = getClient();
if (client == null) return; // NOTE: getClient() already logged it..
- mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, client.autofillClientGetComponentName(),
- isCompatibilityModeEnabledLocked());
+ isCompatibilityModeEnabledLocked(), receiver);
+ mSessionId = receiver.getIntResult();
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
mServiceClient = new AutofillManagerClient(this);
try {
final int userId = mContext.getUserId();
- final int flags = mService.addClient(mServiceClient, userId);
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.addClient(mServiceClient, userId, receiver);
+ final int flags = receiver.getIntResult();
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
}
}
}
+
+ /**
+ * @hide
+ */
+ public static final class SyncResultReceiver extends IResultReceiver.Stub {
+
+ private static final String EXTRA = "EXTRA";
+
+ /**
+ * How long to block waiting for {@link IResultReceiver} callbacks when calling server.
+ */
+ private static final long BINDER_TIMEOUT_MS = 5000;
+
+ private static final int TYPE_STRING = 0;
+ private static final int TYPE_STRING_ARRAY = 1;
+ private static final int TYPE_PARCELABLE = 2;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private int mResult;
+ private Bundle mBundle;
+
+ private void waitResult() {
+ try {
+ if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code int}.
+ */
+ int getIntResult() {
+ waitResult();
+ return mResult;
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code Object}.
+ *
+ * @param type type of expected object.
+ */
+ @Nullable
+ @SuppressWarnings("unchecked")
+ <T> T getObjectResult(int type) {
+ waitResult();
+ if (mBundle == null) {
+ return null;
+ }
+ switch (type) {
+ case TYPE_STRING:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_STRING_ARRAY:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_PARCELABLE:
+ return (T) mBundle.getParcelable(EXTRA);
+ default:
+ throw new IllegalArgumentException("unsupported type: " + type);
+ }
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ mResult = resultCode;
+ mBundle = resultData;
+ mLatch.countDown();
+ }
+
+ /**
+ * Creates a bundle for a {@code String} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String value) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code String[]} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String[] value) {
+ final Bundle bundle = new Bundle();
+ bundle.putStringArray(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code Parcelable} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable Parcelable value) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA, value);
+ return bundle;
+ }
+ }
}
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
+import com.android.internal.os.IResultReceiver;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
*
* {@hide}
*/
- // TODO(b/73536867) STOPSHIP : this whole interface should be either oneway or not, and we're
- // gradually converting the methods (as some of them return a value form the server and must be
- // refactored).
-interface IAutoFillManager {
+oneway interface IAutoFillManager {
// Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
- int addClient(in IAutoFillManagerClient client, int userId);
+ void addClient(in IAutoFillManagerClient client, int userId, in IResultReceiver result);
void removeClient(in IAutoFillManagerClient client, int userId);
- int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
- in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
- in ComponentName componentName, boolean compatMode);
- FillEventHistory getFillEventHistory();
- boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
- oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds,
- in AutofillValue value, int action, int flags, int userId);
- oneway void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
- oneway void finishSession(int sessionId, int userId);
- oneway void cancelSession(int sessionId, int userId);
- oneway void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId,
- int userId);
- oneway void setHasCallback(int sessionId, int userId, boolean hasIt);
+ void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
+ in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
+ in ComponentName componentName, boolean compatMode, in IResultReceiver result);
+ void getFillEventHistory(in IResultReceiver result);
+ void restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback,
+ in IResultReceiver result);
+ void updateSession(int sessionId, in AutofillId id, in Rect bounds,
+ in AutofillValue value, int action, int flags, int userId);
+ void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void finishSession(int sessionId, int userId);
+ void cancelSession(int sessionId, int userId);
+ void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
+ void setHasCallback(int sessionId, int userId, boolean hasIt);
void disableOwnedAutofillServices(int userId);
- boolean isServiceSupported(int userId);
- boolean isServiceEnabled(int userId, String packageName);
+ void isServiceSupported(int userId, in IResultReceiver result);
+ void isServiceEnabled(int userId, String packageName, in IResultReceiver result);
void onPendingSaveUi(int operation, IBinder token);
- UserData getUserData();
- String getUserDataId();
+ void getUserData(in IResultReceiver result);
+ void getUserDataId(in IResultReceiver result);
void setUserData(in UserData userData);
- boolean isFieldClassificationEnabled();
- ComponentName getAutofillServiceComponentName();
- String[] getAvailableFieldClassificationAlgorithms();
- String getDefaultFieldClassificationAlgorithm();
+ void isFieldClassificationEnabled(in IResultReceiver result);
+ void getAutofillServiceComponentName(in IResultReceiver result);
+ void getAvailableFieldClassificationAlgorithms(in IResultReceiver result);
+ void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
}
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
}
+ private void send(@NonNull IResultReceiver receiver, int value) {
+ try {
+ receiver.send(value, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) {
+ try {
+ receiver.send(0, value);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, boolean value) {
+ send(receiver, value ? 1 : 0);
+ }
+
@Nullable
@VisibleForTesting
static Map<String, String[]> getWhitelistedCompatModePackages(String setting) {
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
- public int addClient(IAutoFillManagerClient client, int userId) {
+ public void addClient(IAutoFillManagerClient client, int userId,
+ @NonNull IResultReceiver receiver) {
+ int flags = 0;
synchronized (mLock) {
- int flags = 0;
if (getServiceForUserLocked(userId).addClientLocked(client)) {
flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED;
}
if (sVerbose) {
flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
}
- return flags;
}
+ send(receiver, flags);
}
@Override
}
@Override
- public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
+ public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
- ComponentName componentName, boolean compatMode) {
+ ComponentName componentName, boolean compatMode, IResultReceiver receiver) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
throw new IllegalArgumentException(packageName + " is not a valid package", e);
}
+ final int sessionId;
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
- return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
+ sessionId = service.startSessionLocked(activityToken, getCallingUid(), appCallback,
autofillId, bounds, value, hasCallback, componentName, compatMode,
mAllowInstantService, flags);
}
+ send(receiver, sessionId);
}
@Override
- public FillEventHistory getFillEventHistory() throws RemoteException {
+ public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ FillEventHistory fillEventHistory = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(getCallingUid());
+ fillEventHistory = service.getFillEventHistory(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, fillEventHistory);
}
@Override
- public UserData getUserData() throws RemoteException {
+ public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(getCallingUid());
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserData(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, userData);
}
@Override
- public String getUserDataId() throws RemoteException {
+ public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- final UserData userData = service.getUserData(getCallingUid());
- return userData == null ? null : userData.getId();
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserDataId(): no service for " + userId);
}
}
-
- return null;
+ final String userDataId = userData == null ? null : userData.getId();
+ send(receiver, userDataId);
}
@Override
}
@Override
- public boolean isFieldClassificationEnabled() throws RemoteException {
+ public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled(getCallingUid());
+ enabled = service.isFieldClassificationEnabled(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, enabled);
}
@Override
- public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+ public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String algorithm = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithm);
}
@Override
- public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException {
+ public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String[] algorithms = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+ algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithms);
}
@Override
- public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ ComponentName componentName = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getServiceComponentName();
+ componentName = service.getServiceComponentName();
} else if (sVerbose) {
Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, componentName);
}
@Override
- public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
+ public void restoreSession(int sessionId, @NonNull IBinder activityToken,
+ @NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
throws RemoteException {
final int userId = UserHandle.getCallingUserId();
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
+ boolean restored = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
- return service.restoreSession(sessionId, getCallingUid(), activityToken,
+ restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
} else if (sVerbose) {
Slog.v(TAG, "restoreSession(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, restored);
}
@Override
}
@Override
- public boolean isServiceSupported(int userId) {
+ public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
+ boolean supported = false;
synchronized (mLock) {
- return !mDisabledUsers.get(userId);
+ supported = !mDisabledUsers.get(userId);
}
+ send(receiver, supported);
}
@Override
- public boolean isServiceEnabled(int userId, String packageName) {
+ public void isServiceEnabled(int userId, @NonNull String packageName,
+ @NonNull IResultReceiver receiver) {
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return Objects.equals(packageName, service.getServicePackageName());
+ enabled = Objects.equals(packageName, service.getServicePackageName());
} else if (sVerbose) {
Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
}
- return false;
}
+ send(receiver, enabled);
}
@Override