428 lines
16 KiB
Java
428 lines
16 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2017 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.view.autofill;
|
||
|
|
||
|
import static android.view.autofill.Helper.sVerbose;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.graphics.Rect;
|
||
|
import android.graphics.drawable.Drawable;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.RemoteException;
|
||
|
import android.transition.Transition;
|
||
|
import android.util.Log;
|
||
|
import android.view.View;
|
||
|
import android.view.View.OnTouchListener;
|
||
|
import android.view.ViewTreeObserver;
|
||
|
import android.view.WindowManager;
|
||
|
import android.view.WindowManager.LayoutParams;
|
||
|
import android.widget.PopupWindow;
|
||
|
import android.window.WindowMetricsHelper;
|
||
|
|
||
|
/**
|
||
|
* Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
|
||
|
* UI is rendered in a framework process, but it's controlled by the app.
|
||
|
*
|
||
|
* TODO(b/34943932): use an app surface control solution.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public class AutofillPopupWindow extends PopupWindow {
|
||
|
|
||
|
private static final String TAG = "AutofillPopupWindow";
|
||
|
|
||
|
private final WindowPresenter mWindowPresenter;
|
||
|
private WindowManager.LayoutParams mWindowLayoutParams;
|
||
|
private boolean mFullScreen;
|
||
|
|
||
|
private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
|
||
|
new View.OnAttachStateChangeListener() {
|
||
|
@Override
|
||
|
public void onViewAttachedToWindow(View v) {
|
||
|
/* ignore - handled by the super class */
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onViewDetachedFromWindow(View v) {
|
||
|
dismiss();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a popup window with a presenter owning the window and responsible for
|
||
|
* showing/hiding/updating the backing window. This can be useful of the window is
|
||
|
* being shown by another process while the popup logic is in the process hosting
|
||
|
* the anchor view.
|
||
|
* <p>
|
||
|
* Using this constructor means that the presenter completely owns the content of
|
||
|
* the window and the following methods manipulating the window content shouldn't
|
||
|
* be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
|
||
|
* {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
|
||
|
* {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
|
||
|
* {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
|
||
|
* {@link #setElevation(float)}, ({@link #getAnimationStyle()},
|
||
|
* {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
|
||
|
*/
|
||
|
public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
|
||
|
mWindowPresenter = new WindowPresenter(presenter);
|
||
|
|
||
|
setTouchModal(false);
|
||
|
setOutsideTouchable(true);
|
||
|
setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
|
||
|
setFocusable(true);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean hasContentView() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean hasDecorView() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected LayoutParams getDecorViewLayoutParams() {
|
||
|
return mWindowLayoutParams;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The effective {@code update} method that should be called by its clients.
|
||
|
*/
|
||
|
public void update(View anchor, int offsetX, int offsetY, int width, int height,
|
||
|
Rect virtualBounds) {
|
||
|
mFullScreen = width == LayoutParams.MATCH_PARENT;
|
||
|
// For no fullscreen autofill window, we want to show the window as system controlled one
|
||
|
// so it covers app windows, but it has to be an application type (so it's contained inside
|
||
|
// the application area). Hence, we set it to the application type with the highest z-order,
|
||
|
// which currently is TYPE_APPLICATION_ABOVE_SUB_PANEL.
|
||
|
// For fullscreen mode, autofill window is at the bottom of screen, it should not be
|
||
|
// clipped by app activity window. Fullscreen autofill window does not need to follow app
|
||
|
// anchor view position.
|
||
|
setWindowLayoutType(mFullScreen ? WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
|
||
|
: WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
|
||
|
// If we are showing the popup for a virtual view we use a fake view which
|
||
|
// delegates to the anchor but present itself with the same bounds as the
|
||
|
// virtual view. This ensures that the location logic in popup works
|
||
|
// symmetrically when the dropdown is below and above the anchor.
|
||
|
final View actualAnchor;
|
||
|
if (mFullScreen) {
|
||
|
offsetX = 0;
|
||
|
offsetY = 0;
|
||
|
// If it is not fullscreen height, put window at bottom. Computes absolute position.
|
||
|
// Note that we cannot easily change default gravity from Gravity.TOP to
|
||
|
// Gravity.BOTTOM because PopupWindow base class does not expose computeGravity().
|
||
|
final WindowManager windowManager = anchor.getContext()
|
||
|
.getSystemService(WindowManager.class);
|
||
|
final Rect windowBounds = WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout(
|
||
|
windowManager.getCurrentWindowMetrics());
|
||
|
width = windowBounds.width();
|
||
|
if (height != LayoutParams.MATCH_PARENT) {
|
||
|
offsetY = windowBounds.height() - height;
|
||
|
}
|
||
|
actualAnchor = anchor;
|
||
|
} else if (virtualBounds != null) {
|
||
|
final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top};
|
||
|
actualAnchor = new View(anchor.getContext()) {
|
||
|
@Override
|
||
|
public void getLocationOnScreen(int[] location) {
|
||
|
location[0] = mLocationOnScreen[0];
|
||
|
location[1] = mLocationOnScreen[1];
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getAccessibilityViewId() {
|
||
|
return anchor.getAccessibilityViewId();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ViewTreeObserver getViewTreeObserver() {
|
||
|
return anchor.getViewTreeObserver();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public IBinder getApplicationWindowToken() {
|
||
|
return anchor.getApplicationWindowToken();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public View getRootView() {
|
||
|
return anchor.getRootView();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getLayoutDirection() {
|
||
|
return anchor.getLayoutDirection();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void getWindowDisplayFrame(Rect outRect) {
|
||
|
anchor.getWindowDisplayFrame(outRect);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void addOnAttachStateChangeListener(
|
||
|
OnAttachStateChangeListener listener) {
|
||
|
anchor.addOnAttachStateChangeListener(listener);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void removeOnAttachStateChangeListener(
|
||
|
OnAttachStateChangeListener listener) {
|
||
|
anchor.removeOnAttachStateChangeListener(listener);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isAttachedToWindow() {
|
||
|
return anchor.isAttachedToWindow();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
|
||
|
return anchor.requestRectangleOnScreen(rectangle, immediate);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public IBinder getWindowToken() {
|
||
|
return anchor.getWindowToken();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
actualAnchor.setLeftTopRightBottom(
|
||
|
virtualBounds.left, virtualBounds.top,
|
||
|
virtualBounds.right, virtualBounds.bottom);
|
||
|
actualAnchor.setScrollX(anchor.getScrollX());
|
||
|
actualAnchor.setScrollY(anchor.getScrollY());
|
||
|
|
||
|
anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||
|
mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX);
|
||
|
mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY);
|
||
|
});
|
||
|
actualAnchor.setWillNotDraw(true);
|
||
|
} else {
|
||
|
actualAnchor = anchor;
|
||
|
}
|
||
|
|
||
|
if (!mFullScreen) {
|
||
|
// No fullscreen window animation is controlled by PopupWindow.
|
||
|
setAnimationStyle(-1);
|
||
|
} else if (height == LayoutParams.MATCH_PARENT) {
|
||
|
// Complete fullscreen autofill window has no animation.
|
||
|
setAnimationStyle(0);
|
||
|
} else {
|
||
|
// Slide half screen height autofill window from bottom.
|
||
|
setAnimationStyle(com.android.internal.R.style.AutofillHalfScreenAnimation);
|
||
|
}
|
||
|
if (!isShowing()) {
|
||
|
setWidth(width);
|
||
|
setHeight(height);
|
||
|
showAsDropDown(actualAnchor, offsetX, offsetY);
|
||
|
} else {
|
||
|
update(actualAnchor, offsetX, offsetY, width, height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void update(View anchor, WindowManager.LayoutParams params) {
|
||
|
final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
|
||
|
: View.LAYOUT_DIRECTION_LOCALE;
|
||
|
mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
|
||
|
layoutDirection);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean findDropDownPosition(View anchor, LayoutParams outParams,
|
||
|
int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
|
||
|
if (mFullScreen) {
|
||
|
// In fullscreen mode, don't need consider the anchor view.
|
||
|
outParams.x = xOffset;
|
||
|
outParams.y = yOffset;
|
||
|
outParams.width = width;
|
||
|
outParams.height = height;
|
||
|
outParams.gravity = gravity;
|
||
|
return false;
|
||
|
}
|
||
|
return super.findDropDownPosition(anchor, outParams, xOffset, yOffset,
|
||
|
width, height, gravity, allowScroll);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
|
||
|
if (sVerbose) {
|
||
|
Log.v(TAG, "showAsDropDown(): anchor=" + anchor + ", xoff=" + xoff + ", yoff=" + yoff
|
||
|
+ ", isShowing(): " + isShowing());
|
||
|
}
|
||
|
if (isShowing()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
setShowing(true);
|
||
|
setDropDown(true);
|
||
|
attachToAnchor(anchor, xoff, yoff, gravity);
|
||
|
final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
|
||
|
anchor.getWindowToken());
|
||
|
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
|
||
|
p.width, p.height, gravity, getAllowScrollingAnchorParent());
|
||
|
updateAboveAnchor(aboveAnchor);
|
||
|
p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
|
||
|
p.packageName = anchor.getContext().getPackageName();
|
||
|
mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
|
||
|
anchor.getLayoutDirection());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
|
||
|
super.attachToAnchor(anchor, xoff, yoff, gravity);
|
||
|
anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void detachFromAnchor() {
|
||
|
final View anchor = getAnchor();
|
||
|
if (anchor != null) {
|
||
|
anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
|
||
|
}
|
||
|
super.detachFromAnchor();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void dismiss() {
|
||
|
if (!isShowing() || isTransitioningToDismiss()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
setShowing(false);
|
||
|
setTransitioningToDismiss(true);
|
||
|
|
||
|
mWindowPresenter.hide(getTransitionEpicenter());
|
||
|
detachFromAnchor();
|
||
|
if (getOnDismissListener() != null) {
|
||
|
getOnDismissListener().onDismiss();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getAnimationStyle() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Drawable getBackground() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public View getContentView() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public float getElevation() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Transition getEnterTransition() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Transition getExitTransition() {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setBackgroundDrawable(Drawable background) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setContentView(View contentView) {
|
||
|
if (contentView != null) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setElevation(float elevation) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setEnterTransition(Transition enterTransition) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setExitTransition(Transition exitTransition) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setTouchInterceptor(OnTouchListener l) {
|
||
|
throw new IllegalStateException("You can't call this!");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Contract between the popup window and a presenter that is responsible for
|
||
|
* showing/hiding/updating the actual window.
|
||
|
*
|
||
|
* <p>This can be useful if the anchor is in one process and the backing window is owned by
|
||
|
* another process.
|
||
|
*/
|
||
|
private class WindowPresenter {
|
||
|
final IAutofillWindowPresenter mPresenter;
|
||
|
|
||
|
WindowPresenter(IAutofillWindowPresenter presenter) {
|
||
|
mPresenter = presenter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Shows the backing window.
|
||
|
*
|
||
|
* @param p The window layout params.
|
||
|
* @param transitionEpicenter The transition epicenter if animating.
|
||
|
* @param fitsSystemWindows Whether the content view should account for system decorations.
|
||
|
* @param layoutDirection The content layout direction to be consistent with the anchor.
|
||
|
*/
|
||
|
void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
|
||
|
int layoutDirection) {
|
||
|
try {
|
||
|
mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
|
||
|
} catch (RemoteException e) {
|
||
|
Log.w(TAG, "Error showing fill window", e);
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hides the backing window.
|
||
|
*
|
||
|
* @param transitionEpicenter The transition epicenter if animating.
|
||
|
*/
|
||
|
void hide(Rect transitionEpicenter) {
|
||
|
try {
|
||
|
mPresenter.hide(transitionEpicenter);
|
||
|
} catch (RemoteException e) {
|
||
|
Log.w(TAG, "Error hiding fill window", e);
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|