Merge "Scrollable inline keyboard suggestions"
This commit is contained in:
committed by
Android (Google) Code Review
commit
2dccbf2d60
@@ -31,7 +31,6 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
@@ -93,7 +92,7 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
// Used for metrics / debug only
|
||||
private ComponentName mServiceComponentName;
|
||||
|
||||
private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() {
|
||||
private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub {
|
||||
|
||||
@Override
|
||||
public void onConnected(boolean debug, boolean verbose) {
|
||||
@@ -137,7 +136,7 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
public final IBinder onBind(Intent intent) {
|
||||
mServiceComponentName = intent.getComponent();
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
return mInterface.asBinder();
|
||||
return new AugmentedAutofillServiceImpl();
|
||||
}
|
||||
Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
|
||||
return null;
|
||||
@@ -352,11 +351,13 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
static final int REPORT_EVENT_NO_RESPONSE = 1;
|
||||
static final int REPORT_EVENT_UI_SHOWN = 2;
|
||||
static final int REPORT_EVENT_UI_DESTROYED = 3;
|
||||
static final int REPORT_EVENT_INLINE_RESPONSE = 4;
|
||||
|
||||
@IntDef(prefix = { "REPORT_EVENT_" }, value = {
|
||||
REPORT_EVENT_NO_RESPONSE,
|
||||
REPORT_EVENT_UI_SHOWN,
|
||||
REPORT_EVENT_UI_DESTROYED
|
||||
REPORT_EVENT_UI_DESTROYED,
|
||||
REPORT_EVENT_INLINE_RESPONSE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ReportEvent{}
|
||||
@@ -365,8 +366,8 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
private final Object mLock = new Object();
|
||||
private final IAugmentedAutofillManagerClient mClient;
|
||||
private final int mSessionId;
|
||||
public final int taskId;
|
||||
public final ComponentName componentName;
|
||||
public final int mTaskId;
|
||||
public final ComponentName mComponentName;
|
||||
// Used for metrics / debug only
|
||||
private String mServicePackageName;
|
||||
@GuardedBy("mLock")
|
||||
@@ -406,8 +407,8 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
mSessionId = sessionId;
|
||||
mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
|
||||
mCallback = callback;
|
||||
this.taskId = taskId;
|
||||
this.componentName = componentName;
|
||||
mTaskId = taskId;
|
||||
mComponentName = componentName;
|
||||
mServicePackageName = serviceComponentName.getPackageName();
|
||||
mFocusedId = focusedId;
|
||||
mFocusedValue = focusedValue;
|
||||
@@ -514,22 +515,24 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData,
|
||||
@Nullable Bundle clientState) {
|
||||
void reportResult(@Nullable List<Dataset> inlineSuggestionsData) {
|
||||
try {
|
||||
mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState);
|
||||
final Dataset[] inlineSuggestions = (inlineSuggestionsData != null)
|
||||
? inlineSuggestionsData.toArray(new Dataset[inlineSuggestionsData.size()])
|
||||
: null;
|
||||
mCallback.onSuccess(inlineSuggestions);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Used (mostly) for metrics.
|
||||
public void report(@ReportEvent int event) {
|
||||
if (sVerbose) Log.v(TAG, "report(): " + event);
|
||||
void logEvent(@ReportEvent int event) {
|
||||
if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event);
|
||||
long duration = -1;
|
||||
int type = MetricsEvent.TYPE_UNKNOWN;
|
||||
|
||||
switch (event) {
|
||||
case REPORT_EVENT_NO_RESPONSE:
|
||||
case REPORT_EVENT_NO_RESPONSE: {
|
||||
type = MetricsEvent.TYPE_SUCCESS;
|
||||
if (mFirstOnSuccessTime == 0) {
|
||||
mFirstOnSuccessTime = SystemClock.elapsedRealtime();
|
||||
@@ -538,40 +541,49 @@ public abstract class AugmentedAutofillService extends Service {
|
||||
Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
|
||||
}
|
||||
}
|
||||
try {
|
||||
mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/
|
||||
null);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error reporting success: " + e);
|
||||
} break;
|
||||
|
||||
case REPORT_EVENT_INLINE_RESPONSE: {
|
||||
// TODO: Define a constant and log this event
|
||||
// type = MetricsEvent.TYPE_SUCCESS_INLINE;
|
||||
if (mFirstOnSuccessTime == 0) {
|
||||
mFirstOnSuccessTime = SystemClock.elapsedRealtime();
|
||||
duration = mFirstOnSuccessTime - mFirstRequestTime;
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REPORT_EVENT_UI_SHOWN:
|
||||
} break;
|
||||
|
||||
case REPORT_EVENT_UI_SHOWN: {
|
||||
type = MetricsEvent.TYPE_OPEN;
|
||||
if (mUiFirstShownTime == 0) {
|
||||
mUiFirstShownTime = SystemClock.elapsedRealtime();
|
||||
duration = mUiFirstShownTime - mFirstRequestTime;
|
||||
if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration));
|
||||
}
|
||||
break;
|
||||
case REPORT_EVENT_UI_DESTROYED:
|
||||
} break;
|
||||
|
||||
case REPORT_EVENT_UI_DESTROYED: {
|
||||
type = MetricsEvent.TYPE_CLOSE;
|
||||
if (mUiFirstDestroyedTime == 0) {
|
||||
mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
|
||||
duration = mUiFirstDestroyedTime - mFirstRequestTime;
|
||||
duration = mUiFirstDestroyedTime - mFirstRequestTime;
|
||||
if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration));
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
default:
|
||||
Log.w(TAG, "invalid event reported: " + event);
|
||||
}
|
||||
logResponse(type, mServicePackageName, componentName, mSessionId, duration);
|
||||
logResponse(type, mServicePackageName, mComponentName, mSessionId, duration);
|
||||
}
|
||||
|
||||
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
|
||||
pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
|
||||
pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
|
||||
pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId);
|
||||
pw.print(prefix); pw.print("component: ");
|
||||
pw.println(componentName.flattenToShortString());
|
||||
pw.println(mComponentName.flattenToShortString());
|
||||
pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
|
||||
if (mFocusedValue != null) {
|
||||
pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
|
||||
|
||||
@@ -54,13 +54,15 @@ public final class FillCallback {
|
||||
if (sDebug) Log.d(TAG, "onSuccess(): " + response);
|
||||
|
||||
if (response == null) {
|
||||
mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
|
||||
mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
|
||||
mProxy.reportResult(null /*inlineSuggestions*/);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Dataset> inlineSuggestions = response.getInlineSuggestions();
|
||||
if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
|
||||
mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState());
|
||||
mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
|
||||
mProxy.reportResult(inlineSuggestions);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,12 +62,13 @@ public final class FillController {
|
||||
|
||||
try {
|
||||
mProxy.autofill(values);
|
||||
final FillWindow fillWindow = mProxy.getFillWindow();
|
||||
if (fillWindow != null) {
|
||||
fillWindow.destroy();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
|
||||
final FillWindow fillWindow = mProxy.getFillWindow();
|
||||
if (fillWindow != null) {
|
||||
fillWindow.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class FillRequest {
|
||||
* Gets the task of the activity associated with this request.
|
||||
*/
|
||||
public int getTaskId() {
|
||||
return mProxy.taskId;
|
||||
return mProxy.mTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,7 @@ public final class FillRequest {
|
||||
*/
|
||||
@NonNull
|
||||
public ComponentName getActivityComponent() {
|
||||
return mProxy.componentName;
|
||||
return mProxy.mComponentName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.sVerbo
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.graphics.Rect;
|
||||
@@ -41,6 +42,7 @@ import com.android.internal.util.Preconditions;
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Handle to a window used to display the augmented autofill UI.
|
||||
@@ -70,23 +72,22 @@ public final class FillWindow implements AutoCloseable {
|
||||
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||||
|
||||
private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||
private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private WindowManager mWm;
|
||||
private @NonNull WindowManager mWm;
|
||||
@GuardedBy("mLock")
|
||||
private View mFillView;
|
||||
@GuardedBy("mLock")
|
||||
private boolean mShowing;
|
||||
@GuardedBy("mLock")
|
||||
private Rect mBounds;
|
||||
private @Nullable Rect mBounds;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mUpdateCalled;
|
||||
@GuardedBy("mLock")
|
||||
private boolean mDestroyed;
|
||||
|
||||
private AutofillProxy mProxy;
|
||||
private @NonNull AutofillProxy mProxy;
|
||||
|
||||
/**
|
||||
* Updates the content of the window.
|
||||
@@ -172,11 +173,11 @@ public final class FillWindow implements AutoCloseable {
|
||||
try {
|
||||
mProxy.requestShowFillUi(mBounds.right - mBounds.left,
|
||||
mBounds.bottom - mBounds.top,
|
||||
/*anchorBounds=*/ null, mFillWindowPresenter);
|
||||
/*anchorBounds=*/ null, new FillWindowPresenter(this));
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error requesting to show fill window", e);
|
||||
}
|
||||
mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
|
||||
mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +245,7 @@ public final class FillWindow implements AutoCloseable {
|
||||
if (mUpdateCalled) {
|
||||
mFillView.setOnClickListener(null);
|
||||
hide();
|
||||
mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
|
||||
mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
|
||||
}
|
||||
mDestroyed = true;
|
||||
mCloseGuard.close();
|
||||
@@ -254,9 +255,7 @@ public final class FillWindow implements AutoCloseable {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
mCloseGuard.warnIfOpen();
|
||||
destroy();
|
||||
} finally {
|
||||
super.finalize();
|
||||
@@ -289,22 +288,36 @@ public final class FillWindow implements AutoCloseable {
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
public void close() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
|
||||
private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
|
||||
private final @NonNull WeakReference<FillWindow> mFillWindowReference;
|
||||
|
||||
FillWindowPresenter(@NonNull FillWindow fillWindow) {
|
||||
mFillWindowReference = new WeakReference<>(fillWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
|
||||
boolean fitsSystemWindows, int layoutDirection) {
|
||||
if (sDebug) Log.d(TAG, "FillWindowPresenter.show()");
|
||||
mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
|
||||
final FillWindow fillWindow = mFillWindowReference.get();
|
||||
if (fillWindow != null) {
|
||||
fillWindow.mUiThreadHandler.sendMessage(
|
||||
obtainMessage(FillWindow::handleShow, fillWindow, p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide(Rect transitionEpicenter) {
|
||||
if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()");
|
||||
mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
|
||||
final FillWindow fillWindow = mFillWindowReference.get();
|
||||
if (fillWindow != null) {
|
||||
fillWindow.mUiThreadHandler.sendMessage(
|
||||
obtainMessage(FillWindow::handleHide, fillWindow));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.service.autofill.Dataset;
|
||||
*/
|
||||
interface IFillCallback {
|
||||
void onCancellable(in ICancellationSignal cancellation);
|
||||
void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState);
|
||||
void onSuccess(in @nullable Dataset[] inlineSuggestionsData);
|
||||
boolean isCompleted();
|
||||
void cancel();
|
||||
}
|
||||
|
||||
@@ -818,27 +818,26 @@ final class AutofillManagerServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
|
||||
@Nullable Bundle clientState) {
|
||||
void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) {
|
||||
synchronized (mLock) {
|
||||
if (mAugmentedAutofillEventHistory == null
|
||||
|| mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
|
||||
return;
|
||||
}
|
||||
mAugmentedAutofillEventHistory.addEvent(
|
||||
new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
|
||||
new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null,
|
||||
null, null, null, null, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) {
|
||||
void logAugmentedAutofillShown(int sessionId) {
|
||||
synchronized (mLock) {
|
||||
if (mAugmentedAutofillEventHistory == null
|
||||
|| mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
|
||||
return;
|
||||
}
|
||||
mAugmentedAutofillEventHistory.addEvent(
|
||||
new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
|
||||
new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null,
|
||||
null, null, null, null, null));
|
||||
|
||||
}
|
||||
@@ -1227,16 +1226,15 @@ final class AutofillManagerServiceImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logAugmentedAutofillShown(int sessionId, Bundle clientState) {
|
||||
AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId,
|
||||
clientState);
|
||||
public void logAugmentedAutofillShown(int sessionId) {
|
||||
AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logAugmentedAutofillSelected(int sessionId, String suggestionId,
|
||||
Bundle clientState) {
|
||||
public void logAugmentedAutofillSelected(int sessionId,
|
||||
String suggestionId) {
|
||||
AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId,
|
||||
suggestionId, clientState);
|
||||
suggestionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
||||
import com.android.server.autofill.ui.InlineSuggestionFactory;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -144,7 +145,8 @@ final class RemoteAugmentedAutofillService
|
||||
int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
|
||||
@Nullable AutofillValue focusedValue,
|
||||
@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
|
||||
@Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) {
|
||||
@Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
|
||||
@NonNull Runnable onErrorCallback) {
|
||||
long requestTime = SystemClock.elapsedRealtime();
|
||||
AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
|
||||
|
||||
@@ -161,12 +163,12 @@ final class RemoteAugmentedAutofillService
|
||||
focusedId, focusedValue, requestTime, inlineSuggestionsRequest,
|
||||
new IFillCallback.Stub() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Dataset[] inlineSuggestionsData,
|
||||
@Nullable Bundle clientState) {
|
||||
public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) {
|
||||
mCallbacks.resetLastResponse();
|
||||
maybeRequestShowInlineSuggestions(sessionId,
|
||||
inlineSuggestionsData, focusedId,
|
||||
inlineSuggestionsCallback, client, clientState);
|
||||
inlineSuggestionsCallback, client,
|
||||
onErrorCallback);
|
||||
requestAutofill.complete(null);
|
||||
}
|
||||
|
||||
@@ -231,29 +233,31 @@ final class RemoteAugmentedAutofillService
|
||||
private void maybeRequestShowInlineSuggestions(int sessionId,
|
||||
@Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
|
||||
@Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
|
||||
@NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) {
|
||||
@NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
|
||||
if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
|
||||
return;
|
||||
}
|
||||
mCallbacks.setLastResponse(sessionId);
|
||||
|
||||
try {
|
||||
inlineSuggestionsCallback.onInlineSuggestionsResponse(
|
||||
InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
|
||||
inlineSuggestionsData, focusedId, mContext,
|
||||
dataset -> {
|
||||
mCallbacks.logAugmentedAutofillSelected(sessionId,
|
||||
dataset.getId(), clientState);
|
||||
dataset.getId());
|
||||
try {
|
||||
client.autofill(sessionId, dataset.getFieldIds(),
|
||||
dataset.getFieldValues());
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Encounter exception autofilling the values");
|
||||
}
|
||||
}));
|
||||
}, onErrorCallback));
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
|
||||
}
|
||||
mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
|
||||
|
||||
mCallbacks.logAugmentedAutofillShown(sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,9 +279,8 @@ final class RemoteAugmentedAutofillService
|
||||
|
||||
void setLastResponse(int sessionId);
|
||||
|
||||
void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
|
||||
void logAugmentedAutofillShown(int sessionId);
|
||||
|
||||
void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
|
||||
@Nullable Bundle clientState);
|
||||
void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.view.IInlineSuggestionsRequestCallback;
|
||||
import com.android.internal.view.IInlineSuggestionsResponseCallback;
|
||||
import com.android.server.autofill.ui.AutoFillUI;
|
||||
import com.android.server.autofill.ui.InlineSuggestionFactory;
|
||||
import com.android.server.autofill.ui.PendingUi;
|
||||
import com.android.server.inputmethod.InputMethodManagerInternal;
|
||||
|
||||
@@ -2681,7 +2682,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getUiForShowing().showFillUi(filledId, response, filterText,
|
||||
mService.getServicePackageName(), mComponentName,
|
||||
serviceLabel, serviceIcon, this, id, mCompatMode);
|
||||
@@ -2733,7 +2733,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
InlineSuggestionsResponse inlineSuggestionsResponse =
|
||||
InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(),
|
||||
datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this);
|
||||
datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this, () -> {
|
||||
synchronized (mLock) {
|
||||
requestHideFillUi(mCurrentViewId);
|
||||
}
|
||||
});
|
||||
try {
|
||||
inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
|
||||
} catch (RemoteException e) {
|
||||
@@ -3024,7 +3028,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
mInlineSuggestionsRequestCallback != null
|
||||
? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
|
||||
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
|
||||
currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
|
||||
currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> {
|
||||
synchronized (mLock) {
|
||||
cancelAugmentedAutofillLocked();
|
||||
}
|
||||
});
|
||||
|
||||
if (mAugmentedAutofillDestroyer == null) {
|
||||
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.autofill;
|
||||
package com.android.server.autofill.ui;
|
||||
|
||||
import static com.android.server.autofill.Helper.sDebug;
|
||||
|
||||
@@ -32,18 +32,13 @@ import android.view.inputmethod.InlineSuggestion;
|
||||
import android.view.inputmethod.InlineSuggestionInfo;
|
||||
import android.view.inputmethod.InlineSuggestionsResponse;
|
||||
|
||||
import com.android.internal.util.function.QuadFunction;
|
||||
import com.android.internal.view.inline.IInlineContentCallback;
|
||||
import com.android.internal.view.inline.IInlineContentProvider;
|
||||
import com.android.server.UiThread;
|
||||
import com.android.server.autofill.ui.AutoFillUI;
|
||||
import com.android.server.autofill.ui.InlineSuggestionUi;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final class InlineSuggestionFactory {
|
||||
private static final String TAG = "InlineSuggestionFactory";
|
||||
|
||||
@@ -65,28 +60,12 @@ public final class InlineSuggestionFactory {
|
||||
@NonNull Dataset[] datasets,
|
||||
@NonNull AutofillId autofillId,
|
||||
@NonNull Context context,
|
||||
@NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
|
||||
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
|
||||
|
||||
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
|
||||
final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
|
||||
for (Dataset dataset : datasets) {
|
||||
final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
|
||||
if (fieldIndex < 0) {
|
||||
Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
|
||||
return null;
|
||||
}
|
||||
final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
|
||||
fieldIndex);
|
||||
if (inlinePresentation == null) {
|
||||
Slog.w(TAG, "InlinePresentation not found in dataset");
|
||||
return null;
|
||||
}
|
||||
InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset,
|
||||
inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback);
|
||||
inlineSuggestions.add(inlineSuggestion);
|
||||
}
|
||||
return new InlineSuggestionsResponse(inlineSuggestions);
|
||||
@NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
|
||||
@NonNull Runnable onErrorCallback) {
|
||||
return createInlineSuggestionsResponseInternal(datasets, autofillId,
|
||||
context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
|
||||
filedIndex) -> createAugmentedInlineSuggestion(dataset,
|
||||
inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,11 +76,26 @@ public final class InlineSuggestionFactory {
|
||||
@NonNull Dataset[] datasets,
|
||||
@NonNull AutofillId autofillId,
|
||||
@NonNull Context context,
|
||||
@NonNull AutoFillUI.AutoFillUiCallback client) {
|
||||
if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
|
||||
@NonNull AutoFillUI.AutoFillUiCallback client,
|
||||
@NonNull Runnable onErrorCallback) {
|
||||
return createInlineSuggestionsResponseInternal(datasets, autofillId,
|
||||
context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
|
||||
filedIndex) -> createInlineSuggestion(requestId, dataset, filedIndex,
|
||||
inlinePresentation, inlineSuggestionUi, client));
|
||||
}
|
||||
|
||||
private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
|
||||
@NonNull Dataset[] datasets,
|
||||
@NonNull AutofillId autofillId,
|
||||
@NonNull Context context,
|
||||
@NonNull Runnable onErrorCallback,
|
||||
@NonNull QuadFunction<Dataset, InlinePresentation, InlineSuggestionUi,
|
||||
Integer, InlineSuggestion> suggestionFactory) {
|
||||
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
|
||||
|
||||
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
|
||||
final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
|
||||
final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
|
||||
onErrorCallback);
|
||||
for (Dataset dataset : datasets) {
|
||||
final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
|
||||
if (fieldIndex < 0) {
|
||||
@@ -114,9 +108,8 @@ public final class InlineSuggestionFactory {
|
||||
Slog.w(TAG, "InlinePresentation not found in dataset");
|
||||
return null;
|
||||
}
|
||||
InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset,
|
||||
fieldIndex,
|
||||
inlinePresentation, inlineSuggestionUi, client);
|
||||
InlineSuggestion inlineSuggestion = suggestionFactory.apply(dataset,
|
||||
inlinePresentation, inlineSuggestionUi, fieldIndex);
|
||||
inlineSuggestions.add(inlineSuggestion);
|
||||
}
|
||||
return new InlineSuggestionsResponse(inlineSuggestions);
|
||||
@@ -131,9 +124,8 @@ public final class InlineSuggestionFactory {
|
||||
inlinePresentation.getInlinePresentationSpec(),
|
||||
InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""},
|
||||
InlineSuggestionInfo.TYPE_SUGGESTION);
|
||||
final View.OnClickListener onClickListener = v -> {
|
||||
final View.OnClickListener onClickListener = v ->
|
||||
inlineSuggestionUiCallback.autofill(dataset);
|
||||
};
|
||||
final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
|
||||
createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
|
||||
onClickListener));
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.ui;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
|
||||
/**
|
||||
* This class is the root view for an inline suggestion. It is responsible for
|
||||
* detecting the click on the item and to also transfer input focus to the IME
|
||||
* window if we detect the user is scrolling.
|
||||
*/
|
||||
// TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus
|
||||
@SuppressLint("ViewConstructor")
|
||||
class InlineSuggestionRoot extends FrameLayout {
|
||||
private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName();
|
||||
|
||||
private final @NonNull Runnable mOnErrorCallback;
|
||||
private final int mTouchSlop;
|
||||
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
|
||||
InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) {
|
||||
super(context);
|
||||
mOnErrorCallback = onErrorCallback;
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mDownX = event.getX();
|
||||
mDownY = event.getY();
|
||||
} break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final float distance = MathUtils.dist(mDownX, mDownY,
|
||||
event.getX(), event.getY());
|
||||
if (distance > mTouchSlop) {
|
||||
transferTouchFocusToImeWindow();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private void transferTouchFocusToImeWindow() {
|
||||
final WindowManagerInternal windowManagerInternal = LocalServices.getService(
|
||||
WindowManagerInternal.class);
|
||||
if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
|
||||
getContext().getDisplayId())) {
|
||||
Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME");
|
||||
mOnErrorCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,12 @@ public class InlineSuggestionUi {
|
||||
// (int)}. This name is a single string of the form "package:type/entry".
|
||||
private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)");
|
||||
|
||||
private final Context mContext;
|
||||
private final @NonNull Context mContext;
|
||||
private final @NonNull Runnable mOnErrorCallback;
|
||||
|
||||
public InlineSuggestionUi(Context context) {
|
||||
InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) {
|
||||
this.mContext = context;
|
||||
mOnErrorCallback = onErrorCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,15 +96,17 @@ public class InlineSuggestionUi {
|
||||
}
|
||||
final View suggestionView = renderSlice(inlinePresentation.getSlice(),
|
||||
contextThemeWrapper);
|
||||
if (onClickListener != null) {
|
||||
suggestionView.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(
|
||||
mContext, mOnErrorCallback);
|
||||
suggestionRoot.addView(suggestionView);
|
||||
suggestionRoot.setOnClickListener(onClickListener);
|
||||
|
||||
WindowManager.LayoutParams lp =
|
||||
new WindowManager.LayoutParams(width, height,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION, 0,
|
||||
PixelFormat.TRANSPARENT);
|
||||
wvr.addView(suggestionView, lp);
|
||||
wvr.addView(suggestionRoot, lp);
|
||||
return sc;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
int displayId, InputApplicationHandle application);
|
||||
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
|
||||
private static native boolean nativeTransferTouchFocus(long ptr,
|
||||
InputChannel fromChannel, InputChannel toChannel);
|
||||
IBinder fromChannelToken, IBinder toChannelToken);
|
||||
private static native void nativeSetPointerSpeed(long ptr, int speed);
|
||||
private static native void nativeSetShowTouches(long ptr, boolean enabled);
|
||||
private static native void nativeSetInteractive(long ptr, boolean interactive);
|
||||
@@ -1554,14 +1554,29 @@ public class InputManagerService extends IInputManager.Stub
|
||||
* @return True if the transfer was successful. False if the window with the
|
||||
* specified channel did not actually have touch focus at the time of the request.
|
||||
*/
|
||||
public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
|
||||
if (fromChannel == null) {
|
||||
throw new IllegalArgumentException("fromChannel must not be null.");
|
||||
}
|
||||
if (toChannel == null) {
|
||||
throw new IllegalArgumentException("toChannel must not be null.");
|
||||
}
|
||||
return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
|
||||
public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
|
||||
@NonNull InputChannel toChannel) {
|
||||
return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically transfers touch focus from one window to another as identified by
|
||||
* their input channels. It is possible for multiple windows to have
|
||||
* touch focus if they support split touch dispatch
|
||||
* {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
|
||||
* method only transfers touch focus of the specified window without affecting
|
||||
* other windows that may also have touch focus at the same time.
|
||||
* @param fromChannelToken The channel token of a window that currently has touch focus.
|
||||
* @param toChannelToken The channel token of the window that should receive touch focus in
|
||||
* place of the first.
|
||||
* @return True if the transfer was successful. False if the window with the
|
||||
* specified channel did not actually have touch focus at the time of the request.
|
||||
*/
|
||||
public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
|
||||
@NonNull IBinder toChannelToken) {
|
||||
Objects.nonNull(fromChannelToken);
|
||||
Objects.nonNull(toChannelToken);
|
||||
return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken);
|
||||
}
|
||||
|
||||
@Override // Binder call
|
||||
|
||||
@@ -562,4 +562,14 @@ public abstract class WindowManagerInternal {
|
||||
*/
|
||||
public abstract void setAccessibilityIdToSurfaceMetadata(
|
||||
IBinder windowToken, int accessibilityWindowId);
|
||||
|
||||
/**
|
||||
* Transfers input focus from a given input token to that of the IME window.
|
||||
*
|
||||
* @param sourceInputToken The source token.
|
||||
* @param displayId The display hosting the IME window.
|
||||
* @return Whether transfer was successful.
|
||||
*/
|
||||
public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
|
||||
int displayId);
|
||||
}
|
||||
|
||||
@@ -7558,6 +7558,29 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
|
||||
int displayId) {
|
||||
final IBinder destinationInputToken;
|
||||
|
||||
synchronized (mGlobalLock) {
|
||||
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
|
||||
if (displayContent == null) {
|
||||
return false;
|
||||
}
|
||||
final WindowState imeWindow = displayContent.mInputMethodWindow;
|
||||
if (imeWindow == null) {
|
||||
return false;
|
||||
}
|
||||
if (imeWindow.mInputChannel == null) {
|
||||
return false;
|
||||
}
|
||||
destinationInputToken = imeWindow.mInputChannel.getToken();
|
||||
}
|
||||
|
||||
return mInputManager.transferTouchFocus(sourceInputToken, destinationInputToken);
|
||||
}
|
||||
}
|
||||
|
||||
void registerAppFreezeListener(AppFreezeListener listener) {
|
||||
|
||||
@@ -1563,20 +1563,17 @@ static void nativeSetSystemUiVisibility(JNIEnv* /* env */,
|
||||
}
|
||||
|
||||
static jboolean nativeTransferTouchFocus(JNIEnv* env,
|
||||
jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
|
||||
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
|
||||
|
||||
sp<InputChannel> fromChannel =
|
||||
android_view_InputChannel_getInputChannel(env, fromChannelObj);
|
||||
sp<InputChannel> toChannel =
|
||||
android_view_InputChannel_getInputChannel(env, toChannelObj);
|
||||
|
||||
if (fromChannel == nullptr || toChannel == nullptr) {
|
||||
jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) {
|
||||
if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
|
||||
sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
|
||||
|
||||
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
|
||||
if (im->getInputManager()->getDispatcher()->transferTouchFocus(
|
||||
fromChannel->getConnectionToken(), toChannel->getConnectionToken())) {
|
||||
fromChannelToken, toChannelToken)) {
|
||||
return JNI_TRUE;
|
||||
} else {
|
||||
return JNI_FALSE;
|
||||
@@ -1784,7 +1781,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
|
||||
(void*) nativeSetInputDispatchMode },
|
||||
{ "nativeSetSystemUiVisibility", "(JI)V",
|
||||
(void*) nativeSetSystemUiVisibility },
|
||||
{ "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
|
||||
{ "nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z",
|
||||
(void*) nativeTransferTouchFocus },
|
||||
{ "nativeSetPointerSpeed", "(JI)V",
|
||||
(void*) nativeSetPointerSpeed },
|
||||
|
||||
@@ -126,7 +126,8 @@ public class DragDropControllerTests extends WindowTestsBase {
|
||||
mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
|
||||
mWindow = createDropTargetWindow("Drag test window", 0);
|
||||
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
|
||||
when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
|
||||
when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
|
||||
any(InputChannel.class))).thenReturn(true);
|
||||
|
||||
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
|
||||
}
|
||||
@@ -176,7 +177,8 @@ public class DragDropControllerTests extends WindowTestsBase {
|
||||
.setFormat(PixelFormat.TRANSLUCENT)
|
||||
.build();
|
||||
|
||||
assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
|
||||
assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
|
||||
new InputChannel()));
|
||||
mToken = mTarget.performDrag(
|
||||
new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
|
||||
data);
|
||||
|
||||
Reference in New Issue
Block a user