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