Loading core/java/android/widget/Toast.java +18 −54 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -645,8 +649,6 @@ public class Toast { } } }; presenter.startLayoutParams(mParams, packageName); } private List<Callback> getCallbacks() { Loading Loading @@ -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)); } } Loading @@ -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; } } Loading core/java/android/widget/ToastPresenter.java +126 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } /** * 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; } /** * Initializes {@code params} with default values for toasts. * Creates {@link WindowManager.LayoutParams} with default values for toasts. */ public void startLayoutParams(WindowManager.LayoutParams params, String packageName) { 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(); Loading @@ -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 Loading @@ -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; } /** Loading packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +18 −78 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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 Loading @@ -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; } Loading @@ -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; } } packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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(); } Loading Loading
core/java/android/widget/Toast.java +18 −54 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -645,8 +649,6 @@ public class Toast { } } }; presenter.startLayoutParams(mParams, packageName); } private List<Callback> getCallbacks() { Loading Loading @@ -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)); } } Loading @@ -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; } } Loading
core/java/android/widget/ToastPresenter.java +126 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } /** * 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; } /** * Initializes {@code params} with default values for toasts. * Creates {@link WindowManager.LayoutParams} with default values for toasts. */ public void startLayoutParams(WindowManager.LayoutParams params, String packageName) { 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(); Loading @@ -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 Loading @@ -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; } /** Loading
packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +18 −78 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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 Loading @@ -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; } Loading @@ -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; } }
packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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(); } Loading