summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorBernardo Rufino <brufino@google.com>2020-04-01 16:46:45 +0100
committerBernardo Rufino <brufino@google.com>2020-04-03 12:57:08 +0000
commitdd4f23812774226a29b27d01e183f6a24fbed2d3 (patch)
treef54961b8a7a5bad301e6e4d2edf18ac8e622211a /core/java
parentf9617e33f94f1459d0b24dba46c443ebcea96542 (diff)
Refactor ToastPresenter to perform show()/hide()
In order to support multi-user, we need to create a new context based on the user id and retrieve the services from it (http://b/151414297#comment9). This meant retrieving the services in ToastUI.showToast() instead of on its constructor, which would make the code diverge from Toast$TN.handleShow(). In order to avoid that, now seemed a good time to refactor ToastPresenter to perform show() and hide(). This means ToastPresenter will now be instantiated in every request for a new toast in ToastUI, but fortunately with the refactor we were able to get rid of ToastEntry (which was also beign instantiated in every request). Also found out a bug with this where window tokens were being used to locate the toasts instead of the (non-window) tokens. This is a bit confusing because the method NM.finishToken(package, token) receives a non-window token to locate the ToastRecord and then finish its window token. This didn't have any side-effects because NM itself finishes the tokens after a time-out. Added a test for this. Bug: 152973950 Test: atest android.widget.cts29.ToastTest android.widget.cts.ToastTest ToastWindowTest ToastUITest NotificationManagerServiceTest LegacyToastTest Change-Id: I13cf18890ca22022adb7576c8ecf3285a9b82299
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/widget/Toast.java72
-rw-r--r--core/java/android/widget/ToastPresenter.java140
2 files changed, 144 insertions, 68 deletions
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 4f14539dd976..08b32930971a 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -117,7 +117,6 @@ public class Toast {
private final Binder mToken;
private final Context mContext;
private final Handler mHandler;
- private final ToastPresenter mPresenter;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
final TN mTN;
@UnsupportedAppUsage
@@ -165,8 +164,8 @@ public class Toast {
looper = getLooper(looper);
mHandler = new Handler(looper);
mCallbacks = new ArrayList<>();
- mPresenter = new ToastPresenter(context, AccessibilityManager.getInstance(context));
- mTN = new TN(mPresenter, context.getPackageName(), mToken, mCallbacks, looper);
+ mTN = new TN(context, context.getPackageName(), mToken,
+ mCallbacks, looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
@@ -496,7 +495,7 @@ public class Toast {
return result;
} else {
Toast result = new Toast(context, looper);
- View v = result.mPresenter.getTextToastView(text);
+ View v = ToastPresenter.getTextToastView(context, text);
result.mNextView = v;
result.mDuration = duration;
@@ -565,13 +564,14 @@ public class Toast {
if (sService != null) {
return sService;
}
- sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
+ sService = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
private static class TN extends ITransientNotification.Stub {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ private final WindowManager.LayoutParams mParams;
private static final int SHOW = 0;
private static final int HIDE = 1;
@@ -608,9 +608,13 @@ public class Toast {
* The parameter {@code callbacks} is not copied and is accessed with itself as its own
* lock.
*/
- TN(ToastPresenter presenter, String packageName, Binder token, List<Callback> callbacks,
+ TN(Context context, String packageName, Binder token, List<Callback> callbacks,
@Nullable Looper looper) {
- mPresenter = presenter;
+ WindowManager windowManager = context.getSystemService(WindowManager.class);
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+ mPresenter = new ToastPresenter(context, windowManager, accessibilityManager,
+ getService(), packageName);
+ mParams = mPresenter.getLayoutParams();
mPackageName = packageName;
mToken = token;
mCallbacks = callbacks;
@@ -645,8 +649,6 @@ public class Toast {
}
}
};
-
- presenter.startLayoutParams(mParams, packageName);
}
private List<Callback> getCallbacks() {
@@ -691,31 +693,9 @@ public class Toast {
// remove the old view if necessary
handleHide();
mView = mNextView;
- Context context = mView.getContext().getApplicationContext();
- if (context == null) {
- context = mView.getContext();
- }
- mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mPresenter.adjustLayoutParams(mParams, windowToken, mDuration, mGravity, mX, mY,
- mHorizontalMargin, mVerticalMargin);
- if (mView.getParent() != null) {
- if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
- mWM.removeView(mView);
- }
- if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
- // Since the notification manager service cancels the token right
- // after it notifies us to cancel the toast there is an inherent
- // race and we may attempt to add a window after the token has been
- // invalidated. Let us hedge against that.
- try {
- mWM.addView(mView, mParams);
- mPresenter.trySendAccessibilityEvent(mView, mPackageName);
- for (Callback callback : getCallbacks()) {
- callback.onToastShown();
- }
- } catch (WindowManager.BadTokenException e) {
- /* ignore */
- }
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
}
}
@@ -723,25 +703,9 @@ public class Toast {
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
- // note: checking parent() just to make sure the view has
- // been added... i have seen cases where we get here when
- // the view isn't yet added, so let's try not to crash.
- if (mView.getParent() != null) {
- if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
- mWM.removeViewImmediate(mView);
- }
-
-
- // Now that we've removed the view it's safe for the server to release
- // the resources.
- try {
- getService().finishToken(mPackageName, mToken);
- } catch (RemoteException e) {
- }
-
- for (Callback callback : getCallbacks()) {
- callback.onToastHidden();
- }
+ checkState(mView == mPresenter.getView(),
+ "Trying to hide toast view different than the last one displayed");
+ mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
mView = null;
}
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 0447b6bb9f11..e9d4aa668891 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -16,11 +16,18 @@
package android.widget;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,41 +44,94 @@ import com.android.internal.util.ArrayUtils;
* @hide
*/
public class ToastPresenter {
+ private static final String TAG = "ToastPresenter";
+ private static final String WINDOW_TITLE = "Toast";
private static final long SHORT_DURATION_TIMEOUT = 4000;
private static final long LONG_DURATION_TIMEOUT = 7000;
+ /**
+ * Returns the default text toast view for message {@code text}.
+ */
+ public static View getTextToastView(Context context, CharSequence text) {
+ View view = LayoutInflater.from(context).inflate(
+ R.layout.transient_notification, null);
+ TextView textView = view.findViewById(com.android.internal.R.id.message);
+ textView.setText(text);
+ return view;
+ }
+
private final Context mContext;
private final Resources mResources;
+ private final WindowManager mWindowManager;
private final AccessibilityManager mAccessibilityManager;
+ private final INotificationManager mNotificationManager;
+ private final String mPackageName;
+ private final WindowManager.LayoutParams mParams;
+ @Nullable private View mView;
+ @Nullable private IBinder mToken;
- public ToastPresenter(Context context, AccessibilityManager accessibilityManager) {
+ public ToastPresenter(Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager,
+ INotificationManager notificationManager, String packageName) {
mContext = context;
mResources = context.getResources();
+ mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
+ mNotificationManager = notificationManager;
+ mPackageName = packageName;
+ mParams = createLayoutParams();
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public WindowManager.LayoutParams getLayoutParams() {
+ return mParams;
+ }
+
+ /**
+ * Returns the {@link View} being shown at the moment or {@code null} if no toast is being
+ * displayed.
+ */
+ @Nullable
+ public View getView() {
+ return mView;
}
/**
- * Initializes {@code params} with default values for toasts.
+ * Returns the {@link IBinder} token used to display the toast or {@code null} if there is no
+ * toast being shown at the moment.
*/
- public void startLayoutParams(WindowManager.LayoutParams params, String packageName) {
+ @Nullable
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ /**
+ * Creates {@link WindowManager.LayoutParams} with default values for toasts.
+ */
+ private WindowManager.LayoutParams createLayoutParams() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setFitInsetsIgnoringVisibility(true);
- params.setTitle("Toast");
+ params.setTitle(WINDOW_TITLE);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- setShowForAllUsersIfApplicable(params, packageName);
+ setShowForAllUsersIfApplicable(params, mPackageName);
+ return params;
}
/**
* Customizes {@code params} according to other parameters, ready to be passed to {@link
* WindowManager#addView(View, ViewGroup.LayoutParams)}.
*/
- public void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
+ private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
float verticalMargin) {
Configuration config = mResources.getConfiguration();
@@ -97,7 +157,7 @@ public class ToastPresenter {
* Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
* packageName} is a cross-user package.
*
- * Implementation note:
+ * <p>Implementation note:
* This code is safe to be executed in SystemUI and the app's process:
* <li>SystemUI: It's running on a trusted domain so apps can't tamper with it. SystemUI
* has the permission INTERNAL_SYSTEM_WINDOW needed by the flag, so SystemUI can add
@@ -120,14 +180,66 @@ public class ToastPresenter {
}
/**
- * Returns the default text toast view for message {@code text}.
+ * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
*/
- public View getTextToastView(CharSequence text) {
- View view = LayoutInflater.from(mContext).inflate(
- R.layout.transient_notification, null);
- TextView textView = view.findViewById(com.android.internal.R.id.message);
- textView.setText(text);
- return view;
+ public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
+ int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
+ @Nullable ITransientNotificationCallback callback) {
+ checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
+ mView = view;
+ mToken = token;
+
+ adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
+ horizontalMargin, verticalMargin);
+ if (mView.getParent() != null) {
+ mWindowManager.removeView(mView);
+ }
+ try {
+ mWindowManager.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ // Since the notification manager service cancels the token right after it notifies us
+ // to cancel the toast there is an inherent race and we may attempt to add a window
+ // after the token has been invalidated. Let us hedge against that.
+ Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+ return;
+ }
+ trySendAccessibilityEvent(mView, mPackageName);
+ if (callback != null) {
+ try {
+ callback.onToastShown();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
+ }
+ }
+ }
+
+ /**
+ * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
+ * int, int, int, float, float, ITransientNotificationCallback)}.
+ *
+ * <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
+ * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
+ */
+ public void hide(@Nullable ITransientNotificationCallback callback) {
+ checkState(mView != null, "No toast to hide.");
+
+ if (mView.getParent() != null) {
+ mWindowManager.removeViewImmediate(mView);
+ }
+ try {
+ mNotificationManager.finishToken(mPackageName, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
+ }
+ if (callback != null) {
+ try {
+ callback.onToastHidden();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
+ }
+ }
+ mView = null;
+ mToken = null;
}
/**