diff options
| author | 2019-12-20 18:35:30 +0000 | |
|---|---|---|
| committer | 2020-01-27 11:13:27 +0000 | |
| commit | ab338ac42c3fb33537d7f83477de430ee780b74f (patch) | |
| tree | a14db337126ac31334711a37e304907d474f82d9 | |
| parent | 4a8fc13dd9711bcd9fb37a2f2ee1fcf9c1accc33 (diff) | |
Move text toast creation to system UI
In order to do that, NotificationManager now calls
StatusBar.showToast(), which is in SystemUI. StatusBar then calls a new
component, ToastUI, which is responsible for rendering the toast. The
code for rendering the toast was extracted from the Toast class, so it
should behave identically.
Also refactored the code a bit on NotificationManagerService, creating
two children of ToastRecord (one for custom, other for text toasts).
The change is gated in Toast class in this CL, but it's also gated on
the system server, so apps can't circumvent the background block.
Bug: 128611929
Bug: 144754526
Test: atest android.widget.cts.ToastTest
Merged-In: Id0021cdc6f72f41b55ff1c5a4f09ae7687586460
Change-Id: Id0021cdc6f72f41b55ff1c5a4f09ae7687586460
16 files changed, 853 insertions, 147 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index d665f336ec1d..4b1ba0278682 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -18,6 +18,7 @@ package android.app; import android.app.ITransientNotification; +import android.app.ITransientNotificationCallback; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; @@ -45,8 +46,7 @@ interface INotificationManager void cancelAllNotifications(String pkg, int userId); void clearData(String pkg, int uid, boolean fromApp); - // TODO: Replace parameter (ITransientNotification callback) with (CharSequence text) - void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); + void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback); void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); diff --git a/core/java/android/app/ITransientNotificationCallback.aidl b/core/java/android/app/ITransientNotificationCallback.aidl new file mode 100644 index 000000000000..abe254f2d16a --- /dev/null +++ b/core/java/android/app/ITransientNotificationCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2020, 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.app; + +/** + * Callback object to be called when the associated toast is shown or hidden. + * + * @hide + */ +oneway interface ITransientNotificationCallback { + void onToastShown(); + void onToastHidden(); +} diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 42d7892eeffb..c8a7c079994c 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -17,6 +17,7 @@ package android.widget; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.Preconditions.checkState; import android.annotation.IntDef; import android.annotation.NonNull; @@ -24,6 +25,9 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.app.INotificationManager; import android.app.ITransientNotification; +import android.app.ITransientNotificationCallback; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; @@ -105,15 +109,45 @@ public class Toast { */ public static final int LENGTH_LONG = 1; + /** + * Text toasts will be rendered by SystemUI instead of in-app, so apps can't circumvent + * background custom toast restrictions. + * + * TODO(b/144152069): Add @EnabledAfter(Q) to target R+ after assessing impact on dogfood + */ + @ChangeId + // @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long CHANGE_TEXT_TOASTS_IN_THE_SYSTEM = 147798919L; + + private final Binder mToken; private final Context mContext; + private final Handler mHandler; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final TN mTN; @UnsupportedAppUsage int mDuration; - View mNextView; - // TODO(b/128611929): Remove this and check for null view when toast creation is in the system - boolean mIsCustomToast = false; + + /** + * This is also passed to {@link TN} object, where it's also accessed with itself as its own + * lock. + */ + @GuardedBy("mCallbacks") + private final List<Callback> mCallbacks; + + /** + * View to be displayed, in case this is a custom toast (e.g. not created with {@link + * #makeText(Context, int, int)} or its variants). + */ + @Nullable + private View mNextView; + + /** + * Text to be shown, in case this is NOT a custom toast (e.g. created with {@link + * #makeText(Context, int, int)} or its variants). + */ + @Nullable + private CharSequence mText; /** * Construct an empty Toast object. You must call {@link #setView} before you @@ -133,19 +167,34 @@ public class Toast { public Toast(@NonNull Context context, @Nullable Looper looper) { mContext = context; mToken = new Binder(); - mTN = new TN(context.getPackageName(), mToken, looper); + looper = getLooper(looper); + mHandler = new Handler(looper); + mCallbacks = new ArrayList<>(); + mTN = new TN(context.getPackageName(), mToken, mCallbacks, looper); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); } + private Looper getLooper(@Nullable Looper looper) { + if (looper != null) { + return looper; + } + return checkNotNull(Looper.myLooper(), + "Can't toast on a thread that has not called Looper.prepare()"); + } + /** * Show the view for the specified duration. */ public void show() { - if (mNextView == null) { - throw new RuntimeException("setView must have been called"); + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { + checkState(mNextView != null || mText != null, "You must either set a text or a view"); + } else { + if (mNextView == null) { + throw new RuntimeException("setView must have been called"); + } } INotificationManager service = getService(); @@ -155,10 +204,18 @@ public class Toast { final int displayId = mContext.getDisplayId(); try { - if (mIsCustomToast) { - service.enqueueToast(pkg, mToken, tn, mDuration, displayId); + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { + if (mNextView != null) { + // It's a custom toast + service.enqueueToast(pkg, mToken, tn, mDuration, displayId); + } else { + // It's a text toast + ITransientNotificationCallback callback = + new CallbackBinder(mCallbacks, mHandler); + service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); + } } else { - service.enqueueTextToast(pkg, mToken, tn, mDuration, displayId); + service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty @@ -171,7 +228,16 @@ public class Toast { * after the appropriate duration. */ public void cancel() { - mTN.cancel(); + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) + && mNextView == null) { + try { + getService().cancelToast(mContext.getOpPackageName(), mToken); + } catch (RemoteException e) { + // Empty + } + } else { + mTN.cancel(); + } } /** @@ -187,7 +253,6 @@ public class Toast { */ @Deprecated public void setView(View view) { - mIsCustomToast = true; mNextView = view; } @@ -203,7 +268,6 @@ public class Toast { * will not have custom toast views displayed. */ public View getView() { - mIsCustomToast = true; return mNextView; } @@ -298,8 +362,8 @@ public class Toast { */ public void addCallback(@NonNull Callback callback) { checkNotNull(callback); - synchronized (mTN.mCallbacks) { - mTN.mCallbacks.add(callback); + synchronized (mCallbacks) { + mCallbacks.add(callback); } } @@ -307,8 +371,8 @@ public class Toast { * Removes a callback previously added with {@link #addCallback(Callback)}. */ public void removeCallback(@NonNull Callback callback) { - synchronized (mTN.mCallbacks) { - mTN.mCallbacks.remove(callback); + synchronized (mCallbacks) { + mCallbacks.remove(callback); } } @@ -338,22 +402,30 @@ public class Toast { /** * Make a standard toast to display using the specified looper. * If looper is null, Looper.myLooper() is used. + * * @hide */ public static Toast makeText(@NonNull Context context, @Nullable Looper looper, @NonNull CharSequence text, @Duration int duration) { - Toast result = new Toast(context, looper); - - LayoutInflater inflate = (LayoutInflater) - context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); - TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); - tv.setText(text); - - result.mNextView = v; - result.mDuration = duration; - - return result; + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { + Toast result = new Toast(context, looper); + result.mText = text; + result.mDuration = duration; + return result; + } else { + Toast result = new Toast(context, looper); + + LayoutInflater inflate = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); + TextView tv = (TextView) v.findViewById(com.android.internal.R.id.message); + tv.setText(text); + + result.mNextView = v; + result.mDuration = duration; + + return result; + } } /** @@ -385,14 +457,23 @@ public class Toast { * @param s The new text for the Toast. */ public void setText(CharSequence s) { - if (mNextView == null) { - throw new RuntimeException("This Toast was not created with Toast.makeText()"); - } - TextView tv = mNextView.findViewById(com.android.internal.R.id.message); - if (tv == null) { - throw new RuntimeException("This Toast was not created with Toast.makeText()"); + if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { + if (mNextView != null) { + throw new IllegalStateException( + "Text provided for custom toast, remove previous setView() calls if you " + + "want a text toast instead."); + } + mText = s; + } else { + if (mNextView == null) { + throw new RuntimeException("This Toast was not created with Toast.makeText()"); + } + TextView tv = mNextView.findViewById(com.android.internal.R.id.message); + if (tv == null) { + throw new RuntimeException("This Toast was not created with Toast.makeText()"); + } + tv.setText(s); } - tv.setText(s); } // ======================================================================================= @@ -442,12 +523,18 @@ public class Toast { final Binder mToken; @GuardedBy("mCallbacks") - private final List<Callback> mCallbacks = new ArrayList<>(); + private final List<Callback> mCallbacks; static final long SHORT_DURATION_TIMEOUT = 4000; static final long LONG_DURATION_TIMEOUT = 7000; - TN(String packageName, Binder token, @Nullable Looper looper) { + /** + * Creates a {@link ITransientNotification} object. + * + * The parameter {@code callbacks} is not copied and is accessed with itself as its own + * lock. + */ + TN(String packageName, Binder token, List<Callback> callbacks, @Nullable Looper looper) { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; @@ -464,15 +551,8 @@ public class Toast { mPackageName = packageName; mToken = token; + mCallbacks = callbacks; - if (looper == null) { - // Use Looper.myLooper() if looper is not specified. - looper = Looper.myLooper(); - if (looper == null) { - throw new RuntimeException( - "Can't toast on a thread that has not called Looper.prepare()"); - } - } mHandler = new Handler(looper, null) { @Override public void handleMessage(Message msg) { @@ -655,4 +735,46 @@ public class Toast { */ public void onToastHidden() {} } + + private static class CallbackBinder extends ITransientNotificationCallback.Stub { + private final Handler mHandler; + + @GuardedBy("mCallbacks") + private final List<Callback> mCallbacks; + + /** + * Creates a {@link ITransientNotificationCallback} object. + * + * The parameter {@code callbacks} is not copied and is accessed with itself as its own + * lock. + */ + private CallbackBinder(List<Callback> callbacks, Handler handler) { + mCallbacks = callbacks; + mHandler = handler; + } + + @Override + public void onToastShown() { + mHandler.post(() -> { + for (Callback callback : getCallbacks()) { + callback.onToastShown(); + } + }); + } + + @Override + public void onToastHidden() { + mHandler.post(() -> { + for (Callback callback : getCallbacks()) { + callback.onToastHidden(); + } + }); + } + + private List<Callback> getCallbacks() { + synchronized (mCallbacks) { + return new ArrayList<>(mCallbacks); + } + } + } } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index aa0ac37b007a..a2736333383e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -16,6 +16,7 @@ package com.android.internal.statusbar; +import android.app.ITransientNotificationCallback; import android.content.ComponentName; import android.graphics.Rect; import android.hardware.biometrics.IBiometricServiceReceiverInternal; @@ -198,4 +199,15 @@ oneway interface IStatusBar * Dismiss the warning that the device is about to go to sleep due to user inactivity. */ void dismissInattentiveSleepWarning(boolean animated); + + /** + * Displays a text toast. + */ + void showToast(String packageName, IBinder token, CharSequence text, IBinder windowToken, + int duration, @nullable ITransientNotificationCallback callback); + + /** + * Cancels toast with token {@code token} in {@code packageName}. + */ + void hideToast(String packageName, IBinder token); } diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 981c129a0965..e2297e44fdfe 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -80,5 +80,6 @@ <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.navigationbar.car.CarNavigationBar</item> + <item>com.android.systemui.toast.ToastUI</item> </string-array> </resources> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 4cc5a6731108..dd4c3210922a 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -36,5 +36,6 @@ <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.SizeCompatModeActivityController</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> + <item>com.android.systemui.toast.ToastUI</item> </string-array> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index edcd8012c82c..2aa6d9512898 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -297,6 +297,7 @@ <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.accessibility.SystemActions</item> + <item>com.android.systemui.toast.ToastUI</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index d4e47f699345..5de88e17d320 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarModule; import com.android.systemui.statusbar.tv.TvStatusBar; import com.android.systemui.theme.ThemeOverlayController; +import com.android.systemui.toast.ToastUI; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.volume.VolumeUI; @@ -153,6 +154,12 @@ public abstract class SystemUIBinder { @ClassKey(ThemeOverlayController.class) public abstract SystemUI bindThemeOverlayController(ThemeOverlayController sysui); + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI.class) + public abstract SystemUI bindToastUI(ToastUI service); + /** Inject into TvStatusBar. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 7c0f4f942bce..3af3701dff97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -25,6 +25,8 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.systemui.statusbar.phone.StatusBar.ONLY_CORE_APPS; +import android.annotation.Nullable; +import android.app.ITransientNotificationCallback; import android.app.StatusBarManager; import android.app.StatusBarManager.Disable2Flags; import android.app.StatusBarManager.DisableFlags; @@ -119,6 +121,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_TOP_APP_WINDOW_CHANGED = 50 << MSG_SHIFT; private static final int MSG_SHOW_INATTENTIVE_SLEEP_WARNING = 51 << MSG_SHIFT; private static final int MSG_DISMISS_INATTENTIVE_SLEEP_WARNING = 52 << MSG_SHIFT; + private static final int MSG_SHOW_TOAST = 53 << MSG_SHIFT; + private static final int MSG_HIDE_TOAST = 54 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -308,6 +312,19 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * due to prolonged user inactivity should be dismissed. */ default void dismissInattentiveSleepWarning(boolean animated) { } + + /** + * @see IStatusBar#showToast(String, IBinder, CharSequence, IBinder, int, + * ITransientNotificationCallback) + */ + default void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, + @Nullable ITransientNotificationCallback callback) { } + + /** + * @see IStatusBar#hideToast(String, IBinder) (String, IBinder) + */ + default void hideToast(String packageName, IBinder token) { } } public CommandQueue(Context context) { @@ -761,6 +778,31 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override + public void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.arg2 = token; + args.arg3 = text; + args.arg4 = windowToken; + args.arg5 = callback; + args.argi1 = duration; + mHandler.obtainMessage(MSG_SHOW_TOAST, args).sendToTarget(); + } + } + + @Override + public void hideToast(String packageName, IBinder token) { + synchronized (mLock) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.arg2 = token; + mHandler.obtainMessage(MSG_HIDE_TOAST, args).sendToTarget(); + } + } + + @Override public void onBiometricAuthenticated() { synchronized (mLock) { mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); @@ -1178,6 +1220,30 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).dismissInattentiveSleepWarning((Boolean) msg.obj); } break; + case MSG_SHOW_TOAST: { + args = (SomeArgs) msg.obj; + String packageName = (String) args.arg1; + IBinder token = (IBinder) args.arg2; + CharSequence text = (CharSequence) args.arg3; + IBinder windowToken = (IBinder) args.arg4; + ITransientNotificationCallback callback = + (ITransientNotificationCallback) args.arg5; + int duration = args.argi1; + for (Callbacks callbacks : mCallbacks) { + callbacks.showToast(packageName, token, text, windowToken, duration, + callback); + } + break; + } + case MSG_HIDE_TOAST: { + args = (SomeArgs) msg.obj; + String packageName = (String) args.arg1; + IBinder token = (IBinder) args.arg2; + for (Callbacks callbacks : mCallbacks) { + callbacks.hideToast(packageName, token); + } + break; + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java new file mode 100644 index 000000000000..dea8c5d49dfc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.toast; + +import android.annotation.MainThread; +import android.annotation.Nullable; +import android.app.INotificationManager; +import android.app.ITransientNotificationCallback; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.internal.R; +import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.CommandQueue; + +import java.util.Objects; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controls display of text toasts. + */ +@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 ToastEntry mCurrentToast; + + @Inject + public ToastUI(Context context, CommandQueue commandQueue) { + super(context); + mCommandQueue = commandQueue; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mNotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mAccessibilityManager = AccessibilityManager.getInstance(context); + } + + @Override + public void start() { + mCommandQueue.addCallback(this); + } + + @Override + @MainThread + public void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) { + if (mCurrentToast != null) { + hideCurrentToast(); + } + View view = getView(text); + LayoutParams params = getLayoutParams(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; + } + trySendAccessibilityEvent(view, packageName); + if (callback != null) { + try { + callback.onToastShown(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e); + } + } + } + + @Override + @MainThread + public void hideToast(String packageName, IBinder token) { + if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName) + || !Objects.equals(mCurrentToast.token, token)) { + Log.w(TAG, "Attempt to hide non-current toast from package " + packageName); + return; + } + hideCurrentToast(); + } + + @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 void trySendAccessibilityEvent(View view, String packageName) { + if (!mAccessibilityManager.isEnabled()) { + return; + } + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setClassName(Toast.class.getName()); + event.setPackageName(packageName); + view.dispatchPopulateAccessibilityEvent(event); + mAccessibilityManager.sendAccessibilityEvent(event); + } + + private View getView(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; + } + + private LayoutParams getLayoutParams(IBinder windowToken, int duration) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = com.android.internal.R.style.Animation_Toast; + params.type = WindowManager.LayoutParams.TYPE_TOAST; + params.setTitle("Toast"); + params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + Configuration config = mContext.getResources().getConfiguration(); + int specificGravity = mContext.getResources().getInteger( + com.android.internal.R.integer.config_toastDefaultGravity); + int gravity = Gravity.getAbsoluteGravity(specificGravity, config.getLayoutDirection()); + params.gravity = gravity; + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = 1.0f; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = 1.0f; + } + params.x = 0; + params.y = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); + params.verticalMargin = 0; + params.horizontalMargin = 0; + params.packageName = mContext.getPackageName(); + params.hideTimeoutMilliseconds = + (duration == Toast.LENGTH_LONG) ? DURATION_LONG : DURATION_SHORT; + params.token = windowToken; + 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; + } + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e92f3ec5a836..eea59ca26f34 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -117,6 +117,7 @@ import android.app.AutomaticZenRule; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; +import android.app.ITransientNotificationCallback; import android.app.IUriGrantsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -255,6 +256,9 @@ import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.ManagedServices.UserProfiles; +import com.android.server.notification.toast.CustomToastRecord; +import com.android.server.notification.toast.TextToastRecord; +import com.android.server.notification.toast.ToastRecord; import com.android.server.pm.PackageManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; @@ -295,8 +299,8 @@ import java.util.function.BiConsumer; /** {@hide} */ public class NotificationManagerService extends SystemService { - static final String TAG = "NotificationService"; - static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + public static final String TAG = "NotificationService"; + public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); public static final boolean ENABLE_CHILD_NOTIFICATIONS = SystemProperties.getBoolean("debug.child_notifs", true); @@ -393,6 +397,7 @@ public class NotificationManagerService extends SystemService { private PackageManager mPackageManagerClient; AudioManager mAudioManager; AudioManagerInternal mAudioManagerInternal; + // Can be null for wear @Nullable StatusBarManagerInternal mStatusBar; Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; @@ -850,49 +855,6 @@ public class NotificationManagerService extends SystemService { out.endDocument(); } - private static final class ToastRecord - { - public final int pid; - public final String pkg; - public final IBinder token; - public final ITransientNotification callback; - public int duration; - public int displayId; - public Binder windowToken; - - ToastRecord(int pid, String pkg, IBinder token, ITransientNotification callback, - int duration, Binder windowToken, int displayId) { - this.pid = pid; - this.pkg = pkg; - this.token = token; - this.callback = callback; - this.duration = duration; - this.windowToken = windowToken; - this.displayId = displayId; - } - - void update(int duration) { - this.duration = duration; - } - - void dump(PrintWriter pw, String prefix, DumpFilter filter) { - if (filter != null && !filter.matches(pkg)) return; - pw.println(prefix + this); - } - - @Override - public final String toString() - { - return "ToastRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " pkg=" + pkg - + " token=" + token - + " callback=" + callback - + " duration=" + duration - + "}"; - } - } - @VisibleForTesting final NotificationDelegate mNotificationDelegate = new NotificationDelegate() { @@ -2643,6 +2605,19 @@ public class NotificationManagerService extends SystemService { return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId; } + private ToastRecord getToastRecord(int pid, String packageName, IBinder token, + @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, + Binder windowToken, int displayId, + @Nullable ITransientNotificationCallback textCallback) { + if (callback == null) { + return new TextToastRecord(this, mStatusBar, pid, packageName, token, text, duration, + windowToken, displayId, textCallback); + } else { + return new CustomToastRecord(this, pid, packageName, token, callback, duration, + windowToken, displayId); + } + } + @VisibleForTesting NotificationManagerInternal getInternalService() { return mInternalService; @@ -2654,28 +2629,30 @@ public class NotificationManagerService extends SystemService { // ============================================================================ @Override - public void enqueueTextToast(String pkg, IBinder token, ITransientNotification callback, - int duration, int displayId) { - enqueueToast(pkg, token, callback, duration, displayId, false); + public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, + int displayId, @Nullable ITransientNotificationCallback callback) { + enqueueToast(pkg, token, text, null, duration, displayId, callback); } @Override public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId) { - enqueueToast(pkg, token, callback, duration, displayId, true); + enqueueToast(pkg, token, null, callback, duration, displayId, null); } - private void enqueueToast(String pkg, IBinder token, ITransientNotification callback, - int duration, int displayId, boolean isCustomToast) { + private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, + @Nullable ITransientNotification callback, int duration, int displayId, + @Nullable ITransientNotificationCallback textCallback) { if (DBG) { - Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration + " displayId=" + displayId); } - if (pkg == null || callback == null || token == null) { - Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " callback=" + callback + " token=" - + token); - return ; + if (pkg == null || (text == null && callback == null) + || (text != null && callback != null) || token == null) { + Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " text=" + text + " callback=" + + " token=" + token); + return; } final int callingUid = Binder.getCallingUid(); @@ -2703,7 +2680,7 @@ public class NotificationManagerService extends SystemService { return; } - if (isCustomToast && !appIsForeground && !isSystemToast) { + if (callback != null && !appIsForeground && !isSystemToast) { boolean block; try { block = mPlatformCompat.isChangeEnabledByPackageName( @@ -2745,28 +2722,28 @@ public class NotificationManagerService extends SystemService { int count = 0; final int N = mToastQueue.size(); for (int i=0; i<N; i++) { - final ToastRecord r = mToastQueue.get(i); - if (r.pkg.equals(pkg)) { - count++; - if (count >= MAX_PACKAGE_NOTIFICATIONS) { - Slog.e(TAG, "Package has already posted " + count + final ToastRecord r = mToastQueue.get(i); + if (r.pkg.equals(pkg)) { + count++; + if (count >= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + " toasts. Not showing more. Package=" + pkg); - return; - } - } + return; + } + } } } Binder windowToken = new Binder(); mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId); - record = new ToastRecord(callingPid, pkg, token, callback, duration, - windowToken, displayId); + record = getToastRecord(callingPid, pkg, token, text, callback, duration, + windowToken, displayId, textCallback); mToastQueue.add(record); index = mToastQueue.size() - 1; - keepProcessAliveIfNeededLocked(callingPid); + keepProcessAliveForToastIfNeededLocked(callingPid); } // If it's at index 0, it's the current toast. It doesn't matter if it's - // new or just been updated. Call back and tell it to show itself. + // new or just been updated, show it. // If the callback fails, this will remove it from the list, so don't // assume that it's valid after this. if (index == 0) { @@ -6935,40 +6912,22 @@ public class NotificationManagerService extends SystemService { void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { - if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); - try { - record.callback.show(record.windowToken); + if (record.show()) { scheduleDurationReachedLocked(record); return; - } catch (RemoteException e) { - Slog.w(TAG, "Object died trying to show notification " + record.callback - + " in package " + record.pkg); - // remove it from the list and let the process die - int index = mToastQueue.indexOf(record); - if (index >= 0) { - mToastQueue.remove(index); - } - keepProcessAliveIfNeededLocked(record.pid); - if (mToastQueue.size() > 0) { - record = mToastQueue.get(0); - } else { - record = null; - } } + int index = mToastQueue.indexOf(record); + if (index >= 0) { + mToastQueue.remove(index); + } + record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null; } } @GuardedBy("mToastQueue") void cancelToastLocked(int index) { ToastRecord record = mToastQueue.get(index); - try { - record.callback.hide(); - } catch (RemoteException e) { - Slog.w(TAG, "Object died trying to hide notification " + record.callback - + " in package " + record.pkg); - // don't worry about this, we're about to remove it from - // the list anyway - } + record.hide(); ToastRecord lastToast = mToastQueue.remove(index); @@ -6981,7 +6940,7 @@ public class NotificationManagerService extends SystemService { // one way or another. scheduleKillTokenTimeout(lastToast); - keepProcessAliveIfNeededLocked(record.pid); + keepProcessAliveForToastIfNeededLocked(record.pid); if (mToastQueue.size() > 0) { // Show the next one. If the callback fails, this will remove // it from the list, so don't assume that the list hasn't changed @@ -7004,7 +6963,7 @@ public class NotificationManagerService extends SystemService { { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); - int delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; + int delay = r.getDuration() == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; // Accessibility users may need longer timeout duration. This api compares original delay // with user's preference and return longer one. It returns original delay if there's no // preference. @@ -7053,13 +7012,21 @@ public class NotificationManagerService extends SystemService { return -1; } + /** + * Adjust process {@code pid} importance according to whether it has toasts in the queue or not. + */ + public void keepProcessAliveForToastIfNeeded(int pid) { + synchronized (mToastQueue) { + keepProcessAliveForToastIfNeededLocked(pid); + } + } + @GuardedBy("mToastQueue") - void keepProcessAliveIfNeededLocked(int pid) - { + private void keepProcessAliveForToastIfNeededLocked(int pid) { int toastCount = 0; // toasts from this pid ArrayList<ToastRecord> list = mToastQueue; - int N = list.size(); - for (int i=0; i<N; i++) { + int n = list.size(); + for (int i = 0; i < n; i++) { ToastRecord r = list.get(i); if (r.pid == pid) { toastCount++; diff --git a/services/core/java/com/android/server/notification/toast/CustomToastRecord.java b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java new file mode 100644 index 000000000000..aca6f4853597 --- /dev/null +++ b/services/core/java/com/android/server/notification/toast/CustomToastRecord.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 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 com.android.server.notification.toast; + +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.notification.NotificationManagerService.DBG; + +import android.app.ITransientNotification; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.notification.NotificationManagerService; + +/** + * Represents a custom toast, a toast whose view is provided by the app. + */ +public class CustomToastRecord extends ToastRecord { + private static final String TAG = NotificationManagerService.TAG; + + public final ITransientNotification callback; + + public CustomToastRecord( + NotificationManagerService notificationManager, int pid, String packageName, + IBinder token, ITransientNotification callback, int duration, Binder windowToken, + int displayId) { + super(notificationManager, pid, packageName, token, duration, windowToken, displayId); + this.callback = checkNotNull(callback); + } + + @Override + public boolean show() { + if (DBG) { + Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback); + } + try { + callback.show(windowToken); + return true; + } catch (RemoteException e) { + Slog.w(TAG, "Object died trying to show custom toast " + token + " in package " + + pkg); + mNotificationManager.keepProcessAliveForToastIfNeeded(pid); + return false; + } + } + + @Override + public void hide() { + try { + callback.hide(); + } catch (RemoteException e) { + Slog.w(TAG, "Object died trying to hide custom toast " + token + " in package " + + pkg); + + } + } + + @Override + public String toString() { + return "CustomToastRecord{" + + Integer.toHexString(System.identityHashCode(this)) + + " token=" + token + + " packageName=" + pkg + + " callback=" + callback + + " duration=" + getDuration() + + "}"; + } +} diff --git a/services/core/java/com/android/server/notification/toast/TextToastRecord.java b/services/core/java/com/android/server/notification/toast/TextToastRecord.java new file mode 100644 index 000000000000..3c231b445f62 --- /dev/null +++ b/services/core/java/com/android/server/notification/toast/TextToastRecord.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 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 com.android.server.notification.toast; + +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.notification.NotificationManagerService.DBG; + +import android.annotation.Nullable; +import android.app.ITransientNotificationCallback; +import android.os.Binder; +import android.os.IBinder; +import android.util.Slog; + +import com.android.server.notification.NotificationManagerService; +import com.android.server.statusbar.StatusBarManagerInternal; + +/** + * Represents a text toast, a toast rendered by the system that contains only text. + */ +public class TextToastRecord extends ToastRecord { + private static final String TAG = NotificationManagerService.TAG; + + public final CharSequence text; + @Nullable + private final StatusBarManagerInternal mStatusBar; + @Nullable + private final ITransientNotificationCallback mCallback; + + public TextToastRecord(NotificationManagerService notificationManager, + @Nullable StatusBarManagerInternal statusBarManager, int pid, String packageName, + IBinder token, CharSequence text, int duration, Binder windowToken, int displayId, + @Nullable ITransientNotificationCallback callback) { + super(notificationManager, pid, packageName, token, duration, windowToken, displayId); + mStatusBar = statusBarManager; + mCallback = callback; + this.text = checkNotNull(text); + } + + @Override + public boolean show() { + if (DBG) { + Slog.d(TAG, "Show pkg=" + pkg + " text=" + text); + } + if (mStatusBar == null) { + Slog.w(TAG, "StatusBar not available to show text toast for package " + pkg); + return false; + } + mStatusBar.showToast(pkg, token, text, windowToken, getDuration(), mCallback); + return true; + } + + @Override + public void hide() { + // If it's null, show() would have returned false + checkNotNull(mStatusBar, "Cannot hide toast that wasn't shown"); + + mStatusBar.hideToast(pkg, token); + } + + @Override + public String toString() { + return "TextToastRecord{" + + Integer.toHexString(System.identityHashCode(this)) + + " token=" + token + + " packageName=" + pkg + + " text=" + text + + " duration=" + getDuration() + + "}"; + } +} diff --git a/services/core/java/com/android/server/notification/toast/ToastRecord.java b/services/core/java/com/android/server/notification/toast/ToastRecord.java new file mode 100644 index 000000000000..ef75a6f5dd7b --- /dev/null +++ b/services/core/java/com/android/server/notification/toast/ToastRecord.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 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 com.android.server.notification.toast; + +import android.os.Binder; +import android.os.IBinder; + +import com.android.server.notification.NotificationManagerService; +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; + +/** + * Represents a toast, a transient notification. + */ +public abstract class ToastRecord { + public final int pid; + public final String pkg; + public final IBinder token; + public final int displayId; + public final Binder windowToken; + protected final NotificationManagerService mNotificationManager; + private int mDuration; + + protected ToastRecord( + NotificationManagerService notificationManager, + int pid, String pkg, IBinder token, int duration, + Binder windowToken, int displayId) { + this.mNotificationManager = notificationManager; + this.pid = pid; + this.pkg = pkg; + this.token = token; + this.windowToken = windowToken; + this.displayId = displayId; + mDuration = duration; + } + + /** + * This method is responsible for showing the toast represented by this object. + * + * @return True if it was successfully shown. + */ + public abstract boolean show(); + + /** + * This method is responsible for hiding the toast represented by this object. + */ + public abstract void hide(); + + /** + * Returns the duration of this toast, which can be {@link android.widget.Toast#LENGTH_SHORT} + * or {@link android.widget.Toast#LENGTH_LONG}. + */ + public int getDuration() { + return mDuration; + } + + /** + * Updates toast duration. + */ + public void update(int duration) { + mDuration = duration; + } + + /** + * Dumps a textual representation of this object. + */ + public void dump(PrintWriter pw, String prefix, DumpFilter filter) { + if (filter != null && !filter.matches(pkg)) { + return; + } + pw.println(prefix + this); + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 95ffd8fe43d8..d88dccb9afeb 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -16,7 +16,10 @@ package com.android.server.statusbar; +import android.annotation.Nullable; +import android.app.ITransientNotificationCallback; import android.os.Bundle; +import android.os.IBinder; import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsetsController.Appearance; @@ -123,4 +126,15 @@ public interface StatusBarManagerInternal { /** @see com.android.internal.statusbar.IStatusBar#abortTransient */ void abortTransient(int displayId, @InternalInsetsType int[] types); + + /** + * @see com.android.internal.statusbar.IStatusBar#showToast(String, IBinder, CharSequence, + * IBinder, int, ITransientNotificationCallback) + */ + void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, + @Nullable ITransientNotificationCallback textCallback); + + /** @see com.android.internal.statusbar.IStatusBar#hideToast(String, IBinder) */ + void hideToast(String packageName, IBinder token); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 870c81fb5dc2..3f7d373c1848 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.Nullable; import android.app.ActivityThread; +import android.app.ITransientNotificationCallback; import android.app.Notification; import android.app.StatusBarManager; import android.content.ComponentName; @@ -500,6 +501,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } catch (RemoteException ex) { } } } + + @Override + public void showToast(String packageName, IBinder token, CharSequence text, + IBinder windowToken, int duration, + @Nullable ITransientNotificationCallback callback) { + if (mBar != null) { + try { + mBar.showToast(packageName, token, text, windowToken, duration, callback); + } catch (RemoteException ex) { } + } + } + + @Override + public void hideToast(String packageName, IBinder token) { + if (mBar != null) { + try { + mBar.hideToast(packageName, token); + } catch (RemoteException ex) { } + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { |