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     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      */
     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,
    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
    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) {
    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
    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();
    239     }
    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 }
    371