Loading core/java/android/app/INotificationManager.aidl +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading core/java/android/app/ITransientNotificationCallback.aidl 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * 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(); } core/java/android/widget/Toast.java +167 −45 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -133,20 +167,35 @@ 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 (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(); String pkg = mContext.getOpPackageName(); Loading @@ -155,10 +204,18 @@ public class Toast { final int displayId = mContext.getDisplayId(); try { if (mIsCustomToast) { 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 { service.enqueueTextToast(pkg, mToken, tn, mDuration, displayId); // It's a text toast ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler); service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty Loading @@ -171,8 +228,17 @@ public class Toast { * after the appropriate duration. */ public void 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(); } } /** * Set the view to show. Loading @@ -187,7 +253,6 @@ public class Toast { */ @Deprecated public void setView(View view) { mIsCustomToast = true; mNextView = view; } Loading @@ -203,7 +268,6 @@ public class Toast { * will not have custom toast views displayed. */ public View getView() { mIsCustomToast = true; return mNextView; } Loading Loading @@ -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); } } Loading @@ -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); } } Loading Loading @@ -338,10 +402,17 @@ 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) { 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) Loading @@ -355,6 +426,7 @@ public class Toast { return result; } } /** * Make a standard toast that just contains a text view with the text from a resource. Loading Loading @@ -385,6 +457,14 @@ public class Toast { * @param s The new text for the Toast. */ public void setText(CharSequence s) { 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()"); } Loading @@ -394,6 +474,7 @@ public class Toast { } tv.setText(s); } } // ======================================================================================= // All the gunk below is the interaction with the Notification Service, which handles Loading Loading @@ -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; Loading @@ -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) { Loading Loading @@ -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); } } } } core/java/com/android/internal/statusbar/IStatusBar.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } packages/CarSystemUI/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
core/java/android/app/INotificationManager.aidl +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading
core/java/android/app/ITransientNotificationCallback.aidl 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * 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(); }
core/java/android/widget/Toast.java +167 −45 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -133,20 +167,35 @@ 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 (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(); String pkg = mContext.getOpPackageName(); Loading @@ -155,10 +204,18 @@ public class Toast { final int displayId = mContext.getDisplayId(); try { if (mIsCustomToast) { 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 { service.enqueueTextToast(pkg, mToken, tn, mDuration, displayId); // It's a text toast ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler); service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { service.enqueueToast(pkg, mToken, tn, mDuration, displayId); } } catch (RemoteException e) { // Empty Loading @@ -171,8 +228,17 @@ public class Toast { * after the appropriate duration. */ public void 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(); } } /** * Set the view to show. Loading @@ -187,7 +253,6 @@ public class Toast { */ @Deprecated public void setView(View view) { mIsCustomToast = true; mNextView = view; } Loading @@ -203,7 +268,6 @@ public class Toast { * will not have custom toast views displayed. */ public View getView() { mIsCustomToast = true; return mNextView; } Loading Loading @@ -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); } } Loading @@ -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); } } Loading Loading @@ -338,10 +402,17 @@ 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) { 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) Loading @@ -355,6 +426,7 @@ public class Toast { return result; } } /** * Make a standard toast that just contains a text view with the text from a resource. Loading Loading @@ -385,6 +457,14 @@ public class Toast { * @param s The new text for the Toast. */ public void setText(CharSequence s) { 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()"); } Loading @@ -394,6 +474,7 @@ public class Toast { } tv.setText(s); } } // ======================================================================================= // All the gunk below is the interaction with the Notification Service, which handles Loading Loading @@ -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; Loading @@ -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) { Loading Loading @@ -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); } } } }
core/java/com/android/internal/statusbar/IStatusBar.aidl +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); }
packages/CarSystemUI/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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>