AutofillPopupWindow.java revision 640e9b9bf4b031f9df75178d2049d88f02755524
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.autofill;
18
19import static android.view.autofill.Helper.sVerbose;
20
21import android.annotation.NonNull;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.transition.Transition;
27import android.util.Log;
28import android.view.View;
29import android.view.View.OnTouchListener;
30import android.view.ViewTreeObserver;
31import android.view.WindowManager;
32import android.view.WindowManager.LayoutParams;
33import android.widget.PopupWindow;
34
35/**
36 * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
37 * UI is rendered in a framework process, but it's controlled by the app.
38 *
39 * TODO(b/34943932): use an app surface control solution.
40 *
41 * @hide
42 */
43public class AutofillPopupWindow extends PopupWindow {
44
45    private static final String TAG = "AutofillPopupWindow";
46
47    private final WindowPresenter mWindowPresenter;
48    private WindowManager.LayoutParams mWindowLayoutParams;
49    private boolean mFullScreen;
50
51    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
52            new View.OnAttachStateChangeListener() {
53        @Override
54        public void onViewAttachedToWindow(View v) {
55            /* ignore - handled by the super class */
56        }
57
58        @Override
59        public void onViewDetachedFromWindow(View v) {
60            dismiss();
61        }
62    };
63
64    /**
65     * Creates a popup window with a presenter owning the window and responsible for
66     * showing/hiding/updating the backing window. This can be useful of the window is
67     * being shown by another process while the popup logic is in the process hosting
68     * the anchor view.
69     * <p>
70     * Using this constructor means that the presenter completely owns the content of
71     * the window and the following methods manipulating the window content shouldn't
72     * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
73     * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
74     * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
75     * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
76     * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
77     * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
78     */
79    public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
80        mWindowPresenter = new WindowPresenter(presenter);
81
82        // Here is a bit of voodoo - we want to show the window as system
83        // controlled one so it covers app windows, but at the same time it has to be
84        // an application type (so it's contained inside the application area).
85        // Hence, we set it to the application type with the highest z-order, which currently
86        // is TYPE_APPLICATION_ABOVE_SUB_PANEL.
87        setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
88        setTouchModal(false);
89        setOutsideTouchable(true);
90        setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
91        setFocusable(true);
92    }
93
94    @Override
95    protected boolean hasContentView() {
96        return true;
97    }
98
99    @Override
100    protected boolean hasDecorView() {
101        return true;
102    }
103
104    @Override
105    protected LayoutParams getDecorViewLayoutParams() {
106        return mWindowLayoutParams;
107    }
108
109    /**
110     * The effective {@code update} method that should be called by its clients.
111     */
112    public void update(View anchor, int offsetX, int offsetY, int width, int height,
113            Rect virtualBounds) {
114        mFullScreen = width == LayoutParams.MATCH_PARENT && height == LayoutParams.MATCH_PARENT;
115        // If we are showing the popup for a virtual view we use a fake view which
116        // delegates to the anchor but present itself with the same bounds as the
117        // virtual view. This ensures that the location logic in popup works
118        // symmetrically when the dropdown is below and above the anchor.
119        final View actualAnchor;
120        if (mFullScreen) {
121            offsetX = 0;
122            offsetY = 0;
123            actualAnchor = anchor;
124        } else if (virtualBounds != null) {
125            final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top};
126            actualAnchor = new View(anchor.getContext()) {
127                @Override
128                public void getLocationOnScreen(int[] location) {
129                    location[0] = mLocationOnScreen[0];
130                    location[1] = mLocationOnScreen[1];
131                }
132
133                @Override
134                public int getAccessibilityViewId() {
135                    return anchor.getAccessibilityViewId();
136                }
137
138                @Override
139                public ViewTreeObserver getViewTreeObserver() {
140                    return anchor.getViewTreeObserver();
141                }
142
143                @Override
144                public IBinder getApplicationWindowToken() {
145                    return anchor.getApplicationWindowToken();
146                }
147
148                @Override
149                public View getRootView() {
150                    return anchor.getRootView();
151                }
152
153                @Override
154                public int getLayoutDirection() {
155                    return anchor.getLayoutDirection();
156                }
157
158                @Override
159                public void getWindowDisplayFrame(Rect outRect) {
160                    anchor.getWindowDisplayFrame(outRect);
161                }
162
163                @Override
164                public void addOnAttachStateChangeListener(
165                        OnAttachStateChangeListener listener) {
166                    anchor.addOnAttachStateChangeListener(listener);
167                }
168
169                @Override
170                public void removeOnAttachStateChangeListener(
171                        OnAttachStateChangeListener listener) {
172                    anchor.removeOnAttachStateChangeListener(listener);
173                }
174
175                @Override
176                public boolean isAttachedToWindow() {
177                    return anchor.isAttachedToWindow();
178                }
179
180                @Override
181                public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
182                    return anchor.requestRectangleOnScreen(rectangle, immediate);
183                }
184
185                @Override
186                public IBinder getWindowToken() {
187                    return anchor.getWindowToken();
188                }
189            };
190
191            actualAnchor.setLeftTopRightBottom(
192                    virtualBounds.left, virtualBounds.top,
193                    virtualBounds.right, virtualBounds.bottom);
194            actualAnchor.setScrollX(anchor.getScrollX());
195            actualAnchor.setScrollY(anchor.getScrollY());
196
197            anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
198                mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX);
199                mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY);
200            });
201            actualAnchor.setWillNotDraw(true);
202        } else {
203            actualAnchor = anchor;
204        }
205
206        if (!isShowing()) {
207            setWidth(width);
208            setHeight(height);
209            showAsDropDown(actualAnchor, offsetX, offsetY);
210        } else {
211            update(actualAnchor, offsetX, offsetY, width, height);
212        }
213    }
214
215    @Override
216    protected void update(View anchor, WindowManager.LayoutParams params) {
217        final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
218                : View.LAYOUT_DIRECTION_LOCALE;
219        mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
220                layoutDirection);
221    }
222
223    @Override
224    protected boolean findDropDownPosition(View anchor, LayoutParams outParams,
225            int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
226        if (mFullScreen) {
227            // Do not patch LayoutParams if force full screen
228            return false;
229        }
230        return super.findDropDownPosition(anchor, outParams, xOffset, yOffset,
231                width, height, gravity, allowScroll);
232    }
233
234    @Override
235    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
236        if (sVerbose) {
237            Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
238                    + ", isShowing(): " + isShowing());
239        }
240        if (isShowing()) {
241            return;
242        }
243
244        setShowing(true);
245        setDropDown(true);
246        attachToAnchor(anchor, xoff, yoff, gravity);
247        final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
248                anchor.getWindowToken());
249        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
250                p.width, p.height, gravity, getAllowScrollingAnchorParent());
251        updateAboveAnchor(aboveAnchor);
252        p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
253        p.packageName = anchor.getContext().getPackageName();
254        mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
255                anchor.getLayoutDirection());
256    }
257
258    @Override
259    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
260        super.attachToAnchor(anchor, xoff, yoff, gravity);
261        anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
262    }
263
264    @Override
265    protected void detachFromAnchor() {
266        final View anchor = getAnchor();
267        if (anchor != null) {
268            anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
269        }
270        super.detachFromAnchor();
271    }
272
273    @Override
274    public void dismiss() {
275        if (!isShowing() || isTransitioningToDismiss()) {
276            return;
277        }
278
279        setShowing(false);
280        setTransitioningToDismiss(true);
281
282        mWindowPresenter.hide(getTransitionEpicenter());
283        detachFromAnchor();
284        if (getOnDismissListener() != null) {
285            getOnDismissListener().onDismiss();
286        }
287    }
288
289    @Override
290    public int getAnimationStyle() {
291        throw new IllegalStateException("You can't call this!");
292    }
293
294    @Override
295    public Drawable getBackground() {
296        throw new IllegalStateException("You can't call this!");
297    }
298
299    @Override
300    public View getContentView() {
301        throw new IllegalStateException("You can't call this!");
302    }
303
304    @Override
305    public float getElevation() {
306        throw new IllegalStateException("You can't call this!");
307    }
308
309    @Override
310    public Transition getEnterTransition() {
311        throw new IllegalStateException("You can't call this!");
312    }
313
314    @Override
315    public Transition getExitTransition() {
316        throw new IllegalStateException("You can't call this!");
317    }
318
319    @Override
320    public void setAnimationStyle(int animationStyle) {
321        throw new IllegalStateException("You can't call this!");
322    }
323
324    @Override
325    public void setBackgroundDrawable(Drawable background) {
326        throw new IllegalStateException("You can't call this!");
327    }
328
329    @Override
330    public void setContentView(View contentView) {
331        if (contentView != null) {
332            throw new IllegalStateException("You can't call this!");
333        }
334    }
335
336    @Override
337    public void setElevation(float elevation) {
338        throw new IllegalStateException("You can't call this!");
339    }
340
341    @Override
342    public void setEnterTransition(Transition enterTransition) {
343        throw new IllegalStateException("You can't call this!");
344    }
345
346    @Override
347    public void setExitTransition(Transition exitTransition) {
348        throw new IllegalStateException("You can't call this!");
349    }
350
351    @Override
352    public void setTouchInterceptor(OnTouchListener l) {
353        throw new IllegalStateException("You can't call this!");
354    }
355
356    /**
357     * Contract between the popup window and a presenter that is responsible for
358     * showing/hiding/updating the actual window.
359     *
360     * <p>This can be useful if the anchor is in one process and the backing window is owned by
361     * another process.
362     */
363    private class WindowPresenter {
364        final IAutofillWindowPresenter mPresenter;
365
366        WindowPresenter(IAutofillWindowPresenter presenter) {
367            mPresenter = presenter;
368        }
369
370        /**
371         * Shows the backing window.
372         *
373         * @param p The window layout params.
374         * @param transitionEpicenter The transition epicenter if animating.
375         * @param fitsSystemWindows Whether the content view should account for system decorations.
376         * @param layoutDirection The content layout direction to be consistent with the anchor.
377         */
378        void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
379                int layoutDirection) {
380            try {
381                mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
382            } catch (RemoteException e) {
383                Log.w(TAG, "Error showing fill window", e);
384                e.rethrowFromSystemServer();
385            }
386        }
387
388        /**
389         * Hides the backing window.
390         *
391         * @param transitionEpicenter The transition epicenter if animating.
392         */
393        void hide(Rect transitionEpicenter) {
394            try {
395                mPresenter.hide(transitionEpicenter);
396            } catch (RemoteException e) {
397                Log.w(TAG, "Error hiding fill window", e);
398                e.rethrowFromSystemServer();
399            }
400        }
401    }
402}
403