• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.view.autofill;
18 
19 import static android.view.autofill.Helper.sVerbose;
20 
21 import android.annotation.NonNull;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.os.IBinder;
25 import android.os.RemoteException;
26 import android.transition.Transition;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.View.OnTouchListener;
30 import android.view.ViewTreeObserver;
31 import android.view.WindowManager;
32 import android.view.WindowManager.LayoutParams;
33 import 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  */
43 public class AutofillPopupWindow extends PopupWindow {
44 
45     private static final String TAG = "AutofillPopupWindow";
46 
47     private final WindowPresenter mWindowPresenter;
48     private WindowManager.LayoutParams mWindowLayoutParams;
49 
50     private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
51             new View.OnAttachStateChangeListener() {
52         @Override
53         public void onViewAttachedToWindow(View v) {
54             /* ignore - handled by the super class */
55         }
56 
57         @Override
58         public void onViewDetachedFromWindow(View v) {
59             dismiss();
60         }
61     };
62 
63     /**
64      * Creates a popup window with a presenter owning the window and responsible for
65      * showing/hiding/updating the backing window. This can be useful of the window is
66      * being shown by another process while the popup logic is in the process hosting
67      * the anchor view.
68      * <p>
69      * Using this constructor means that the presenter completely owns the content of
70      * the window and the following methods manipulating the window content shouldn't
71      * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
72      * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
73      * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
74      * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
75      * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
76      * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
77      */
AutofillPopupWindow(@onNull IAutofillWindowPresenter presenter)78     public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
79         mWindowPresenter = new WindowPresenter(presenter);
80 
81         setOutsideTouchable(true);
82         setInputMethodMode(INPUT_METHOD_NEEDED);
83     }
84 
85     @Override
hasContentView()86     protected boolean hasContentView() {
87         return true;
88     }
89 
90     @Override
hasDecorView()91     protected boolean hasDecorView() {
92         return true;
93     }
94 
95     @Override
getDecorViewLayoutParams()96     protected LayoutParams getDecorViewLayoutParams() {
97         return mWindowLayoutParams;
98     }
99 
100     /**
101      * The effective {@code update} method that should be called by its clients.
102      */
update(View anchor, int offsetX, int offsetY, int width, int height, Rect virtualBounds)103     public void update(View anchor, int offsetX, int offsetY, int width, int height,
104             Rect virtualBounds) {
105         // If we are showing the popup for a virtual view we use a fake view which
106         // delegates to the anchor but present itself with the same bounds as the
107         // virtual view. This ensures that the location logic in popup works
108         // symmetrically when the dropdown is below and above the anchor.
109         final View actualAnchor;
110         if (virtualBounds != null) {
111             actualAnchor = new View(anchor.getContext()) {
112                 @Override
113                 public void getLocationOnScreen(int[] location) {
114                     location[0] = virtualBounds.left;
115                     location[1] = virtualBounds.top;
116                 }
117 
118                 @Override
119                 public int getAccessibilityViewId() {
120                     return anchor.getAccessibilityViewId();
121                 }
122 
123                 @Override
124                 public ViewTreeObserver getViewTreeObserver() {
125                     return anchor.getViewTreeObserver();
126                 }
127 
128                 @Override
129                 public IBinder getApplicationWindowToken() {
130                     return anchor.getApplicationWindowToken();
131                 }
132 
133                 @Override
134                 public View getRootView() {
135                     return anchor.getRootView();
136                 }
137 
138                 @Override
139                 public int getLayoutDirection() {
140                     return anchor.getLayoutDirection();
141                 }
142 
143                 @Override
144                 public void getWindowDisplayFrame(Rect outRect) {
145                     anchor.getWindowDisplayFrame(outRect);
146                 }
147 
148                 @Override
149                 public void addOnAttachStateChangeListener(
150                         OnAttachStateChangeListener listener) {
151                     anchor.addOnAttachStateChangeListener(listener);
152                 }
153 
154                 @Override
155                 public void removeOnAttachStateChangeListener(
156                         OnAttachStateChangeListener listener) {
157                     anchor.removeOnAttachStateChangeListener(listener);
158                 }
159 
160                 @Override
161                 public boolean isAttachedToWindow() {
162                     return anchor.isAttachedToWindow();
163                 }
164 
165                 @Override
166                 public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
167                     return anchor.requestRectangleOnScreen(rectangle, immediate);
168                 }
169 
170                 @Override
171                 public IBinder getWindowToken() {
172                     return anchor.getWindowToken();
173                 }
174             };
175 
176             actualAnchor.setLeftTopRightBottom(
177                     virtualBounds.left, virtualBounds.top,
178                     virtualBounds.right, virtualBounds.bottom);
179             actualAnchor.setScrollX(anchor.getScrollX());
180             actualAnchor.setScrollY(anchor.getScrollY());
181         } else {
182             actualAnchor = anchor;
183         }
184 
185         if (!isShowing()) {
186             setWidth(width);
187             setHeight(height);
188             showAsDropDown(actualAnchor, offsetX, offsetY);
189         } else {
190             update(actualAnchor, offsetX, offsetY, width, height);
191         }
192     }
193 
194     @Override
update(View anchor, WindowManager.LayoutParams params)195     protected void update(View anchor, WindowManager.LayoutParams params) {
196         final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
197                 : View.LAYOUT_DIRECTION_LOCALE;
198         mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
199                 layoutDirection);
200     }
201 
202     @Override
showAsDropDown(View anchor, int xoff, int yoff, int gravity)203     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
204         if (sVerbose) {
205             Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
206                     + ", isShowing(): " + isShowing());
207         }
208         if (isShowing()) {
209             return;
210         }
211 
212         setShowing(true);
213         setDropDown(true);
214         attachToAnchor(anchor, xoff, yoff, gravity);
215         final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
216                 anchor.getWindowToken());
217         final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
218                 p.width, p.height, gravity, getAllowScrollingAnchorParent());
219         updateAboveAnchor(aboveAnchor);
220         p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
221         p.packageName = anchor.getContext().getPackageName();
222         mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
223                 anchor.getLayoutDirection());
224     }
225 
226     @Override
attachToAnchor(View anchor, int xoff, int yoff, int gravity)227     protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
228         super.attachToAnchor(anchor, xoff, yoff, gravity);
229         anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
230     }
231 
232     @Override
detachFromAnchor()233     protected void detachFromAnchor() {
234         final View anchor = getAnchor();
235         if (anchor != null) {
236             anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
237         }
238         super.detachFromAnchor();
239     }
240 
241     @Override
dismiss()242     public void dismiss() {
243         if (!isShowing() || isTransitioningToDismiss()) {
244             return;
245         }
246 
247         setShowing(false);
248         setTransitioningToDismiss(true);
249 
250         mWindowPresenter.hide(getTransitionEpicenter());
251         detachFromAnchor();
252         if (getOnDismissListener() != null) {
253             getOnDismissListener().onDismiss();
254         }
255     }
256 
257     @Override
getAnimationStyle()258     public int getAnimationStyle() {
259         throw new IllegalStateException("You can't call this!");
260     }
261 
262     @Override
getBackground()263     public Drawable getBackground() {
264         throw new IllegalStateException("You can't call this!");
265     }
266 
267     @Override
getContentView()268     public View getContentView() {
269         throw new IllegalStateException("You can't call this!");
270     }
271 
272     @Override
getElevation()273     public float getElevation() {
274         throw new IllegalStateException("You can't call this!");
275     }
276 
277     @Override
getEnterTransition()278     public Transition getEnterTransition() {
279         throw new IllegalStateException("You can't call this!");
280     }
281 
282     @Override
getExitTransition()283     public Transition getExitTransition() {
284         throw new IllegalStateException("You can't call this!");
285     }
286 
287     @Override
setAnimationStyle(int animationStyle)288     public void setAnimationStyle(int animationStyle) {
289         throw new IllegalStateException("You can't call this!");
290     }
291 
292     @Override
setBackgroundDrawable(Drawable background)293     public void setBackgroundDrawable(Drawable background) {
294         throw new IllegalStateException("You can't call this!");
295     }
296 
297     @Override
setContentView(View contentView)298     public void setContentView(View contentView) {
299         if (contentView != null) {
300             throw new IllegalStateException("You can't call this!");
301         }
302     }
303 
304     @Override
setElevation(float elevation)305     public void setElevation(float elevation) {
306         throw new IllegalStateException("You can't call this!");
307     }
308 
309     @Override
setEnterTransition(Transition enterTransition)310     public void setEnterTransition(Transition enterTransition) {
311         throw new IllegalStateException("You can't call this!");
312     }
313 
314     @Override
setExitTransition(Transition exitTransition)315     public void setExitTransition(Transition exitTransition) {
316         throw new IllegalStateException("You can't call this!");
317     }
318 
319     @Override
setTouchInterceptor(OnTouchListener l)320     public void setTouchInterceptor(OnTouchListener l) {
321         throw new IllegalStateException("You can't call this!");
322     }
323 
324     /**
325      * Contract between the popup window and a presenter that is responsible for
326      * showing/hiding/updating the actual window.
327      *
328      * <p>This can be useful if the anchor is in one process and the backing window is owned by
329      * another process.
330      */
331     private class WindowPresenter {
332         final IAutofillWindowPresenter mPresenter;
333 
WindowPresenter(IAutofillWindowPresenter presenter)334         WindowPresenter(IAutofillWindowPresenter presenter) {
335             mPresenter = presenter;
336         }
337 
338         /**
339          * Shows the backing window.
340          *
341          * @param p The window layout params.
342          * @param transitionEpicenter The transition epicenter if animating.
343          * @param fitsSystemWindows Whether the content view should account for system decorations.
344          * @param layoutDirection The content layout direction to be consistent with the anchor.
345          */
show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection)346         void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
347                 int layoutDirection) {
348             try {
349                 mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
350             } catch (RemoteException e) {
351                 Log.w(TAG, "Error showing fill window", e);
352                 e.rethrowFromSystemServer();
353             }
354         }
355 
356         /**
357          * Hides the backing window.
358          *
359          * @param transitionEpicenter The transition epicenter if animating.
360          */
hide(Rect transitionEpicenter)361         void hide(Rect transitionEpicenter) {
362             try {
363                 mPresenter.hide(transitionEpicenter);
364             } catch (RemoteException e) {
365                 Log.w(TAG, "Error hiding fill window", e);
366                 e.rethrowFromSystemServer();
367             }
368         }
369     }
370 }
371