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

Commit c6d6d4a4 authored by Winson Chung's avatar Winson Chung
Browse files

Fixing indexing issue causing certain items not to load. Performance...

Fixing indexing issue causing certain items not to load.  Performance improvements + testing temporary loading scheme.

Change-Id: I65bfb60237f397ff546b77fbef1b4a7d1c8cba07
parent 8dac3bf9
Loading
Loading
Loading
Loading
+126 −106
Original line number Diff line number Diff line
@@ -16,8 +16,8 @@

package android.widget;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import android.content.ComponentName;
@@ -85,6 +85,10 @@ public class RemoteViewsAdapter extends BaseAdapter {
        public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
            mConnected = true;

            // start the background loader
            mViewCache.startBackgroundLoader();

            // notifyDataSetChanged should be called first, to ensure that the
            // views are not updated twice
            notifyDataSetChanged();
@@ -112,12 +116,15 @@ public class RemoteViewsAdapter extends BaseAdapter {
        public void onServiceDisconnected(ComponentName name) {
            mRemoteViewsFactory = null;
            mConnected = false;
            if (mCallback != null)
                mCallback.onRemoteAdapterDisconnected();

            // clear the main/worker queues
            mMainQueue.removeMessages(0);
            mWorkerQueue.removeMessages(0);
            
            // stop the background loader
            mViewCache.stopBackgroundLoader();

            if (mCallback != null)
                mCallback.onRemoteAdapterDisconnected();
        }

        public IRemoteViewsFactory getRemoteViewsFactory() {
@@ -135,6 +142,9 @@ public class RemoteViewsAdapter extends BaseAdapter {
    private class RemoteViewsCache {
        private RemoteViewsInfo mViewCacheInfo;
        private RemoteViewsIndexInfo[] mViewCache;
        private int[] mTmpViewCacheLoadIndices;
        private LinkedList<Integer> mViewCacheLoadIndices;
        private boolean mBackgroundLoaderEnabled;

        // if a user loading view is not provided, then we create a temporary one
        // for the user using the height of the first view
@@ -150,16 +160,6 @@ public class RemoteViewsAdapter extends BaseAdapter {
        private int mCacheSlack;
        private final float mCacheSlackPercentage = 0.75f;

        // determines whether to reorder the posted items on the worker thread
        // so that the items in the current window can be loaded first
        private int mPriorityLoadingWindowSize;
        private int mPriorityLoadingWindowStart;
        private int mPriorityLoadingWindowEnd;

        // determines which way to load items in the current window based on how
        // the window shifted last
        private boolean mLoadUpwards;

        /**
         * The data structure stored at each index of the cache. Any member 
         * that is not invalidated persists throughout the lifetime of the cache.
@@ -218,22 +218,21 @@ public class RemoteViewsAdapter extends BaseAdapter {
            mCacheSlack = Math.round(mCacheSlackPercentage * mHalfCacheSize);
            mViewCacheStartPosition = 0;
            mViewCacheEndPosition = -1;
            mPriorityLoadingWindowSize = 4;
            mPriorityLoadingWindowStart = 0;
            mPriorityLoadingWindowEnd = 0;
            mLoadUpwards = false;
            mBackgroundLoaderEnabled = false;

            // initialize the cache
            int cacheSize = 2 * mHalfCacheSize + 1;
            mViewCacheInfo = new RemoteViewsInfo();
            mViewCache = new RemoteViewsIndexInfo[2 * mHalfCacheSize + 1];
            mViewCache = new RemoteViewsIndexInfo[cacheSize];
            for (int i = 0; i < mViewCache.length; ++i) {
                mViewCache[i] = new RemoteViewsIndexInfo();
            }
            mTmpViewCacheLoadIndices = new int[cacheSize];
            mViewCacheLoadIndices = new LinkedList<Integer>();
        }

        private final boolean contains(int position) {
            // take the modulo of the position
            return (mViewCacheStartPosition <= position) && (position < mViewCacheEndPosition);
            return (mViewCacheStartPosition <= position) && (position <= mViewCacheEndPosition);
        }

        private final boolean containsAndIsValid(int position) {
@@ -247,6 +246,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
        }

        private final int getCacheIndex(int position) {
            // take the modulo of the position
            return (mViewCache.length + (position % mViewCache.length)) % mViewCache.length;
        }

@@ -298,7 +298,7 @@ public class RemoteViewsAdapter extends BaseAdapter {

                synchronized (mViewCache) {
                    // skip if the window has moved
                    if (position < mViewCacheStartPosition || position >= mViewCacheEndPosition)
                    if (position < mViewCacheStartPosition || position > mViewCacheEndPosition)
                        return;

                    final int positionIndex = position;
@@ -316,16 +316,28 @@ public class RemoteViewsAdapter extends BaseAdapter {
                                    RemoteViewsIndexInfo indexInfo = mViewCache[cacheIndex];
                                    FrameLayout flipper = indexInfo.flipper;

                                    // recompose the flipper
                                    View loadingView = flipper.getChildAt(0);
                                    loadingView.setVisibility(View.GONE);
                                    flipper.removeAllViews();
                                    flipper.addView(loadingView);
                                    flipper.addView(indexInfo.view.apply(mContext, flipper));

                                    // hide the loader view and bring the new view to the front
                                    flipper.requestLayout();
                                    flipper.invalidate();
                                    // update the flipper
                                    flipper.getChildAt(0).setVisibility(View.GONE);
                                    boolean addNewView = true;
                                    if (flipper.getChildCount() > 1) {
                                        View v = flipper.getChildAt(1);
                                        int typeId = ((Integer) v.getTag()).intValue();
                                        if (typeId == indexInfo.typeId) {
                                            // we can reapply since it is the same type
                                            indexInfo.view.reapply(mContext, v);
                                            v.setVisibility(View.VISIBLE);
                                            if (v.getAnimation() != null) 
                                                v.buildDrawingCache();
                                            addNewView = false;
                                        } else {
                                            flipper.removeViewAt(1);
                                        }
                                    }
                                    if (addNewView) {
                                        View v = indexInfo.view.apply(mContext, flipper);
                                        v.setTag(new Integer(indexInfo.typeId));
                                        flipper.addView(v);
                                    }
                                }
                            }
                        }
@@ -336,12 +348,8 @@ public class RemoteViewsAdapter extends BaseAdapter {

        private RemoteViewsIndexInfo requestCachedIndexInfo(final int position) {
            int indicesToLoadCount = 0;
            int[] indicesToLoad = null;

            synchronized (mViewCache) {
                indicesToLoad = new int[mViewCache.length];
                Arrays.fill(indicesToLoad, 0);

                if (containsAndIsValid(position)) {
                    // return the info if it exists in the window and is loaded
                    return mViewCache[getCacheIndex(position)];
@@ -352,72 +360,40 @@ public class RemoteViewsAdapter extends BaseAdapter {
                if ((mViewCacheEndPosition <= mViewCacheStartPosition) || (Math.abs(position - centerPosition) > mCacheSlack)) {
                    int newStartPosition = position - mHalfCacheSize;
                    int newEndPosition = position + mHalfCacheSize;
                    int frameSize = mHalfCacheSize / 4;
                    int frameCount = (int) Math.ceil(mViewCache.length / (float) frameSize);

                    // prune/add before the current start position
                    int effectiveStart = Math.max(newStartPosition, 0);
                    int effectiveEnd = Math.min(newEndPosition, getCount());

                    mWorkerQueue.removeMessages(0);
                    int effectiveEnd = Math.min(newEndPosition, getCount() - 1);

                    // invalidate items in the queue
                    boolean loadFromBeginning = effectiveStart < mViewCacheStartPosition;
                    int numLoadFromBeginning = mViewCacheStartPosition - effectiveStart;
                    boolean loadFromEnd = effectiveEnd > mViewCacheEndPosition;
                    int overlapStart = Math.max(mViewCacheStartPosition, effectiveStart);
                    int overlapEnd = Math.min(Math.max(mViewCacheStartPosition, mViewCacheEndPosition), effectiveEnd);
                    for (int i = newStartPosition; i < newEndPosition; ++i) {
                        if (loadFromBeginning && (effectiveStart <= i) && (i < overlapStart)) {
                            // load new items at the beginning in reverse order
                            mViewCache[getCacheIndex(i)].invalidate();
                            indicesToLoad[indicesToLoadCount++] = effectiveStart
                                    + (numLoadFromBeginning - (i - effectiveStart) - 1);
                        } else if (loadFromEnd && (overlapEnd <= i) && (i < effectiveEnd)) {
                            mViewCache[getCacheIndex(i)].invalidate();
                            indicesToLoad[indicesToLoadCount++] = i;
                        } else if ((overlapStart <= i) && (i < overlapEnd)) {
                    for (int i = 0; i < (frameSize * frameCount); ++i) {
                        int index = newStartPosition + ((i % frameSize) * frameCount + (i / frameSize));
                        
                        if (index <= newEndPosition) {
                            if ((overlapStart <= index) && (index <= overlapEnd)) {
                                // load the stuff in the middle that has not already
                                // been loaded
                            if (!mViewCache[getCacheIndex(i)].isValid()) {
                                indicesToLoad[indicesToLoadCount++] = i;
                                if (!mViewCache[getCacheIndex(index)].isValid()) {
                                    mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
                                }
                            } else if ((effectiveStart <= index) && (index <= effectiveEnd)) {
                                // invalidate and load all new effective items
                                mViewCache[getCacheIndex(index)].invalidate();
                                mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
                            } else {
                                // invalidate all other cache indices (outside the effective start/end)
                            mViewCache[getCacheIndex(i)].invalidate();
                                // but don't load
                                mViewCache[getCacheIndex(index)].invalidate();
                            }
                        }
                    }

                    mViewCacheStartPosition = newStartPosition;
                    mViewCacheEndPosition = newEndPosition;
                    mPriorityLoadingWindowStart = position;
                    mPriorityLoadingWindowEnd = position + mPriorityLoadingWindowSize;
                    mLoadUpwards = loadFromBeginning && !loadFromEnd;
                } else if (contains(position)) {
                    // prioritize items around this position so that they load first
                    if (position < mPriorityLoadingWindowStart || position > mPriorityLoadingWindowEnd) {
                        mWorkerQueue.removeMessages(0);

                        int index;
                        int effectiveStart = Math.max(position - mPriorityLoadingWindowSize, 0);
                        int effectiveEnd = 0;
                        synchronized (mViewCacheInfo) {
                            effectiveEnd = Math.min(position + mPriorityLoadingWindowSize - 1,
                                    mViewCacheInfo.count - 1);
                        }

                        for (int i = 0; i < mViewCache.length; ++i) {
                            if (mLoadUpwards) {
                                index = effectiveEnd - i;
                            } else {
                                index = effectiveStart + i;
                            }
                            if (!mViewCache[getCacheIndex(index)].isValid()) {
                                indicesToLoad[indicesToLoadCount++] = index;
                            }
                        }

                        mPriorityLoadingWindowStart = effectiveStart;
                        mPriorityLoadingWindowEnd = position + mPriorityLoadingWindowSize;
                    }
                }
            }

@@ -426,15 +402,15 @@ public class RemoteViewsAdapter extends BaseAdapter {
            synchronized (mViewCacheInfo) {
                length = mViewCacheInfo.count;
            }
            if (indicesToLoadCount > 0) {
                synchronized (mViewCacheLoadIndices) {
                    mViewCacheLoadIndices.clear();
                    for (int i = 0; i < indicesToLoadCount; ++i) {
                final int index = indicesToLoad[i];
                        final int index = mTmpViewCacheLoadIndices[i];
                        if (0 <= index && index < length) {
                    mWorkerQueue.post(new Runnable() {
                        @Override
                        public void run() {
                            updateRemoteViewsInfo(index);
                            mViewCacheLoadIndices.addLast(index);
                        }
                    }
                    });
                }
            }

@@ -446,7 +422,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
            if (mServiceConnection.isConnected()) {
                // create the flipper views if necessary (we have to do this now
                // for all the flippers while we have the reference to the parent)
                createInitialLoadingFlipperViews(parent);
                initializeLoadingViews(parent);

                // request the item from the cache (queueing it to load if not
                // in the cache already)
@@ -456,6 +432,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
                synchronized (mViewCache) {
                    int cacheIndex = getCacheIndex(position);
                    FrameLayout flipper = mViewCache[cacheIndex].flipper;
                    flipper.setVisibility(View.VISIBLE);

                    if (indexInfo == null) {
                        // hide the item view and show the loading view
@@ -463,17 +440,12 @@ public class RemoteViewsAdapter extends BaseAdapter {
                        for (int i = 1; i < flipper.getChildCount(); ++i) {
                            flipper.getChildAt(i).setVisibility(View.GONE);
                        }
                        flipper.requestLayout();
                        flipper.invalidate();
                    } else {
                        // hide the loading view and show the item view
                        flipper.setVisibility(View.VISIBLE);
                        for (int i = 0; i < flipper.getChildCount() - 1; ++i) {
                            flipper.getChildAt(i).setVisibility(View.GONE);
                        }
                        flipper.getChildAt(flipper.getChildCount() - 1).setVisibility(View.VISIBLE);
                        flipper.requestLayout();
                        flipper.invalidate();
                    }
                    return flipper;
                }
@@ -481,7 +453,7 @@ public class RemoteViewsAdapter extends BaseAdapter {
            return new View(mContext);
        }

        private void createInitialLoadingFlipperViews(ViewGroup parent) {
        private void initializeLoadingViews(ViewGroup parent) {
            // ensure that the cache has the appropriate initial flipper
            synchronized (mViewCache) {
                if (mViewCache[0].flipper == null) {
@@ -519,6 +491,50 @@ public class RemoteViewsAdapter extends BaseAdapter {
            }
        }

        public void startBackgroundLoader() {
            // initialize the worker runnable
            mBackgroundLoaderEnabled = true;
            mWorkerQueue.post(new Runnable() {
                @Override
                public void run() {
                    while (mBackgroundLoaderEnabled) {
                        int index = -1;
                        synchronized (mViewCacheLoadIndices) {
                            if (!mViewCacheLoadIndices.isEmpty()) {
                                index = mViewCacheLoadIndices.removeFirst();
                            }
                        }
                        if (index < 0) {
                            // there were no items to load, so sleep for a bit
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            // otherwise, try and load the item
                            updateRemoteViewsInfo(index);

                            // sleep for a bit to allow things to catch up after the load
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
        }

        public void stopBackgroundLoader() {
            // clear the items to be loaded
            mBackgroundLoaderEnabled = false;
            synchronized (mViewCacheLoadIndices) {
                mViewCacheLoadIndices.clear();
            }
        }

        public long getItemId(int position) {
            synchronized (mViewCache) {
                if (containsAndIsValid(position)) {
@@ -567,9 +583,13 @@ public class RemoteViewsAdapter extends BaseAdapter {
        }

        public void flushCache() {
            // clear the items to be loaded
            synchronized (mViewCacheLoadIndices) {
                mViewCacheLoadIndices.clear();
            }

            synchronized (mViewCache) {
                // flush the internal cache and invalidate the adapter for future loads
                mWorkerQueue.removeMessages(0);
                mMainQueue.removeMessages(0);

                for (int i = 0; i < mViewCache.length; ++i) {