Make auto-fill UI robust
Now the autofill UI tracks the movement of the anchor view,
both real and virtual and while still preventing the filled
app from accessing the chooser UI. This was achieved by using
a popup window in the app process to determine the window
location and adding a window presenter interface to popup
window that controls the actual window addition, removal, and
update which is implemented by the system server.
Test: all autofill CTS tests pass
bug: 36392498
bug: 35708258
bug: 34943932
fixes: 36039182
fixes: 36493078
Change-Id: I0321913b2e2e759f4b17003bf85cb873e63a467c
diff --git a/Android.mk b/Android.mk
index e324a75..749242d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -326,6 +326,7 @@
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
core/java/android/view/autofill/IAutoFillManager.aidl \
core/java/android/view/autofill/IAutoFillManagerClient.aidl \
+ core/java/android/view/autofill/IAutofillWindowPresenter.aidl \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IDockedStackListener.aidl \
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4fd3f39..fa1de03 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,9 +17,12 @@
package android.app;
import android.metrics.LogMaker;
+import android.graphics.Rect;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
@@ -768,7 +771,6 @@
/*package*/ Configuration mCurrentConfig;
private SearchManager mSearchManager;
private MenuInflater mMenuInflater;
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
static final class NonConfigurationInstances {
Object activity;
@@ -850,6 +852,8 @@
private boolean mAutoFillResetNeeded;
+ private AutofillPopupWindow mAutofillPopupWindow;
+
private static native String getDlWarning();
/** Return the intent that started this activity. */
@@ -7190,60 +7194,7 @@
/** @hide */
@Override
- public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
- final View root = getWindow().getDecorView();
- final int itemCount = ids.size();
- int numApplied = 0;
- ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
-
- for (int i = 0; i < itemCount; i++) {
- final AutofillId id = ids.get(i);
- final AutofillValue value = values.get(i);
- final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.w(TAG, "autofill(): no View with id " + viewId);
- continue;
- }
- if (id.isVirtual()) {
- final int parentId = id.getViewId();
- if (virtualValues == null) {
- // Most likely there will be just one view with virtual children.
- virtualValues = new ArrayMap<>(1);
- }
- SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
- if (valuesByParent == null) {
- // We don't know the size yet, but usually it will be just a few fields...
- valuesByParent = new SparseArray<>(5);
- virtualValues.put(view, valuesByParent);
- }
- valuesByParent.put(id.getVirtualChildId(), value);
- } else {
- if (view.autofill(value)) {
- numApplied++;
- }
- }
- }
-
- if (virtualValues != null) {
- for (int i = 0; i < virtualValues.size(); i++) {
- final View parent = virtualValues.keyAt(i);
- final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
- if (parent.autofill(childrenValues)) {
- numApplied += childrenValues.size();
- }
- }
- }
-
- final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
- mMetricsLogger.write(log);
- }
-
- /** @hide */
- @Override
- public void authenticate(IntentSender intent, Intent fillInIntent) {
+ final public void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent) {
try {
startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
0, fillInIntent, 0, 0, null);
@@ -7254,10 +7205,47 @@
/** @hide */
@Override
- public void resetableStateAvailable() {
+ final public void autofillCallbackResetableStateAvailable() {
mAutoFillResetNeeded = true;
}
+ /** @hide */
+ @Override
+ final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
+ int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final Rect actualAnchorBounds = new Rect();
+ anchor.getBoundsOnScreen(actualAnchorBounds);
+
+ final int offsetX = (anchorBounds != null)
+ ? anchorBounds.left - actualAnchorBounds.left : 0;
+ int offsetY = (anchorBounds != null)
+ ? anchorBounds.top - actualAnchorBounds.top : 0;
+
+ final boolean wasShowing;
+
+ if (mAutofillPopupWindow == null) {
+ wasShowing = false;
+ mAutofillPopupWindow = new AutofillPopupWindow(presenter);
+ } else {
+ wasShowing = mAutofillPopupWindow.isShowing();
+ }
+ mAutofillPopupWindow.update(anchor, offsetX, offsetY, width, height, anchorBounds,
+ actualAnchorBounds);
+
+ return !wasShowing && mAutofillPopupWindow.isShowing();
+ }
+
+ /** @hide */
+ @Override
+ final public boolean autofillCallbackRequestHideFillUi() {
+ if (mAutofillPopupWindow == null) {
+ return false;
+ }
+ mAutofillPopupWindow.dismiss();
+ mAutofillPopupWindow = null;
+ return true;
+ }
+
/**
* If set to true, this indicates to the system that it should never take a
* screenshot of the activity to be used as a representation while it is not in a started state.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9ed6371..b4d2c6b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,14 +26,20 @@
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Rect;
+import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManagerGlobal;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -83,7 +89,7 @@
/** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000;
/** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
- @NonNull private final Rect mTempRect = new Rect();
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final IAutoFillManager mService;
private IAutoFillManagerClient mServiceClient;
@@ -98,25 +104,37 @@
/** @hide */
public interface AutofillClient {
/**
- * Asks the client to perform an autofill.
- *
- * @param ids The values to autofill
- * @param values The values to autofill
- */
- void autofill(List<AutofillId> ids, List<AutofillValue> values);
-
- /**
* Asks the client to start an authentication flow.
*
* @param intent The authentication intent.
* @param fillInIntent The authentication fill-in intent.
*/
- void authenticate(IntentSender intent, Intent fillInIntent);
+ void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
/**
* Tells the client this manager has state to be reset.
*/
- void resetableStateAvailable();
+ void autofillCallbackResetableStateAvailable();
+
+ /**
+ * Request showing the autofill UI.
+ *
+ * @param anchor The real view the UI needs to anchor to.
+ * @param width The width of the fill UI content.
+ * @param height The height of the fill UI content.
+ * @param virtualBounds The bounds of the virtual decendant of the anchor.
+ * @param presenter The presenter that controls the fill UI window.
+ * @return Whether the UI was shown.
+ */
+ boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
+ @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
+
+ /**
+ * Request hiding the autofill UI.
+ *
+ * @return Whether the UI was hidden.
+ */
+ boolean autofillCallbackRequestHideFillUi();
}
/**
@@ -156,12 +174,10 @@
return;
}
- final Rect bounds = mTempRect;
- view.getBoundsOnScreen(bounds);
final AutofillId id = getAutofillId(view);
final AutofillValue value = view.getAutofillValue();
- startSession(id, view.getWindowToken(), bounds, value, FLAG_MANUAL_REQUEST);
+ startSession(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
}
/**
@@ -202,17 +218,15 @@
return;
}
- final Rect bounds = mTempRect;
- view.getBoundsOnScreen(bounds);
final AutofillId id = getAutofillId(view);
final AutofillValue value = view.getAutofillValue();
if (!mHasSession) {
// Starts new session.
- startSession(id, view.getWindowToken(), bounds, value, 0);
+ startSession(id, view.getWindowToken(), null, value, 0);
} else {
// Update focus on existing session.
- updateSession(id, bounds, value, FLAG_VIEW_ENTERED);
+ updateSession(id, null, value, FLAG_VIEW_ENTERED);
}
}
@@ -389,7 +403,7 @@
mCallback != null, flags, mContext.getOpPackageName());
AutofillClient client = getClient();
if (client != null) {
- client.resetableStateAvailable();
+ client.autofillCallbackResetableStateAvailable();
}
mHasSession = true;
} catch (RemoteException e) {
@@ -490,28 +504,114 @@
}
}
- private void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
- if (mCallback == null) return;
- if (id == null) {
- Log.w(TAG, "onAutofillEvent(): no id for event " + event);
+ private void requestShowFillUi(IBinder windowToken, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final View anchor = findAchorView(windowToken, id);
+ if (anchor == null) {
+ return;
+ }
+ if (getClient().autofillCallbackRequestShowFillUi(anchor, width, height,
+ anchorBounds, presenter) && mCallback != null) {
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_SHOWN);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
+ }
+ }
+ }
+
+ private void handleAutofill(IBinder windowToken, List<AutofillId> ids,
+ List<AutofillValue> values) {
+ final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
+ if (root == null) {
return;
}
+ final int itemCount = ids.size();
+ int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+
+ for (int i = 0; i < itemCount; i++) {
+ final AutofillId id = ids.get(i);
+ final AutofillValue value = values.get(i);
+ final int viewId = id.getViewId();
+ final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ if (view == null) {
+ Log.w(TAG, "autofill(): no View with id " + viewId);
+ continue;
+ }
+ if (id.isVirtual()) {
+ if (virtualValues == null) {
+ // Most likely there will be just one view with virtual children.
+ virtualValues = new ArrayMap<>(1);
+ }
+ SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+ if (valuesByParent == null) {
+ // We don't know the size yet, but usually it will be just a few fields...
+ valuesByParent = new SparseArray<>(5);
+ virtualValues.put(view, valuesByParent);
+ }
+ valuesByParent.put(id.getVirtualChildId(), value);
+ } else {
+ if (view.autofill(value)) {
+ numApplied++;
+ }
+ }
+ }
+
+ if (virtualValues != null) {
+ for (int i = 0; i < virtualValues.size(); i++) {
+ final View parent = virtualValues.keyAt(i);
+ final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+ if (parent.autofill(childrenValues)) {
+ numApplied += childrenValues.size();
+ }
+ }
+ }
+
+ final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
+ log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
+ mMetricsLogger.write(log);
+ }
+
+ private void requestHideFillUi(IBinder windowToken, AutofillId id) {
+ if (getClient().autofillCallbackRequestHideFillUi() && mCallback != null) {
+ final View anchor = findAchorView(windowToken, id);
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_HIDDEN);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
+ }
+ }
+ }
+
+ private void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+ if (mCallback != null) {
+ final View anchor = findAchorView(windowToken, id);
+ if (id.isVirtual()) {
+ mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+ AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ } else {
+ mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ }
+ }
+ }
+
+ private View findAchorView(IBinder windowToken, AutofillId id) {
final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
if (root == null) {
- Log.w(TAG, "onAutofillEvent() for " + id + ": root view gone");
- return;
+ Log.w(TAG, "no window with token " + windowToken);
+ return null;
}
final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
if (view == null) {
- Log.w(TAG, "onAutofillEvent() for " + id + ": view gone");
- return;
+ Log.w(TAG, "no view with id " + id);
+ return null;
}
- if (id.isVirtual()) {
- mCallback.onAutofillEvent(view, id.getVirtualChildId(), event);
- } else {
- mCallback.onAutofillEvent(view, event);
- }
+ return view;
}
/**
@@ -590,16 +690,14 @@
}
@Override
- public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
+ public void autofill(IBinder windowToken, List<AutofillId> ids,
+ List<AutofillValue> values) {
// TODO(b/33197203): must keep the dataset so subsequent calls pass the same
// dataset.extras to service
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.mContext.getMainThreadHandler().post(() -> {
- if (afm.getClient() != null) {
- afm.getClient().autofill(ids, values);
- }
- });
+ afm.mContext.getMainThreadHandler().post(() ->
+ afm.handleAutofill(windowToken, ids, values));
}
}
@@ -609,19 +707,45 @@
if (afm != null) {
afm.mContext.getMainThreadHandler().post(() -> {
if (afm.getClient() != null) {
- afm.getClient().authenticate(intent, fillInIntent);
+ afm.getClient().autofillCallbackAuthenticate(intent, fillInIntent);
}
});
}
}
@Override
- public void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
+ public void requestShowFillUi(IBinder windowToken, AutofillId id,
+ int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.mContext.getMainThreadHandler().post(() -> {
if (afm.getClient() != null) {
- afm.onAutofillEvent(windowToken, id, event);
+ afm.requestShowFillUi(windowToken, id, width,
+ height, anchorBounds, presenter);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void requestHideFillUi(IBinder windowToken, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.mContext.getMainThreadHandler().post(() -> {
+ if (afm.getClient() != null) {
+ afm.requestHideFillUi(windowToken, id);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.mContext.getMainThreadHandler().post(() -> {
+ if (afm.getClient() != null) {
+ afm.notifyNoFillUi(windowToken, id);
}
});
}
diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java
new file mode 100644
index 0000000..056e540
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillPopupWindow.java
@@ -0,0 +1,260 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+/**
+ * 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;
+
+ /**
+ * 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);
+
+ setOutsideTouchable(true);
+ setInputMethodMode(INPUT_METHOD_NEEDED);
+ }
+
+ @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 anchorBounds, Rect actualAnchorBounds) {
+ if (!isShowing()) {
+ setWidth(width);
+ setHeight(height);
+ showAsDropDown(anchor, offsetX, offsetY);
+ } else {
+ update(anchor, offsetX, offsetY, width, height);
+ }
+
+ if (anchorBounds != null && mWindowLayoutParams.y > anchorBounds.bottom) {
+ offsetY = anchorBounds.bottom - actualAnchorBounds.bottom;
+ update(anchor, 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
+ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+ 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());
+ return;
+ }
+
+ @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 setAnimationStyle(int animationStyle) {
+ 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();
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index eabf6b1..7bea174 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -20,9 +20,11 @@
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Rect;
import android.os.IBinder;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
/**
* Object running in the application process and responsible for autofilling it.
@@ -38,7 +40,7 @@
/**
* Autofills the activity with the contents of a dataset.
*/
- void autofill(in List<AutofillId> ids, in List<AutofillValue> values);
+ void autofill(in IBinder windowToken, in List<AutofillId> ids, in List<AutofillValue> values);
/**
* Authenticates a fill response or a data set.
@@ -46,7 +48,18 @@
void authenticate(in IntentSender intent, in Intent fillInIntent);
/**
- * Notifies the client when the auto-fill UI changed.
+ * Requests showing the fill UI.
*/
- void onAutofillEvent(in IBinder windowToken, in AutofillId id, int event);
+ void requestShowFillUi(in IBinder windowToken, in AutofillId id, int width,
+ int height, in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+ /**
+ * Requests hiding the fill UI.
+ */
+ void requestHideFillUi(in IBinder windowToken, in AutofillId id);
+
+ /**
+ * Nitifies no fill UI will be shown.
+ */
+ void notifyNoFillUi(in IBinder windowToken, in AutofillId id);
}
diff --git a/core/java/android/view/autofill/IAutofillWindowPresenter.aidl b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
new file mode 100644
index 0000000..172b992
--- /dev/null
+++ b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * This is a handle to the FillUi for controlling
+ * when its window should be shown and hidden.
+ *
+ * {@hide}
+ */
+oneway interface IAutofillWindowPresenter {
+ void show(in WindowManager.LayoutParams p, in Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection);
+ void hide(in Rect transitionEpicenter);
+}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 9f10531..b63b899 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -209,7 +209,7 @@
private int mGravity = Gravity.NO_GRAVITY;
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
- com.android.internal.R.attr.state_above_anchor
+ com.android.internal.R.attr.state_above_anchor
};
private final OnAttachStateChangeListener mOnAnchorDetachedListener =
@@ -589,7 +589,6 @@
mIgnoreCheekPress = true;
}
-
/**
* <p>Change the animation style resource for this popup.</p>
*
@@ -860,6 +859,11 @@
mAllowScrollingAnchorParent = enabled;
}
+ /** @hide */
+ protected final boolean getAllowScrollingAnchorParent() {
+ return mAllowScrollingAnchorParent;
+ }
+
/**
* <p>Indicates whether the popup window supports splitting touches.</p>
*
@@ -960,6 +964,11 @@
mLayoutInsetDecor = enabled;
}
+ /** @hide */
+ protected final boolean isLayoutInsetDecor() {
+ return mLayoutInsetDecor;
+ }
+
/**
* Set the layout type for this window.
* <p>
@@ -1122,6 +1131,26 @@
return mIsShowing;
}
+ /** @hide */
+ protected final void setShowing(boolean isShowing) {
+ mIsShowing = isShowing;
+ }
+
+ /** @hide */
+ protected final void setDropDown(boolean isDropDown) {
+ mIsDropdown = isDropDown;
+ }
+
+ /** @hide */
+ protected final void setTransitioningToDismiss(boolean transitioningToDismiss) {
+ mIsTransitioningToDismiss = transitioningToDismiss;
+ }
+
+ /** @hide */
+ protected final boolean isTransitioningToDismiss() {
+ return mIsTransitioningToDismiss;
+ }
+
/**
* <p>
* Display the content view in a popup window at the specified location. If the popup window
@@ -1232,7 +1261,7 @@
* @see #dismiss()
*/
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
- if (isShowing() || mContentView == null) {
+ if (isShowing() || !hasContentView()) {
return;
}
@@ -1255,7 +1284,8 @@
invokePopup(p);
}
- private void updateAboveAnchor(boolean aboveAnchor) {
+ /** @hide */
+ protected final void updateAboveAnchor(boolean aboveAnchor) {
if (aboveAnchor != mAboveAnchor) {
mAboveAnchor = aboveAnchor;
@@ -1426,8 +1456,10 @@
* @param token the window token used to bind the popup's window
*
* @return the layout parameters to pass to the window manager
+ *
+ * @hide
*/
- private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
+ protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
@@ -1510,7 +1542,7 @@
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
if (mAttachedInDecor) {
- curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
}
return curFlags;
}
@@ -1543,8 +1575,10 @@
* @param allowScroll whether the anchor view's parent may be scrolled
* when the popup window doesn't fit on screen
* @return true if the popup is translated upwards to fit on screen
+ *
+ * @hide
*/
- private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
+ protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
final int anchorHeight = anchor.getHeight();
final int anchorWidth = anchor.getWidth();
@@ -1853,7 +1887,7 @@
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (!isShowing() || mIsTransitioningToDismiss) {
+ if (!isShowing() || isTransitioningToDismiss()) {
return;
}
@@ -1923,8 +1957,10 @@
*
* @return the window-relative epicenter bounds to be used by enter and
* exit transitions
+ *
+ * @hide
*/
- private Rect getTransitionEpicenter() {
+ protected final Rect getTransitionEpicenter() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
final View decor = mDecorView;
if (anchor == null || decor == null) {
@@ -1981,6 +2017,11 @@
mOnDismissListener = onDismissListener;
}
+ /** @hide */
+ protected final OnDismissListener getOnDismissListener() {
+ return mOnDismissListener;
+ }
+
/**
* Updates the state of the popup window, if it is currently being displayed,
* from the currently set state.
@@ -1996,12 +2037,11 @@
* </ul>
*/
public void update() {
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
boolean update = false;
@@ -2024,11 +2064,16 @@
}
if (update) {
- setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mDecorView, p);
+ update(mAnchor.get(), p);
}
}
+ /** @hide */
+ protected void update(View anchor, WindowManager.LayoutParams params) {
+ setLayoutDirectionFromAnchor();
+ mWindowManager.updateViewLayout(mDecorView, params);
+ }
+
/**
* Updates the dimension of the popup window.
* <p>
@@ -2039,8 +2084,7 @@
* @param height the new height in pixels, must be >= 0 or -1 to ignore
*/
public void update(int width, int height) {
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
update(p.x, p.y, width, height, false);
}
@@ -2086,12 +2130,11 @@
setHeight(height);
}
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
- final WindowManager.LayoutParams p =
- (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
boolean update = force;
@@ -2135,19 +2178,34 @@
update = true;
}
- int newAccessibilityIdOfAnchor =
- (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1;
+ final View anchor = mAnchor.get();
+ final int newAccessibilityIdOfAnchor = (anchor != null)
+ ? anchor.getAccessibilityViewId() : -1;
if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
update = true;
}
if (update) {
- setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mDecorView, p);
+ update(anchor, p);
}
}
+ /** @hide */
+ protected boolean hasContentView() {
+ return mContentView != null;
+ }
+
+ /** @hide */
+ protected boolean hasDecorView() {
+ return mDecorView != null;
+ }
+
+ /** @hide */
+ protected WindowManager.LayoutParams getDecorViewLayoutParams() {
+ return (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ }
+
/**
* Updates the position and the dimension of the popup window.
* <p>
@@ -2185,7 +2243,7 @@
private void update(View anchor, boolean updateLocation, int xoff, int yoff,
int width, int height) {
- if (!isShowing() || mContentView == null) {
+ if (!isShowing() || !hasContentView()) {
return;
}
@@ -2201,7 +2259,7 @@
mAnchorYoff = yoff;
}
- final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams();
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
final int oldGravity = p.gravity;
final int oldWidth = p.width;
final int oldHeight = p.height;
@@ -2243,7 +2301,8 @@
public void onDismiss();
}
- private void detachFromAnchor() {
+ /** @hide */
+ protected final void detachFromAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
if (anchor != null) {
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2262,7 +2321,8 @@
mIsAnchorRootAttached = false;
}
- private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ /** @hide */
+ protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
detachFromAnchor();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2287,9 +2347,8 @@
private void alignToAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
- if (anchor != null && anchor.isAttachedToWindow() && mDecorView != null) {
- final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mDecorView.getLayoutParams();
+ if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
+ final WindowManager.LayoutParams p = getDecorViewLayoutParams();
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
p.width, p.height, mAnchoredGravity, false));
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index a77e533..72d37ad 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -365,7 +365,6 @@
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
- bounds = Preconditions.checkNotNull(bounds, "bounds");
packageName = Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6fb9f7c..4d78350 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -71,8 +71,7 @@
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
-import android.view.autofill.AutofillManager.AutofillCallback;
-
+import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -298,17 +297,17 @@
}
void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken,
- @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds,
- @Nullable AutofillValue value, boolean hasCallback, int flags,
- @NonNull String packageName) {
+ @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
+ @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
+ int flags, @NonNull String packageName) {
if (!isEnabled()) {
return;
}
final String historyItem = "s=" + mInfo.getServiceInfo().packageName
+ " u=" + mUserId + " a=" + activityToken
-
- + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags;
+ + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback
+ + " f=" + flags;
mRequestsHistory.log(historyItem);
// TODO(b/33197203): Handle partitioning
@@ -320,7 +319,7 @@
final Session newSession = createSessionByTokenLocked(activityToken,
windowToken, appCallbackToken, hasCallback, flags, packageName);
- newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION);
+ newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION);
}
void finishSessionLocked(IBinder activityToken) {
@@ -388,7 +387,7 @@
return newSession;
}
- void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds,
+ void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect virtualBounds,
AutofillValue value, int flags) {
final Session session = mSessions.get(activityToken);
if (session == null) {
@@ -398,7 +397,7 @@
return;
}
- session.updateLocked(autofillId, bounds, value, flags);
+ session.updateLocked(autofillId, virtualBounds, value, flags);
}
private void handleSessionSave(IBinder activityToken) {
@@ -508,8 +507,8 @@
/**
* Called when the fill UI is ready to be shown for this view.
*/
- void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
- AutofillId focusedId, @Nullable AutofillValue value);
+ void onFillReady(FillResponse fillResponse, AutofillId focusedId,
+ @Nullable AutofillValue value);
}
final AutofillId mId;
@@ -522,7 +521,9 @@
Intent mAuthIntent;
private AutofillValue mAutofillValue;
- private Rect mBounds;
+
+ // Bounds if a virtual view, null otherwise
+ private Rect mVirtualBounds;
private boolean mValueUpdated;
@@ -555,12 +556,12 @@
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that
// it can change the value and update the UI; similarly, should replace code that
// directly sets mAutoFilLValue to use encapsulation.
- void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) {
+ void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
if (autofillValue != null) {
mAutofillValue = autofillValue;
}
- if (bounds != null) {
- mBounds = bounds;
+ if (virtualBounds != null) {
+ mVirtualBounds = virtualBounds;
}
maybeCallOnFillReady();
@@ -568,19 +569,19 @@
/**
* Calls {@link
- * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the
+ * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
* fill UI is ready to be displayed (i.e. when response and bounds are set).
*/
void maybeCallOnFillReady() {
if (mResponse != null && (mResponse.getAuthentication() != null
- || mResponse.getDatasets() != null) && mBounds != null) {
- mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue);
+ || mResponse.getDatasets() != null)) {
+ mListener.onFillReady(mResponse, mId, mAutofillValue);
}
}
@Override
public String toString() {
- return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds
+ return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
+ ", updated = " + mValueUpdated + "]";
}
@@ -588,7 +589,7 @@
pw.print(prefix); pw.print("id:" ); pw.println(mId);
pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
- pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+ pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
}
}
@@ -817,8 +818,24 @@
// AutoFillUiCallback
@Override
- public void onEvent(AutofillId id, int event) {
- mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event));
+ public void requestShowFillUi(AutofillId id, int width, int height,
+ IAutofillWindowPresenter presenter) {
+ try {
+ mClient.requestShowFillUi(mWindowToken, id, width, height,
+ mCurrentViewState.mVirtualBounds, presenter);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to show fill UI", e);
+ }
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestHideFillUi(AutofillId id) {
+ try {
+ mClient.requestHideFillUi(mWindowToken, id);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to hide fill UI", e);
+ }
}
public void setAuthenticationResultLocked(Bundle data) {
@@ -835,10 +852,11 @@
processResponseLocked(mCurrentResponse);
} else if (result instanceof Dataset) {
Dataset dataset = (Dataset) result;
- mCurrentResponse.getDatasets().remove(mAutoFilledDataset);
- mCurrentResponse.getDatasets().add(dataset);
- mAutoFilledDataset = dataset;
- processResponseLocked(mCurrentResponse);
+ final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
+ if (index >= 0) {
+ mCurrentResponse.getDatasets().set(index, dataset);
+ autoFill(dataset);
+ }
}
}
}
@@ -1009,7 +1027,7 @@
mRemoteFillService.onSaveRequest(mStructure, extras);
}
- void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
+ void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
// TODO(b/33197203): ignoring because we don't support partitions yet
Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
@@ -1025,7 +1043,7 @@
if ((flags & FLAG_START_SESSION) != 0) {
// View is triggering autofill.
mCurrentViewState = viewState;
- viewState.update(value, bounds);
+ viewState.update(value, virtualBounds);
return;
}
@@ -1065,7 +1083,7 @@
}
// If the ViewState is ready to be displayed, onReady() will be called.
- viewState.update(value, bounds);
+ viewState.update(value, virtualBounds);
// TODO(b/33197203): Remove when there is a response per activity.
if (mCurrentResponse != null) {
@@ -1087,23 +1105,14 @@
}
@Override
- public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
- AutofillId filledId, @Nullable AutofillValue value) {
+ public void onFillReady(FillResponse response, AutofillId filledId,
+ @Nullable AutofillValue value) {
String filterText = null;
if (value != null && value.isText()) {
filterText = value.getTextValue().toString();
}
- getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName);
- }
-
- private void notifyChangeToClient(AutofillId id, int event) {
- if (!mHasCallback) return;
- try {
- mClient.onAutofillEvent(mWindowToken, id, event);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e);
- }
+ getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
}
private void notifyUnavailableToClient() {
@@ -1112,7 +1121,13 @@
Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
return;
}
- notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+ if (!mHasCallback) return;
+ try {
+ mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
+ + " id=" + mCurrentViewState.mId, e);
+ }
}
private void processResponseLocked(FillResponse response) {
@@ -1212,7 +1227,7 @@
if (DEBUG) {
Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
}
- mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues());
+ mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
} catch (RemoteException e) {
Slog.w(TAG, "Error autofilling activity: " + e);
}
@@ -1221,7 +1236,7 @@
private AutoFillUI getUiForShowing() {
synchronized (mLock) {
- mUi.setCallback(this, mWindowToken);
+ mUi.setCallback(this);
return mUi;
}
}
@@ -1261,8 +1276,7 @@
private void destroyLocked() {
mRemoteFillService.destroy();
- mUi.setCallback(null, null);
-
+ mUi.setCallback(null);
mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
mPackageName);
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 299b456..003c8f1 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -43,7 +43,6 @@
import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
-import com.android.server.autofill.AutofillManagerServiceImpl.Session;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -89,10 +88,13 @@
private PendingRequest mPendingRequest;
public interface FillServiceCallbacks {
- void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName);
- void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+ void onFillRequestSuccess(@Nullable FillResponse response,
+ @NonNull String servicePackageName);
+ void onFillRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName);
void onSaveRequestSuccess(@NonNull String servicePackageName);
- void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+ void onSaveRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName);
void onServiceDied(RemoteFillService service);
void onDisableSelf();
}
@@ -243,14 +245,10 @@
}
mBinding = false;
if (isBound()) {
- // TODO(b/33197203): synchronize access instead?
- // Need to double check if it's null, since it could be set on onServiceDisconnected()
- if (mAutoFillService != null) {
- try {
- mAutoFillService.onInit(null);
- } catch (Exception e) {
- Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
- }
+ try {
+ mAutoFillService.onInit(null);
+ } catch (Exception e) {
+ Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
}
if (mAutoFillService != null) {
mAutoFillService.asBinder().unlinkToDeath(this, 0);
@@ -323,19 +321,13 @@
handleBinderDied();
return;
}
-
try {
- // TODO(b/33197203): synchronize access instead?
- // Need to double check if it's null, since it could be set on
- // onServiceDisconnected()
- if (mAutoFillService != null) {
- mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
- @Override
- public void disableSelf() {
- mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
- }
- });
- }
+ mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
+ @Override
+ public void disableSelf() {
+ mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
+ }
+ });
} catch (RemoteException e) {
Slog.w(LOG_TAG, "Exception calling onConnected(): " + e);
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 375a726..2555cee 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -15,19 +15,14 @@
*/
package com.android.server.autofill.ui;
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
-
import static com.android.server.autofill.ui.Helper.DEBUG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.IntentSender;
-import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Handler;
-import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
@@ -35,6 +30,7 @@
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
+import android.view.autofill.IAutofillWindowPresenter;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
@@ -62,7 +58,6 @@
private @Nullable SaveUi mSaveUi;
private @Nullable AutoFillUiCallback mCallback;
- private @Nullable IBinder mWindowToken;
private int mSaveTimeoutMs = (int) (5 * DateUtils.SECOND_IN_MILLIS);
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -72,20 +67,20 @@
void fill(@NonNull Dataset dataset);
void save();
void cancelSave();
- void onEvent(AutofillId id, int event);
+ void requestShowFillUi(AutofillId id, int width, int height,
+ IAutofillWindowPresenter presenter);
+ void requestHideFillUi(AutofillId id);
}
public AutoFillUI(@NonNull Context context) {
mContext = context;
}
- public void setCallback(@Nullable AutoFillUiCallback callback,
- @Nullable IBinder windowToken) {
+ public void setCallback(@Nullable AutoFillUiCallback callback) {
mHandler.post(() -> {
- if (mCallback != callback || mWindowToken != windowToken) {
+ if (mCallback != callback) {
hideAllUiThread();
mCallback = callback;
- mWindowToken = windowToken;
}
});
}
@@ -109,12 +104,7 @@
* Hides the fill UI.
*/
public void hideFillUi(AutofillId id) {
- mHandler.post(() -> {
- hideFillUiUiThread();
- if (mCallback != null) {
- mCallback.onEvent(id, EVENT_INPUT_HIDDEN);
- }
- });
+ mHandler.post(this::hideFillUiUiThread);
}
/**
@@ -135,36 +125,17 @@
}
/**
- * Updates the position of the fill UI.
- *
- * @param anchoredBounds The bounds of the anchor view.
- */
- public void updateFillUi(@NonNull Rect anchoredBounds) {
- mHandler.post(() -> {
- if (!hasCallback()) {
- return;
- }
- hideSaveUiUiThread();
- if (mFillUi != null) {
- mFillUi.update(anchoredBounds);
- }
- });
- }
-
- /**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param focusedId the currently focused field
* @param response the current fill response
- * @param anchorBounds bounds of the focused view
* @param filterText text of the view to be filled
* @param packageName package name of the activity that is filled
*/
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
- @NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) {
+ @Nullable String filterText, @NonNull String packageName) {
if (DEBUG) {
- Slog.d(TAG, "showFillUi(): id=" + focusedId + ", bounds=" + anchorBounds + " filter="
- + filterText);
+ Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText);
}
final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
.setPackageName(packageName)
@@ -179,7 +150,7 @@
}
hideAllUiThread();
mFillUi = new FillUi(mContext, response, focusedId,
- mWindowToken, anchorBounds, filterText, new FillUi.Callback() {
+ filterText, new FillUi.Callback() {
@Override
public void onResponsePicked(FillResponse response) {
log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
@@ -211,8 +182,22 @@
}
mMetricsLogger.write(log);
}
+
+ @Override
+ public void requestShowFillUi(int width, int height,
+ IAutofillWindowPresenter windowPresenter) {
+ if (mCallback != null) {
+ mCallback.requestShowFillUi(focusedId, width, height, windowPresenter);
+ }
+ }
+
+ @Override
+ public void requestHideFillUi() {
+ if (mCallback != null) {
+ mCallback.requestHideFillUi(focusedId);
+ }
+ }
});
- mCallback.onEvent(focusedId, EVENT_INPUT_SHOWN);
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 98a02f2..d38fb96 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -18,14 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
-import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.util.Slog;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -34,11 +30,13 @@
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.RemoteViews;
import com.android.internal.R;
+import com.android.server.UiThread;
import libcore.util.Objects;
import java.io.PrintWriter;
@@ -54,9 +52,13 @@
void onDatasetPicked(@NonNull Dataset dataset);
void onCanceled();
void onDestroy();
+ void requestShowFillUi(int width, int height,
+ IAutofillWindowPresenter windowPresenter);
+ void requestHideFillUi();
}
- private final Rect mAnchorBounds = new Rect();
+ private final @NonNull AutofillWindowPresenter mWindowPresenter =
+ new AutofillWindowPresenter();
private final @NonNull AnchoredWindow mWindow;
@@ -75,10 +77,8 @@
private boolean mDestroyed;
FillUi(@NonNull Context context, @NonNull FillResponse response,
- @NonNull AutofillId focusedViewId, @NonNull IBinder windowToken,
- @NonNull Rect anchorBounds, @Nullable String filterText,
+ @NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText,
@NonNull Callback callback) {
- mAnchorBounds.set(anchorBounds);
mCallback = callback;
mAccessibilityTitle = context.getString(R.string.autofill_picker_accessibility_title);
@@ -104,8 +104,8 @@
mContentWidth = content.getMeasuredWidth();
mContentHeight = content.getMeasuredHeight();
- mWindow = new AnchoredWindow(windowToken, content);
- mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+ mWindow = new AnchoredWindow(content);
+ mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
} else {
final int datasetCount = response.getDatasets().size();
final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
@@ -153,15 +153,7 @@
}
applyNewFilterText();
- mWindow = new AnchoredWindow(windowToken, mListView);
- }
- }
-
- public void update(@NonNull Rect anchorBounds) {
- throwIfDestroyed();
- if (!mAnchorBounds.equals(anchorBounds)) {
- mAnchorBounds.set(anchorBounds);
- mWindow.show(mContentWidth, mContentHeight, anchorBounds);
+ mWindow = new AnchoredWindow(mListView);
}
}
@@ -171,10 +163,10 @@
return;
}
if (count <= 0) {
- mWindow.hide();
+ mCallback.requestHideFillUi();
} else {
if (updateContentSize()) {
- mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+ mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
}
if (mAdapter.getCount() > VISIBLE_OPTIONS_MAX_COUNT) {
mListView.setVerticalScrollBarEnabled(true);
@@ -209,7 +201,7 @@
public void destroy() {
throwIfDestroyed();
mCallback.onDestroy();
- mWindow.hide();
+ mCallback.requestHideFillUi();
mDestroyed = true;
}
@@ -285,33 +277,61 @@
}
}
+ private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
+ @Override
+ public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+ boolean fitsSystemWindows, int layoutDirection) {
+ UiThread.getHandler().post(() -> mWindow.show(p));
+ }
+
+ @Override
+ public void hide(Rect transitionEpicenter) {
+ UiThread.getHandler().post(mWindow::hide);
+ }
+ }
+
final class AnchoredWindow implements View.OnTouchListener {
- private final Point mTempPoint = new Point();
-
private final WindowManager mWm;
-
- private final IBinder mActivityToken;
private final View mContentView;
+ private boolean mShowing;
/**
* Constructor.
*
- * @param activityToken token to pass to window manager
* @param contentView content of the window
*/
- AnchoredWindow(IBinder activityToken, View contentView) {
+ AnchoredWindow(View contentView) {
mWm = contentView.getContext().getSystemService(WindowManager.class);
- mActivityToken = activityToken;
mContentView = contentView;
}
/**
+ * Shows the window.
+ */
+ public void show(WindowManager.LayoutParams params) {
+ try {
+ if (!mShowing) {
+ params.accessibilityTitle = mAccessibilityTitle;
+ mWm.addView(mContentView, params);
+ mContentView.setOnTouchListener(this);
+ mShowing = true;
+ } else {
+ mWm.updateViewLayout(mContentView, params);
+ }
+ } catch (WindowManager.BadTokenException e) {
+ Slog.i(TAG, "Filed with with token " + params.token + " gone.");
+ mCallback.onDestroy();
+ }
+ }
+
+ /**
* Hides the window.
*/
void hide() {
- if (mContentView.isAttachedToWindow()) {
+ if (mShowing) {
mContentView.setOnTouchListener(null);
mWm.removeView(mContentView);
+ mShowing = false;
}
}
@@ -324,82 +344,9 @@
}
return false;
}
-
- public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) {
- try {
- // TODO: temporary workaround to avoud system_server crashes.
- unsafelyShow(desiredWidth, desiredHeight, anchorBounds);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error showing Anchored window: w=" + desiredWidth + ", h="
- + desiredHeight + ", b=" + anchorBounds, e);
- }
- }
-
- private void unsafelyShow(int desiredWidth, int desiredHeight, Rect anchorBounds) {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-
- params.setTitle("FillUi");
- params.token = mActivityToken;
- params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
- params.accessibilityTitle = mAccessibilityTitle;
-
- mWm.getDefaultDisplay().getRealSize(mTempPoint);
- final int screenWidth = mTempPoint.x;
- final int screenHeight = mTempPoint.y;
-
- // Try to place the window at the start of the anchor view if
- // there is space to fit the content, otherwise fit as much of
- // the window as possible moving it to the left using all available
- // screen width.
- params.x = Math.min(anchorBounds.left, Math.max(screenWidth - desiredWidth, 0));
- params.width = Math.min(screenWidth, desiredWidth);
-
- // Try to fit below using all available space with top-start gravity
- // and if that fails try to fit above using all available space with
- // bottom-start gravity.
- final int verticalSpaceBelow = screenHeight - anchorBounds.bottom;
- if (desiredHeight <= verticalSpaceBelow) {
- // Fits below bounds.
- params.height = desiredHeight;
- params.gravity = Gravity.TOP | Gravity.START;
- params.y = anchorBounds.bottom;
- } else {
- final int verticalSpaceAbove = anchorBounds.top;
- if (desiredHeight <= verticalSpaceAbove) {
- // Fits above bounds.
- params.height = desiredHeight;
- params.gravity = Gravity.BOTTOM | Gravity.START;
- params.y = anchorBounds.top + desiredHeight;
- } else {
- // Pick above/below based on which has the most space.
- if (verticalSpaceBelow >= verticalSpaceAbove) {
- params.height = verticalSpaceBelow;
- params.gravity = Gravity.TOP | Gravity.START;
- params.y = anchorBounds.bottom;
- } else {
- params.height = verticalSpaceAbove;
- params.gravity = Gravity.BOTTOM | Gravity.START;
- params.y = anchorBounds.top + desiredHeight;
- }
- }
- }
-
- if (!mContentView.isAttachedToWindow()) {
- mWm.addView(mContentView, params);
- mContentView.setOnTouchListener(this);
- } else {
- mWm.updateViewLayout(mContentView, params);
- }
- }
}
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mAnchorBounds: "); pw.println(mAnchorBounds);
pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);