Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 158b6563 authored by Feng Cao's avatar Feng Cao
Browse files

Use the same UI classes for augmented autofill as the standard android autofill.

Bug: 122479403
Test: atest CtsAutoFillServiceTestCases:android.autofillservice.cts.augmented.AugmentedLoginActivityTest
Test: atest CtsAutoFillServiceTestCases # sanity check, 3 are consistently failing due to timeout.
Change-Id: Ida44a5714e1abcfc308a2a8d747b7766202fcb45
parent 4be2e3f8
Loading
Loading
Loading
Loading
+27 −13
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAugmentedAutofillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;

import com.android.internal.annotations.GuardedBy;

@@ -95,10 +96,10 @@ public abstract class AugmentedAutofillService extends Service {
        }

        @Override
        public void onDestroyFillWindowRequest(int sessionId) {
        public void onDestroyAllFillWindowsRequest() {
            mHandler.sendMessage(
                    obtainMessage(AugmentedAutofillService::handleOnDestroyFillWindowRequest,
                            AugmentedAutofillService.this, sessionId));
                    obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
                            AugmentedAutofillService.this));
        }
    };

@@ -185,18 +186,21 @@ public abstract class AugmentedAutofillService extends Service {
                new FillCallback(proxy));
    }

    private void handleOnDestroyFillWindowRequest(@NonNull int sessionId) {
        AutofillProxy proxy = null;
    private void handleOnDestroyAllFillWindowsRequest() {
        if (mAutofillProxies != null) {
            proxy = mAutofillProxies.get(sessionId);
        }
            final int size = mAutofillProxies.size();
            for (int i = 0; i < size; i++) {
                final int sessionId = mAutofillProxies.keyAt(i);
                final AutofillProxy proxy = mAutofillProxies.valueAt(i);
                if (proxy == null) {
                    // TODO(b/111330312): this might be fine, in which case we should logv it
                    Log.w(TAG, "No proxy for session " + sessionId);
                    return;
                }
                proxy.destroy();
        mAutofillProxies.remove(sessionId);
            }
            mAutofillProxies.clear();
        }
    }

    private void handleOnUnbind() {
@@ -350,6 +354,16 @@ public abstract class AugmentedAutofillService extends Service {
            }
        }

        public void requestShowFillUi(int width, int height, Rect anchorBounds,
                IAutofillWindowPresenter presenter) throws RemoteException {
            mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
                    presenter);
        }

        public void requestHideFillUi() throws RemoteException {
            mClient.requestHideFillUi(mSessionId, mFocusedId);
        }

        private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
            synchronized (mLock) {
                // TODO(b/111330312): should we close the popupwindow if the focused id changed?
+121 −62
Original line number Diff line number Diff line
@@ -16,22 +16,25 @@
package android.service.autofill.augmented;

import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG;
import static android.service.autofill.augmented.AugmentedAutofillService.VERBOSE;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Dialog;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.service.autofill.augmented.PresentationParams.Area;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.autofill.IAutofillWindowPresenter;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -71,7 +74,7 @@ public final class FillWindow implements AutoCloseable {
    /** Indicates the data being shown is a physical address */
    public static final long FLAG_METADATA_ADDRESS = 0x1;

    // TODO(b/111330312): add moar flags
    // TODO(b/111330312): add more flags

    /** @hide */
    @LongDef(prefix = { "FLAG" }, value = {
@@ -83,8 +86,17 @@ public final class FillWindow implements AutoCloseable {
    private final Object mLock = new Object();
    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;
    @GuardedBy("mLock")
    private View mFillView;
    @GuardedBy("mLock")
    private Dialog mDialog;
    private boolean mShowing;
    @GuardedBy("mLock")
    private Rect mBounds;

    @GuardedBy("mLock")
    private boolean mDestroyed;
@@ -140,51 +152,28 @@ public final class FillWindow implements AutoCloseable {
            // window instead of destroying. In fact, it might be better to allocate a full window
            // initially, which is transparent (and let touches get through) everywhere but in the
            // rect boundaries.
            destroy();

            // TODO(b/111330312): make sure all touch events are handled, window is always closed,
            // etc.

            mDialog = new Dialog(rootView.getContext()) {
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                        FillWindow.this.destroy();
            mWm = rootView.getContext().getSystemService(WindowManager.class);
            mFillView = rootView;
            // Listen to the touch outside to destroy the window when typing is detected.
            mFillView.setOnTouchListener(
                    (view, motionEvent) -> {
                        if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
                            if (VERBOSE) Log.v(TAG, "Outside touch detected, hiding the window");
                            hide();
                        }
                        return false;
                    }
            };
            mCloseGuard.open("destroy");
            final Window window = mDialog.getWindow();
            window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
            // Makes sure touch outside the dialog is received by the window behind the dialog.
            window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
            // Makes sure the touch outside the dialog is received by the dialog to dismiss it.
            window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
            // Makes sure keyboard shows up.
            window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

            final int height = rect.bottom - rect.top;
            final int width = rect.right - rect.left;
            final WindowManager.LayoutParams windowParams = window.getAttributes();
            windowParams.gravity = Gravity.TOP | Gravity.LEFT;
            windowParams.y = rect.top + height;
            windowParams.height = height;
            windowParams.x = rect.left;
            windowParams.width = width;

            window.setAttributes(windowParams);
            window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            window.setBackgroundDrawableResource(android.R.color.transparent);

            mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
            mDialog.setContentView(rootView, diagParams);

            );
            mShowing = false;
            mBounds = new Rect(area.getBounds());
            if (DEBUG) {
                Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
            }

            mDestroyed = false;
            mProxy.setFillWindow(this);
            return true;
        }
@@ -194,36 +183,87 @@ public final class FillWindow implements AutoCloseable {
    void show() {
        // TODO(b/111330312): check if updated first / throw exception
        if (DEBUG) Log.d(TAG, "show()");

        synchronized (mLock) {
            checkNotDestroyedLocked();
            if (mDialog == null) {
            if (mWm == null || mFillView == null) {
                throw new IllegalStateException("update() not called yet, or already destroyed()");
            }

            mDialog.show();
            if (mProxy != null) {
                try {
                    mProxy.requestShowFillUi(mBounds.right - mBounds.left,
                            mBounds.bottom - mBounds.top,
                            /*anchorBounds=*/ null, mFillWindowPresenter);
                } catch (RemoteException e) {
                    Log.w(TAG, "Error requesting to show fill window", e);
                }
                mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
            }
        }
    }

    /**
     * Hides the window.
     *
     * <p>The window is not destroyed and can be shown again
     */
    private void hide() {
        if (DEBUG) Log.d(TAG, "hide()");
        synchronized (mLock) {
            checkNotDestroyedLocked();
            if (mWm == null || mFillView == null) {
                throw new IllegalStateException("update() not called yet, or already destroyed()");
            }
            if (mProxy != null && mShowing) {
                try {
                    mProxy.requestHideFillUi();
                } catch (RemoteException e) {
                    Log.w(TAG, "Error requesting to hide fill window", e);
                }
            }
        }
    }

    private void handleShow(WindowManager.LayoutParams p) {
        if (DEBUG) Log.d(TAG, "handleShow()");
        synchronized (mLock) {
            if (mWm != null && mFillView != null) {
                p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                if (!mShowing) {
                    mWm.addView(mFillView, p);
                    mShowing = true;
                } else {
                    mWm.updateViewLayout(mFillView, p);
                }
            }
        }
    }

    private void handleHide() {
        if (DEBUG) Log.d(TAG, "handleHide()");
        synchronized (mLock) {
            if (mWm != null && mFillView != null && mShowing) {
                mWm.removeView(mFillView);
                mShowing = false;
            }
        }
    }

    /**
     * Destroys the window.
     *
     * <p>Once destroyed, this window cannot be used anymore
     */
    public void destroy() {
        if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);

        synchronized (this) {
            if (mDestroyed || mDialog == null) return;

            mDialog.dismiss();
            mDialog = null;
            if (mProxy != null) {
                mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
        if (DEBUG) {
            Log.d(TAG,
                    "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
                            + mFillView);
        }
        synchronized (mLock) {
            if (mDestroyed) return;
            hide();
            mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
            mDestroyed = true;
            mCloseGuard.close();
        }
    }
@@ -250,11 +290,15 @@ public final class FillWindow implements AutoCloseable {
    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
        synchronized (this) {
            pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
            if (mDialog != null) {
                pw.print(prefix); pw.print("dialog: ");
                pw.println(mDialog.isShowing() ? "shown" : "hidden");
                pw.print(prefix); pw.print("window: ");
                pw.println(mDialog.getWindow().getAttributes());
            if (mFillView != null) {
                pw.print(prefix); pw.print("fill window: ");
                pw.println(mShowing ? "shown" : "hidden");
                pw.print(prefix); pw.print("fill view: ");
                pw.println(mFillView);
                pw.print(prefix); pw.print("mBounds: ");
                pw.println(mBounds);
                pw.print(prefix); pw.print("mWm: ");
                pw.println(mWm);
            }
        }
    }
@@ -264,4 +308,19 @@ public final class FillWindow implements AutoCloseable {
    public void close() throws Exception {
        destroy();
    }

    private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
        @Override
        public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
                boolean fitsSystemWindows, int layoutDirection) {
            if (DEBUG) Log.d(TAG, "FillWindowPresenter.show()");
            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
        }

        @Override
        public void hide(Rect transitionEpicenter) {
            if (DEBUG) Log.d(TAG, "FillWindowPresenter.hide()");
            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -36,5 +36,5 @@ oneway interface IAugmentedAutofillService {
                       in ComponentName activityComponent, in AutofillId focusedId,
                       in AutofillValue focusedValue, long requestTime, in IFillCallback callback);

    void onDestroyFillWindowRequest(int sessionId);
    void onDestroyAllFillWindowsRequest();
}
+18 −0
Original line number Diff line number Diff line
@@ -2997,5 +2997,23 @@ public final class AutofillManager {
                afm.post(() -> afm.autofill(sessionId, ids, values));
            }
        }

        @Override
        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
                Rect anchorBounds, IAutofillWindowPresenter presenter) {
            final AutofillManager afm = mAfm.get();
            if (afm != null) {
                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
                        presenter));
            }
        }

        @Override
        public void requestHideFillUi(int sessionId, AutofillId id) {
            final AutofillManager afm = mAfm.get();
            if (afm != null) {
                afm.post(() -> afm.requestHideFillUi(id, false));
            }
        }
    }
}
+21 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import java.util.List;
import android.graphics.Rect;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutofillWindowPresenter;

/**
 * Object running in the application process and responsible to provide the functionalities
@@ -29,6 +30,24 @@ import android.view.autofill.AutofillValue;
 * @hide
 */
interface IAugmentedAutofillManagerClient {
    /**
      * Gets the coordinates of the input field view.
      */
    Rect getViewCoordinates(in AutofillId id);

    /**
     * Autofills the activity with the contents of the values.
     */
    void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);

    /**
      * Requests showing the fill UI.
      */
    void requestShowFillUi(int sessionId, in AutofillId id, int width, int height,
            in Rect anchorBounds, in IAutofillWindowPresenter presenter);

    /**
      * Requests hiding the fill UI.
      */
    void requestHideFillUi(int sessionId, in AutofillId id);
}
Loading