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

Commit b874e5ba authored by Adam Powell's avatar Adam Powell Committed by Android (Google) Code Review
Browse files

Merge "Avoid potential leaks with Runnables posted from ProgressBar"

parents 4527a924 a050663f
Loading
Loading
Loading
Loading
+98 −25
Original line number Diff line number Diff line
@@ -37,9 +37,11 @@ import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.util.Pool;
import android.util.Poolable;
import android.util.PoolableManager;
import android.util.Pools;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -55,6 +57,8 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;

import java.util.ArrayList;


/**
 * <p>
@@ -218,6 +222,10 @@ public class ProgressBar extends View {
    private boolean mShouldStartAnimationDrawable;

    private boolean mInDrawing;
    private boolean mAttached;
    private boolean mRefreshIsPosted;

    private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();

    private AccessibilityEventSender mAccessibilityEventSender;

@@ -558,29 +566,76 @@ public class ProgressBar extends View {
    }

    private class RefreshProgressRunnable implements Runnable {
        public void run() {
            synchronized (ProgressBar.this) {
                final int count = mRefreshData.size();
                for (int i = 0; i < count; i++) {
                    final RefreshData rd = mRefreshData.get(i);
                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
                    rd.recycle();
                }
                mRefreshData.clear();
                mRefreshIsPosted = false;
            }
        }
    }

        private int mId;
        private int mProgress;
        private boolean mFromUser;
    private static class RefreshData implements Poolable<RefreshData> {
        public int id;
        public int progress;
        public boolean fromUser;
        
        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
            mId = id;
            mProgress = progress;
            mFromUser = fromUser;
        private RefreshData mNext;
        private boolean mIsPooled;
        
        private static final int POOL_MAX = 24;
        private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
                Pools.finitePool(new PoolableManager<RefreshData>() {
                    @Override
                    public RefreshData newInstance() {
                        return new RefreshData();
                    }

        public void run() {
            doRefreshProgress(mId, mProgress, mFromUser, true);
            // Put ourselves back in the cache when we are done
            mRefreshProgressRunnable = this;
                    @Override
                    public void onAcquired(RefreshData element) {
                    }

        public void setup(int id, int progress, boolean fromUser) {
            mId = id;
            mProgress = progress;
            mFromUser = fromUser;
                    @Override
                    public void onReleased(RefreshData element) {
                    }
                }, POOL_MAX));

        public static RefreshData obtain(int id, int progress, boolean fromUser) {
            RefreshData rd = sPool.acquire();
            rd.id = id;
            rd.progress = progress;
            rd.fromUser = fromUser;
            return rd;
        }
        
        public void recycle() {
            sPool.release(this);
        }

        @Override
        public void setNextPoolable(RefreshData element) {
            mNext = element;
        }

        @Override
        public RefreshData getNextPoolable() {
            return mNext;
        }

        @Override
        public boolean isPooled() {
            return mIsPooled;
        }

        @Override
        public void setPooled(boolean isPooled) {
            mIsPooled = isPooled;
        }
    }
    
    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
@@ -619,14 +674,16 @@ public class ProgressBar extends View {
            if (mRefreshProgressRunnable != null) {
                // Use cached RefreshProgressRunnable if available
                r = mRefreshProgressRunnable;
                // Uncache it
                mRefreshProgressRunnable = null;
                r.setup(id, progress, fromUser);
            } else {
                // Make a new one
                r = new RefreshProgressRunnable(id, progress, fromUser);
                r = new RefreshProgressRunnable();
            }
            final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
            mRefreshData.add(rd);
            if (mAttached && !mRefreshIsPosted) {
                post(r);
                mRefreshIsPosted = true;
            }
        }
    }
    
@@ -1092,6 +1149,18 @@ public class ProgressBar extends View {
        if (mIndeterminate) {
            startAnimation();
        }
        if (mRefreshData != null) {
            synchronized (this) {
                final int count = mRefreshData.size();
                for (int i = 0; i < count; i++) {
                    final RefreshData rd = mRefreshData.get(i);
                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
                    rd.recycle();
                }
                mRefreshData.clear();
            }
        }
        mAttached = true;
    }

    @Override
@@ -1102,12 +1171,16 @@ public class ProgressBar extends View {
        if (mRefreshProgressRunnable != null) {
            removeCallbacks(mRefreshProgressRunnable);
        }
        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
            removeCallbacks(mRefreshProgressRunnable);
        }
        if (mAccessibilityEventSender != null) {
            removeCallbacks(mAccessibilityEventSender);
        }
        // This should come after stopAnimation(), otherwise an invalidate message remains in the
        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
        super.onDetachedFromWindow();
        mAttached = false;
    }

    @Override