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

Commit a050663f authored by Adam Powell's avatar Adam Powell
Browse files

Avoid potential leaks with Runnables posted from ProgressBar

Bug 6093695

Handle pending progress updates when a view is not attached when the
view becomes attached again. Batch pending progress updates together
rather than posting separate runnables for each.

Change-Id: I5dea671d5b9fbe1302912ca4734a63955e77ff4d
parent d3ce6f50
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