diff options
17 files changed, 304 insertions, 142 deletions
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 368c94cdc790..79852d3c0fca 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -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); diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index d0ffd7b4d8d4..19eff57269ae 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -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; } diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java index 63ec2d886caf..7d552d62fa72 100644 --- a/core/java/android/service/autofill/augmented/FillController.java +++ b/core/java/android/service/autofill/augmented/FillController.java @@ -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(); + } } } diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index ca49e7daf054..6927cf6541e0 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -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; } /** diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 5d003706ac83..077df6cf16ef 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -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)); + } } } } diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 31e77f358782..d9837211be19 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -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(); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 1a4fc32a76cd..1cb9313d9bf9 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -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 diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 5e6f6fea4dda..880c40158114 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -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); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 415ecd8cfd63..7e5123c82054 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -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(); diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index cb6c8f5d3ea5..38a5b5b1cdaa 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -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)); diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java new file mode 100644 index 000000000000..8d476d72c639 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java @@ -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(); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java index 2adefeabfcc8..bf148a642bbd 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java @@ -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; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 90358ca1f133..a8f706cdd629 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -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 diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 6e243f0b937a..59eee9cc9404 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -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); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a5b99b0a26a3..6e1f46bb5e42 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -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) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 212a3a638634..49db3d5b2b64 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -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 }, diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 5acc0f273554..956c200022e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -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); |