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