Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7af99a71 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Merge "Refactor ToastPresenter to perform show()/hide()" into rvc-dev...

Merge "Merge "Refactor ToastPresenter to perform show()/hide()" into rvc-dev am: 918db9ec am: 9a2f55df" into rvc-d1-dev-plus-aosp am: 34460a2f

Change-Id: Ic923cb776bc2c57d9e5d655ce8d98ba2bbef1e3c
parents da91edc2 34460a2f
Loading
Loading
Loading
Loading
+18 −54
Original line number Diff line number Diff line
@@ -117,7 +117,6 @@ public class Toast {
    private final Binder mToken;
    private final Context mContext;
    private final Handler mHandler;
    private final ToastPresenter mPresenter;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    final TN mTN;
    @UnsupportedAppUsage
@@ -165,8 +164,8 @@ public class Toast {
        looper = getLooper(looper);
        mHandler = new Handler(looper);
        mCallbacks = new ArrayList<>();
        mPresenter = new ToastPresenter(context, AccessibilityManager.getInstance(context));
        mTN = new TN(mPresenter, context.getPackageName(), mToken, mCallbacks, looper);
        mTN = new TN(context, context.getPackageName(), mToken,
                mCallbacks, looper);
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
        mTN.mGravity = context.getResources().getInteger(
@@ -496,7 +495,7 @@ public class Toast {
            return result;
        } else {
            Toast result = new Toast(context, looper);
            View v = result.mPresenter.getTextToastView(text);
            View v = ToastPresenter.getTextToastView(context, text);
            result.mNextView = v;
            result.mDuration = duration;

@@ -565,13 +564,14 @@ public class Toast {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        sService = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
        return sService;
    }

    private static class TN extends ITransientNotification.Stub {
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        private final WindowManager.LayoutParams mParams;

        private static final int SHOW = 0;
        private static final int HIDE = 1;
@@ -608,9 +608,13 @@ public class Toast {
         * The parameter {@code callbacks} is not copied and is accessed with itself as its own
         * lock.
         */
        TN(ToastPresenter presenter, String packageName, Binder token, List<Callback> callbacks,
        TN(Context context, String packageName, Binder token, List<Callback> callbacks,
                @Nullable Looper looper) {
            mPresenter = presenter;
            WindowManager windowManager = context.getSystemService(WindowManager.class);
            AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
            mPresenter = new ToastPresenter(context, windowManager, accessibilityManager,
                    getService(), packageName);
            mParams = mPresenter.getLayoutParams();
            mPackageName = packageName;
            mToken = token;
            mCallbacks = callbacks;
@@ -645,8 +649,6 @@ public class Toast {
                    }
                }
            };

            presenter.startLayoutParams(mParams, packageName);
        }

        private List<Callback> getCallbacks() {
@@ -691,31 +693,9 @@ public class Toast {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                mPresenter.adjustLayoutParams(mParams, windowToken, mDuration, mGravity, mX, mY,
                        mHorizontalMargin, mVerticalMargin);
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                    mWM.addView(mView, mParams);
                    mPresenter.trySendAccessibilityEvent(mView, mPackageName);
                    for (Callback callback : getCallbacks()) {
                        callback.onToastShown();
                    }
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
                mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
                        mHorizontalMargin, mVerticalMargin,
                        new CallbackBinder(getCallbacks(), mHandler));
            }
        }

@@ -723,25 +703,9 @@ public class Toast {
        public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeViewImmediate(mView);
                }


                // Now that we've removed the view it's safe for the server to release
                // the resources.
                try {
                    getService().finishToken(mPackageName, mToken);
                } catch (RemoteException e) {
                }

                for (Callback callback : getCallbacks()) {
                    callback.onToastHidden();
                }
                checkState(mView == mPresenter.getView(),
                        "Trying to hide toast view different than the last one displayed");
                mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
                mView = null;
            }
        }
+126 −14
Original line number Diff line number Diff line
@@ -16,11 +16,18 @@

package android.widget;

import static com.android.internal.util.Preconditions.checkState;

import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,41 +44,94 @@ import com.android.internal.util.ArrayUtils;
 * @hide
 */
public class ToastPresenter {
    private static final String TAG = "ToastPresenter";
    private static final String WINDOW_TITLE = "Toast";
    private static final long SHORT_DURATION_TIMEOUT = 4000;
    private static final long LONG_DURATION_TIMEOUT = 7000;

    /**
     * Returns the default text toast view for message {@code text}.
     */
    public static View getTextToastView(Context context, CharSequence text) {
        View view = LayoutInflater.from(context).inflate(
                R.layout.transient_notification, null);
        TextView textView = view.findViewById(com.android.internal.R.id.message);
        textView.setText(text);
        return view;
    }

    private final Context mContext;
    private final Resources mResources;
    private final WindowManager mWindowManager;
    private final AccessibilityManager mAccessibilityManager;
    private final INotificationManager mNotificationManager;
    private final String mPackageName;
    private final WindowManager.LayoutParams mParams;
    @Nullable private View mView;
    @Nullable private IBinder mToken;

    public ToastPresenter(Context context, AccessibilityManager accessibilityManager) {
    public ToastPresenter(Context context, WindowManager windowManager,
            AccessibilityManager accessibilityManager,
            INotificationManager notificationManager, String packageName) {
        mContext = context;
        mResources = context.getResources();
        mWindowManager = windowManager;
        mAccessibilityManager = accessibilityManager;
        mNotificationManager = notificationManager;
        mPackageName = packageName;
        mParams = createLayoutParams();
    }

    public String getPackageName() {
        return mPackageName;
    }

    public WindowManager.LayoutParams getLayoutParams() {
        return mParams;
    }

    /**
     * Returns the {@link View} being shown at the moment or {@code null} if no toast is being
     * displayed.
     */
    @Nullable
    public View getView() {
        return mView;
    }

    /**
     * Returns the {@link IBinder} token used to display the toast or {@code null} if there is no
     * toast being shown at the moment.
     */
    @Nullable
    public IBinder getToken() {
        return mToken;
    }

    /**
     * Initializes {@code params} with default values for toasts.
     * Creates {@link WindowManager.LayoutParams} with default values for toasts.
     */
    public void startLayoutParams(WindowManager.LayoutParams params, String packageName) {
    private WindowManager.LayoutParams createLayoutParams() {
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.windowAnimations = R.style.Animation_Toast;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setFitInsetsIgnoringVisibility(true);
        params.setTitle("Toast");
        params.setTitle(WINDOW_TITLE);
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        setShowForAllUsersIfApplicable(params, packageName);
        setShowForAllUsersIfApplicable(params, mPackageName);
        return params;
    }

    /**
     * Customizes {@code params} according to other parameters, ready to be passed to {@link
     * WindowManager#addView(View, ViewGroup.LayoutParams)}.
     */
    public void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
    private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
            int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
            float verticalMargin) {
        Configuration config = mResources.getConfiguration();
@@ -97,7 +157,7 @@ public class ToastPresenter {
     * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
     * packageName} is a cross-user package.
     *
     * Implementation note:
     * <p>Implementation note:
     *     This code is safe to be executed in SystemUI and the app's process:
     *         <li>SystemUI: It's running on a trusted domain so apps can't tamper with it. SystemUI
     *             has the permission INTERNAL_SYSTEM_WINDOW needed by the flag, so SystemUI can add
@@ -120,14 +180,66 @@ public class ToastPresenter {
    }

    /**
     * Returns the default text toast view for message {@code text}.
     * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
     */
    public View getTextToastView(CharSequence text) {
        View view = LayoutInflater.from(mContext).inflate(
                R.layout.transient_notification, null);
        TextView textView = view.findViewById(com.android.internal.R.id.message);
        textView.setText(text);
        return view;
    public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
            int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
            @Nullable ITransientNotificationCallback callback) {
        checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
        mView = view;
        mToken = token;

        adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                horizontalMargin, verticalMargin);
        if (mView.getParent() != null) {
            mWindowManager.removeView(mView);
        }
        try {
            mWindowManager.addView(mView, mParams);
        } catch (WindowManager.BadTokenException e) {
            // Since the notification manager service cancels the token right after it notifies us
            // to cancel the toast there is an inherent race and we may attempt to add a window
            // after the token has been invalidated. Let us hedge against that.
            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
            return;
        }
        trySendAccessibilityEvent(mView, mPackageName);
        if (callback != null) {
            try {
                callback.onToastShown();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
            }
        }
    }

    /**
     * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
     * int, int, int, float, float, ITransientNotificationCallback)}.
     *
     * <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
     * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
     */
    public void hide(@Nullable ITransientNotificationCallback callback) {
        checkState(mView != null, "No toast to hide.");

        if (mView.getParent() != null) {
            mWindowManager.removeViewImmediate(mView);
        }
        try {
            mNotificationManager.finishToken(mPackageName, mToken);
        } catch (RemoteException e) {
            Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
        }
        if (callback != null) {
            try {
                callback.onToastHidden();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
            }
        }
        mView = null;
        mToken = null;
    }

    /**
+18 −78
Original line number Diff line number Diff line
@@ -21,15 +21,13 @@ import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import android.widget.ToastPresenter;

import com.android.internal.R;
@@ -49,18 +47,14 @@ import javax.inject.Singleton;
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
    private static final String TAG = "ToastUI";

    /**
     * Values taken from {@link Toast}.
     */
    private static final long DURATION_SHORT = 4000;
    private static final long DURATION_LONG = 7000;

    private final CommandQueue mCommandQueue;
    private final WindowManager mWindowManager;
    private final INotificationManager mNotificationManager;
    private final AccessibilityManager mAccessibilityManager;
    private final ToastPresenter mPresenter;
    private ToastEntry mCurrentToast;
    private final int mGravity;
    private final int mY;
    @Nullable private ToastPresenter mPresenter;
    @Nullable private ITransientNotificationCallback mCallback;

    @Inject
    public ToastUI(Context context, CommandQueue commandQueue) {
@@ -79,7 +73,9 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
        mWindowManager = windowManager;
        mNotificationManager = notificationManager;
        mAccessibilityManager = accessibilityManager;
        mPresenter = new ToastPresenter(context, accessibilityManager);
        Resources resources = mContext.getResources();
        mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
        mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
    }

    @Override
@@ -91,33 +87,21 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
    @MainThread
    public void showToast(String packageName, IBinder token, CharSequence text,
            IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
        if (mCurrentToast != null) {
        if (mPresenter != null) {
            hideCurrentToast();
        }
        View view = mPresenter.getTextToastView(text);
        LayoutParams params = getLayoutParams(packageName, windowToken, duration);
        mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback);
        try {
            mWindowManager.addView(view, params);
        } catch (WindowManager.BadTokenException e) {
            Log.w(TAG, "Error while attempting to show toast from " + packageName, e);
            return;
        }
        mPresenter.trySendAccessibilityEvent(view, packageName);
        if (callback != null) {
            try {
                callback.onToastShown();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e);
            }
        }
        View view = ToastPresenter.getTextToastView(mContext, text);
        mCallback = callback;
        mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager,
                mNotificationManager, packageName);
        mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
    }

    @Override
    @MainThread
    public void hideToast(String packageName, IBinder token) {
        if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName)
                || !Objects.equals(mCurrentToast.token, token)) {
        if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
                || !Objects.equals(mPresenter.getToken(), token)) {
            Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
            return;
        }
@@ -126,51 +110,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {

    @MainThread
    private void hideCurrentToast() {
        if (mCurrentToast.view.getParent() != null) {
            mWindowManager.removeViewImmediate(mCurrentToast.view);
        }
        String packageName = mCurrentToast.packageName;
        try {
            mNotificationManager.finishToken(packageName, mCurrentToast.windowToken);
        } catch (RemoteException e) {
            Log.w(TAG, "Error finishing toast window token from package " + packageName, e);
        }
        if (mCurrentToast.callback != null) {
            try {
                mCurrentToast.callback.onToastHidden();
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e);
            }
        }
        mCurrentToast = null;
    }

    private LayoutParams getLayoutParams(String packageName, IBinder windowToken, int duration) {
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        mPresenter.startLayoutParams(params, packageName);
        int gravity = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_toastDefaultGravity);
        int yOffset = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
        mPresenter.adjustLayoutParams(params, windowToken, duration, gravity, 0, yOffset, 0, 0);
        return params;
    }

    private static class ToastEntry {
        public final String packageName;
        public final IBinder token;
        public final View view;
        public final IBinder windowToken;

        @Nullable
        public final ITransientNotificationCallback callback;

        private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken,
                @Nullable ITransientNotificationCallback callback) {
            this.packageName = packageName;
            this.token = token;
            this.view = view;
            this.windowToken = windowToken;
            this.callback = callback;
        }
        mPresenter.hide(mCallback);
        mPresenter = null;
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -176,7 +176,7 @@ public class ToastUITest extends SysuiTestCase {

        mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);

        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
    }

    @Test
@@ -218,7 +218,7 @@ public class ToastUITest extends SysuiTestCase {
        mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);

        verify(mWindowManager).removeViewImmediate(view);
        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
        verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
        verify(mCallback).onToastHidden();
    }