Merge "Refactor ToastPresenter to perform show()/hide()" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
918db9ecde
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes {@code params} with default values for toasts.
|
||||
* Returns the {@link View} being shown at the moment or {@code null} if no toast is being
|
||||
* displayed.
|
||||
*/
|
||||
public void startLayoutParams(WindowManager.LayoutParams params, String packageName) {
|
||||
@Nullable
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IBinder} token used to display the toast or {@code null} if there is no
|
||||
* toast being shown at the moment.
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,15 +21,13 @@ import android.annotation.Nullable;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.ITransientNotificationCallback;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToastPresenter;
|
||||
|
||||
import com.android.internal.R;
|
||||
@@ -49,18 +47,14 @@ import javax.inject.Singleton;
|
||||
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
|
||||
private static final String TAG = "ToastUI";
|
||||
|
||||
/**
|
||||
* Values taken from {@link Toast}.
|
||||
*/
|
||||
private static final long DURATION_SHORT = 4000;
|
||||
private static final long DURATION_LONG = 7000;
|
||||
|
||||
private final CommandQueue mCommandQueue;
|
||||
private final WindowManager mWindowManager;
|
||||
private final INotificationManager mNotificationManager;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final ToastPresenter mPresenter;
|
||||
private ToastEntry mCurrentToast;
|
||||
private final int mGravity;
|
||||
private final int mY;
|
||||
@Nullable private ToastPresenter mPresenter;
|
||||
@Nullable private ITransientNotificationCallback mCallback;
|
||||
|
||||
@Inject
|
||||
public ToastUI(Context context, CommandQueue commandQueue) {
|
||||
@@ -79,7 +73,9 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
|
||||
mWindowManager = windowManager;
|
||||
mNotificationManager = notificationManager;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
mPresenter = new ToastPresenter(context, accessibilityManager);
|
||||
Resources resources = mContext.getResources();
|
||||
mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
|
||||
mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,33 +87,21 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
|
||||
@MainThread
|
||||
public void showToast(String packageName, IBinder token, CharSequence text,
|
||||
IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
|
||||
if (mCurrentToast != null) {
|
||||
if (mPresenter != null) {
|
||||
hideCurrentToast();
|
||||
}
|
||||
View view = mPresenter.getTextToastView(text);
|
||||
LayoutParams params = getLayoutParams(packageName, windowToken, duration);
|
||||
mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback);
|
||||
try {
|
||||
mWindowManager.addView(view, params);
|
||||
} catch (WindowManager.BadTokenException e) {
|
||||
Log.w(TAG, "Error while attempting to show toast from " + packageName, e);
|
||||
return;
|
||||
}
|
||||
mPresenter.trySendAccessibilityEvent(view, packageName);
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onToastShown();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e);
|
||||
}
|
||||
}
|
||||
View view = ToastPresenter.getTextToastView(mContext, text);
|
||||
mCallback = callback;
|
||||
mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager,
|
||||
mNotificationManager, packageName);
|
||||
mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@MainThread
|
||||
public void hideToast(String packageName, IBinder token) {
|
||||
if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName)
|
||||
|| !Objects.equals(mCurrentToast.token, token)) {
|
||||
if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
|
||||
|| !Objects.equals(mPresenter.getToken(), token)) {
|
||||
Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
|
||||
return;
|
||||
}
|
||||
@@ -126,51 +110,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
|
||||
|
||||
@MainThread
|
||||
private void hideCurrentToast() {
|
||||
if (mCurrentToast.view.getParent() != null) {
|
||||
mWindowManager.removeViewImmediate(mCurrentToast.view);
|
||||
}
|
||||
String packageName = mCurrentToast.packageName;
|
||||
try {
|
||||
mNotificationManager.finishToken(packageName, mCurrentToast.windowToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error finishing toast window token from package " + packageName, e);
|
||||
}
|
||||
if (mCurrentToast.callback != null) {
|
||||
try {
|
||||
mCurrentToast.callback.onToastHidden();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e);
|
||||
}
|
||||
}
|
||||
mCurrentToast = null;
|
||||
}
|
||||
|
||||
private LayoutParams getLayoutParams(String packageName, IBinder windowToken, int duration) {
|
||||
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
|
||||
mPresenter.startLayoutParams(params, packageName);
|
||||
int gravity = mContext.getResources().getInteger(
|
||||
com.android.internal.R.integer.config_toastDefaultGravity);
|
||||
int yOffset = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
|
||||
mPresenter.adjustLayoutParams(params, windowToken, duration, gravity, 0, yOffset, 0, 0);
|
||||
return params;
|
||||
}
|
||||
|
||||
private static class ToastEntry {
|
||||
public final String packageName;
|
||||
public final IBinder token;
|
||||
public final View view;
|
||||
public final IBinder windowToken;
|
||||
|
||||
@Nullable
|
||||
public final ITransientNotificationCallback callback;
|
||||
|
||||
private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken,
|
||||
@Nullable ITransientNotificationCallback callback) {
|
||||
this.packageName = packageName;
|
||||
this.token = token;
|
||||
this.view = view;
|
||||
this.windowToken = windowToken;
|
||||
this.callback = callback;
|
||||
}
|
||||
mPresenter.hide(mCallback);
|
||||
mPresenter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public class ToastUITest extends SysuiTestCase {
|
||||
|
||||
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
|
||||
|
||||
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
|
||||
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -218,7 +218,7 @@ public class ToastUITest extends SysuiTestCase {
|
||||
mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
|
||||
|
||||
verify(mWindowManager).removeViewImmediate(view);
|
||||
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
|
||||
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
|
||||
verify(mCallback).onToastHidden();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user