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

Commit 652801f3 authored by Valentin Iftime's avatar Valentin Iftime
Browse files

Fix Toast memory leaks

 Use weak references for Context in Toast.TN
 The memory leaks only repro with the legacy (non SystemUi Toasts)

Test: atest ToastWindowTest
 atest ToastPresenterTest
 atest ToastUITest
Bug: 204272000
Change-Id: Ia0112978f866ca4c98e48c41b09849b5d5a49276
parent 81c0694a
Loading
Loading
Loading
Loading
+17 −10
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

@@ -205,7 +206,7 @@ public class Toast {
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        tn.mNextView = new WeakReference<>(mNextView);
        final boolean isUiContext = mContext.isUiContext();
        final int displayId = mContext.getDisplayId();

@@ -622,7 +623,7 @@ public class Toast {
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
        View mView;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
        View mNextView;
        WeakReference<View> mNextView;
        int mDuration;

        WindowManager mWM;
@@ -632,7 +633,7 @@ public class Toast {
        private final ToastPresenter mPresenter;

        @GuardedBy("mCallbacks")
        private final List<Callback> mCallbacks;
        private final WeakReference<List<Callback>> mCallbacks;

        /**
         * Creates a {@link ITransientNotification} object.
@@ -649,7 +650,7 @@ public class Toast {
            mParams = mPresenter.getLayoutParams();
            mPackageName = packageName;
            mToken = token;
            mCallbacks = callbacks;
            mCallbacks = new WeakReference<>(callbacks);

            mHandler = new Handler(looper, null) {
                @Override
@@ -685,7 +686,11 @@ public class Toast {

        private List<Callback> getCallbacks() {
            synchronized (mCallbacks) {
                return new ArrayList<>(mCallbacks);
                if (mCallbacks.get() != null) {
                    return new ArrayList<>(mCallbacks.get());
                } else {
                    return new ArrayList<>();
                }
            }
        }

@@ -721,15 +726,17 @@ public class Toast {
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
            if (mNextView != null && mView != mNextView.get()) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                mView = mNextView.get();
                if (mView != null) {
                    mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
                            mHorizontalMargin, mVerticalMargin,
                            new CallbackBinder(getCallbacks(), mHandler));
                }
            }
        }

        @UnsupportedAppUsage
        public void handleHide() {
+31 −19
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;

import java.lang.ref.WeakReference;

/**
 * Class responsible for toast presentation inside app's process and in system UI.
 *
@@ -87,25 +89,33 @@ public class ToastPresenter {
        return view;
    }

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

    public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
            INotificationManager notificationManager, String packageName) {
        mContext = context;
        mResources = context.getResources();
        mWindowManager = context.getSystemService(WindowManager.class);
        mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
        mNotificationManager = notificationManager;
        mPackageName = packageName;
        mAccessibilityManager = accessibilityManager;
        mContextPackageName = context.getPackageName();
        mParams = createLayoutParams();

        // We obtain AccessibilityManager manually via its constructor instead of using method
        // AccessibilityManager.getInstance() for 2 reasons:
        //   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
        //   2. getInstance() caches the instance for the process even if we pass a different
        //      context to it. This is problematic for multi-user because callers can pass a context
        //      created via Context.createContextAsUser().
        mAccessibilityManager = new WeakReference<>(
                new AccessibilityManager(context, accessibilityManager, context.getUserId()));
    }

    public String getPackageName() {
@@ -173,7 +183,7 @@ public class ToastPresenter {
        params.y = yOffset;
        params.horizontalMargin = horizontalMargin;
        params.verticalMargin = verticalMargin;
        params.packageName = mContext.getPackageName();
        params.packageName = mContextPackageName;
        params.hideTimeoutMilliseconds =
                (duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
        params.token = windowToken;
@@ -270,8 +280,9 @@ public class ToastPresenter {
    public void hide(@Nullable ITransientNotificationCallback callback) {
        checkState(mView != null, "No toast to hide.");

        if (mView.getParent() != null) {
            mWindowManager.removeViewImmediate(mView);
        final WindowManager windowManager = mWindowManager.get();
        if (mView.getParent() != null && windowManager != null) {
            windowManager.removeViewImmediate(mView);
        }
        try {
            mNotificationManager.finishToken(mPackageName, mToken);
@@ -295,14 +306,11 @@ public class ToastPresenter {
     * enabled.
     */
    public void trySendAccessibilityEvent(View view, String packageName) {
        // We obtain AccessibilityManager manually via its constructor instead of using method
        // AccessibilityManager.getInstance() for 2 reasons:
        //   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
        //   2. getInstance() caches the instance for the process even if we pass a different
        //      context to it. This is problematic for multi-user because callers can pass a context
        //      created via Context.createContextAsUser().
        final AccessibilityManager accessibilityManager =
                new AccessibilityManager(mContext, mAccessibilityManager, mContext.getUserId());
        final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
        if (accessibilityManager == null) {
            return;
        }

        if (!accessibilityManager.isEnabled()) {
            accessibilityManager.removeClient();
            return;
@@ -320,11 +328,15 @@ public class ToastPresenter {
    }

    private void addToastView() {
        final WindowManager windowManager = mWindowManager.get();
        if (windowManager == null) {
            return;
        }
        if (mView.getParent() != null) {
            mWindowManager.removeView(mView);
            windowManager.removeView(mView);
        }
        try {
            mWindowManager.addView(mView, mParams);
            windowManager.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