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

Commit a5073ec6 authored by Feng Cao's avatar Feng Cao Committed by Android (Google) Code Review
Browse files

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

parents 7dcdca64 158b6563
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