/* * Copyright (C) 2007 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.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; 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.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.view.accessibility.IAccessibilityManager; import android.widget.flags.Flags; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * A toast is a view containing a quick little message for the user. The toast class * helps you create and show those. * {@more} * *
* When the view is shown to the user, appears as a floating view over the * application. It will never receive focus. The user will probably be in the * middle of typing something else. The idea is to be as unobtrusive as * possible, while still showing the user the information you want them to see. * Two examples are the volume control, and the brief message saying that your * settings have been saved. *
* The easiest way to use this class is to call one of the static methods that constructs * everything you need and returns a new Toast object. *
* Note that * Snackbars are * preferred for brief messages while the app is in the foreground. *
* Note that toasts being sent from the background are rate limited, so avoid sending such toasts * in quick succession. *
* Starting with Android 12 (API level 31), apps targeting Android 12 or newer will have * their toasts limited to two lines. * *
For information about creating Toast notifications, read the * Toast Notifications developer * guide.
*Note that toasts being sent from the background are rate limited, so avoid sending such * toasts in quick succession. */ 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(); TN tn = mTN; if (Flags.toastNoWeakref()) { tn.mNextView = mNextView; } else { tn.mNextViewWeakRef = new WeakReference<>(mNextView); } final boolean isUiContext = mContext.isUiContext(); final int displayId = mContext.getDisplayId(); boolean wasEnqueued = false; try { if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { if (mNextView != null) { // It's a custom toast wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId); } else { // It's a text toast ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler); wasEnqueued = service.enqueueTextToast(pkg, mToken, mText, mDuration, isUiContext, displayId, callback); } } else { wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId); } } catch (RemoteException e) { // Empty } finally { if (Flags.toastNoWeakref()) { if (!wasEnqueued) { tn.mNextViewWeakRef = null; tn.mNextView = null; } } } } /** * Close the view if it's showing, or don't show it if it isn't showing yet. * You do not normally have to call this. Normally view will disappear on its own * 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. * * @see #getView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the * {@link #makeText(Context, CharSequence, int)} method, or use a * Snackbar * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background * will not have custom toast views displayed. */ @Deprecated public void setView(View view) { mNextView = view; } /** * Return the view. * *
Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} * with a non-{@code null} view will return {@code null} here. * *
Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context, * CharSequence, int)} or its variants will also return {@code null} here unless they had called * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the * toast is shown or hidden, use {@link #addCallback(Callback)}. * * @see #setView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the * {@link #makeText(Context, CharSequence, int)} method, or use a * Snackbar * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background * will not have custom toast views displayed. */ @Deprecated @Nullable public View getView() { return mNextView; } /** * Set how long to show the view for. * @see #LENGTH_SHORT * @see #LENGTH_LONG */ public void setDuration(@Duration int duration) { mDuration = duration; mTN.mDuration = duration; } /** * Return the duration. * @see #setDuration */ @Duration public int getDuration() { return mDuration; } /** * Set the margins of the view. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when * called on text toasts. * * @param horizontalMargin The horizontal margin, in percentage of the * container width, between the container's edges and the * notification * @param verticalMargin The vertical margin, in percentage of the * container height, between the container's edges and the * notification */ public void setMargin(float horizontalMargin, float verticalMargin) { if (isSystemRenderedTextToast()) { Log.e(TAG, "setMargin() shouldn't be called on text toasts, the values won't be used"); } mTN.mHorizontalMargin = horizontalMargin; mTN.mVerticalMargin = verticalMargin; } /** * Return the horizontal margin. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called * on text toasts as its return value may not reflect actual value since text toasts are not * rendered by the app anymore. */ public float getHorizontalMargin() { if (isSystemRenderedTextToast()) { Log.e(TAG, "getHorizontalMargin() shouldn't be called on text toasts, the result may " + "not reflect actual values."); } return mTN.mHorizontalMargin; } /** * Return the vertical margin. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called * on text toasts as its return value may not reflect actual value since text toasts are not * rendered by the app anymore. */ public float getVerticalMargin() { if (isSystemRenderedTextToast()) { Log.e(TAG, "getVerticalMargin() shouldn't be called on text toasts, the result may not" + " reflect actual values."); } return mTN.mVerticalMargin; } /** * Set the location at which the notification should appear on the screen. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when * called on text toasts. * * @see android.view.Gravity * @see #getGravity */ public void setGravity(int gravity, int xOffset, int yOffset) { if (isSystemRenderedTextToast()) { Log.e(TAG, "setGravity() shouldn't be called on text toasts, the values won't be used"); } mTN.mGravity = gravity; mTN.mX = xOffset; mTN.mY = yOffset; } /** * Get the location at which the notification should appear on the screen. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called * on text toasts as its return value may not reflect actual value since text toasts are not * rendered by the app anymore. * * @see android.view.Gravity * @see #getGravity */ public int getGravity() { if (isSystemRenderedTextToast()) { Log.e(TAG, "getGravity() shouldn't be called on text toasts, the result may not reflect" + " actual values."); } return mTN.mGravity; } /** * Return the X offset in pixels to apply to the gravity's location. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called * on text toasts as its return value may not reflect actual value since text toasts are not * rendered by the app anymore. */ public int getXOffset() { if (isSystemRenderedTextToast()) { Log.e(TAG, "getXOffset() shouldn't be called on text toasts, the result may not reflect" + " actual values."); } return mTN.mX; } /** * Return the Y offset in pixels to apply to the gravity's location. * *
Warning: Starting from Android {@link Build.VERSION_CODES#R}, for apps
* targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called
* on text toasts as its return value may not reflect actual value since text toasts are not
* rendered by the app anymore.
*/
public int getYOffset() {
if (isSystemRenderedTextToast()) {
Log.e(TAG, "getYOffset() shouldn't be called on text toasts, the result may not reflect"
+ " actual values.");
}
return mTN.mY;
}
private boolean isSystemRenderedTextToast() {
return Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null;
}
/**
* Adds a callback to be notified when the toast is shown or hidden.
*
* Note that if the toast is blocked for some reason you won't get a call back.
*
* @see #removeCallback(Callback)
*/
public void addCallback(@NonNull Callback callback) {
checkNotNull(callback);
synchronized (mCallbacks) {
mCallbacks.add(callback);
}
}
/**
* Removes a callback previously added with {@link #addCallback(Callback)}.
*/
public void removeCallback(@NonNull Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
/**
* Gets the LayoutParams for the Toast window.
* @hide
*/
@UnsupportedAppUsage
@Nullable public WindowManager.LayoutParams getWindowParams() {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
// Custom toasts
return mTN.mParams;
} else {
// Text toasts
return null;
}
} else {
// Text and custom toasts are app-rendered
return mTN.mParams;
}
}
/**
* Make a standard toast that just contains text.
*
* @param context The context to use. Usually your {@link android.app.Activity} object.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
*
*/
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
/**
* 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);
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
result.mText = text;
} else {
result.mNextView = ToastPresenter.getTextToastView(context, text);
}
result.mDuration = duration;
return result;
}
/**
* Make a standard toast with an icon to display using the specified looper.
* If looper is null, Looper.myLooper() is used.
*
* The toast will be a custom view that's rendered by the app (instead of by SystemUI).
* In Android version R and above, non-system apps can only render the toast
* when it's in the foreground.
*
* @hide
*/
public static Toast makeCustomToastWithIcon(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration, @NonNull Drawable icon) {
if (icon == null) {
throw new IllegalArgumentException("Drawable icon should not be null "
+ "for makeCustomToastWithIcon");
}
Toast result = new Toast(context, looper);
result.mNextView = ToastPresenter.getTextToastViewWithIcon(context, text, icon);
result.mDuration = duration;
return result;
}
/**
* Make a standard toast that just contains text from a resource.
*
* @param context The context to use. Usually your {@link android.app.Activity} object.
* @param resId The resource id of the string resource to use. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
*
* @throws Resources.NotFoundException if the resource can't be found.
*/
public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)
throws Resources.NotFoundException {
return makeText(context, context.getResources().getText(resId), duration);
}
/**
* Update the text in a Toast that was previously created using one of the makeText() methods.
* @param resId The new text for the Toast.
*/
public void setText(@StringRes int resId) {
setText(mContext.getText(resId));
}
/**
* Update the text in a Toast that was previously created using one of the makeText() methods.
* @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()");
}
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);
}
}
/**
* Get the Toast.TN ITransientNotification object
* @return TN
* @hide
*/
@VisibleForTesting
public TN getTn() {
return mTN;
}
// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
// =======================================================================================
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private static INotificationManager sService;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
/**
* @hide
*/
@VisibleForTesting
public static class TN extends ITransientNotification.Stub {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private final WindowManager.LayoutParams mParams;
private static final int SHOW = 0;
private static final int HIDE = 1;
private static final int CANCEL = 2;
final Handler mHandler;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
int mGravity;
int mX;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
int mY;
float mHorizontalMargin;
float mVerticalMargin;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
View mView;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
WeakReference> mCallbacks;
/**
* Creates a {@link ITransientNotification} object.
*
* The parameter {@code callbacks} is not copied and is accessed with itself as its own
* lock.
*/
TN(Context context, String packageName, Binder token, List