blob: b4688bb1d48bb87c78733a4b9a677ea0b8901bdb [file] [log] [blame]
Felipe Leme4753bb02017-03-22 20:24:00 -07001/*
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
Felipe Leme8697a312017-05-30 09:35:17 -070019import static android.view.autofill.Helper.sVerbose;
20
Felipe Leme4753bb02017-03-22 20:24:00 -070021import android.annotation.NonNull;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
Svet Ganov374cae12017-05-10 13:42:33 -070024import android.os.IBinder;
Felipe Leme4753bb02017-03-22 20:24:00 -070025import android.os.RemoteException;
26import android.transition.Transition;
27import android.util.Log;
28import android.view.View;
29import android.view.View.OnTouchListener;
Svet Ganov374cae12017-05-10 13:42:33 -070030import android.view.ViewTreeObserver;
Felipe Leme4753bb02017-03-22 20:24:00 -070031import 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
Svet Ganov77150c52017-09-22 17:19:04 -070050 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
Felipe Leme4753bb02017-03-22 20:24:00 -070063 /**
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 */
78 public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
79 mWindowPresenter = new WindowPresenter(presenter);
80
81 setOutsideTouchable(true);
82 setInputMethodMode(INPUT_METHOD_NEEDED);
83 }
84
85 @Override
86 protected boolean hasContentView() {
87 return true;
88 }
89
90 @Override
91 protected boolean hasDecorView() {
92 return true;
93 }
94
95 @Override
96 protected LayoutParams getDecorViewLayoutParams() {
97 return mWindowLayoutParams;
98 }
99
100 /**
101 * The effective {@code update} method that should be called by its clients.
102 */
103 public void update(View anchor, int offsetX, int offsetY, int width, int height,
Svet Ganov374cae12017-05-10 13:42:33 -0700104 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
Felipe Leme4753bb02017-03-22 20:24:00 -0700185 if (!isShowing()) {
186 setWidth(width);
187 setHeight(height);
Svet Ganov374cae12017-05-10 13:42:33 -0700188 showAsDropDown(actualAnchor, offsetX, offsetY);
Felipe Leme4753bb02017-03-22 20:24:00 -0700189 } else {
Svet Ganov374cae12017-05-10 13:42:33 -0700190 update(actualAnchor, offsetX, offsetY, width, height);
Felipe Leme4753bb02017-03-22 20:24:00 -0700191 }
192 }
193
194 @Override
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
203 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
Felipe Leme8697a312017-05-30 09:35:17 -0700204 if (sVerbose) {
205 Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
206 + ", isShowing(): " + isShowing());
207 }
Felipe Leme4753bb02017-03-22 20:24:00 -0700208 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());
Svet Ganov77150c52017-09-22 17:19:04 -0700224 }
225
226 @Override
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
233 protected void detachFromAnchor() {
234 final View anchor = getAnchor();
235 if (anchor != null) {
236 anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
237 }
238 super.detachFromAnchor();
Felipe Leme4753bb02017-03-22 20:24:00 -0700239 }
240
241 @Override
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
258 public int getAnimationStyle() {
259 throw new IllegalStateException("You can't call this!");
260 }
261
262 @Override
263 public Drawable getBackground() {
264 throw new IllegalStateException("You can't call this!");
265 }
266
267 @Override
268 public View getContentView() {
269 throw new IllegalStateException("You can't call this!");
270 }
271
272 @Override
273 public float getElevation() {
274 throw new IllegalStateException("You can't call this!");
275 }
276
277 @Override
278 public Transition getEnterTransition() {
279 throw new IllegalStateException("You can't call this!");
280 }
281
282 @Override
283 public Transition getExitTransition() {
284 throw new IllegalStateException("You can't call this!");
285 }
286
287 @Override
288 public void setAnimationStyle(int animationStyle) {
289 throw new IllegalStateException("You can't call this!");
290 }
291
292 @Override
293 public void setBackgroundDrawable(Drawable background) {
294 throw new IllegalStateException("You can't call this!");
295 }
296
297 @Override
298 public void setContentView(View contentView) {
299 if (contentView != null) {
300 throw new IllegalStateException("You can't call this!");
301 }
302 }
303
304 @Override
305 public void setElevation(float elevation) {
306 throw new IllegalStateException("You can't call this!");
307 }
308
309 @Override
310 public void setEnterTransition(Transition enterTransition) {
311 throw new IllegalStateException("You can't call this!");
312 }
313
314 @Override
315 public void setExitTransition(Transition exitTransition) {
316 throw new IllegalStateException("You can't call this!");
317 }
318
319 @Override
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
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 */
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 */
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}