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

Commit 335c3b68 authored by Adam Cohen's avatar Adam Cohen
Browse files

Caching the FixedSizeRemoteViewsCaches across rotation

-> This prevents unnecessary flashing of collection widgets on rotation

Change-Id: Id29d4952aa640ca90b31dc3e02e2402cc0fb84d3
parent 2daf9dc9
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.widget;

import com.android.internal.R;

import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -69,6 +67,8 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.R;

import java.util.ArrayList;
import java.util.List;

@@ -1813,6 +1813,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        }
        ss.checkedItemCount = mCheckedItemCount;

        if (mRemoteAdapter != null) {
            mRemoteAdapter.saveRemoteViewsCache();
        }

        return ss;
    }

@@ -5974,6 +5978,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        mDeferNotifyDataSetChanged = false;
        // Otherwise, create a new RemoteViewsAdapter for binding
        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
        if (mRemoteAdapter.isDataReady()) {
            setAdapter(mRemoteAdapter);
        }
    }

    /**
+6 −0
Original line number Diff line number Diff line
@@ -813,6 +813,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        if (mRemoteViewsAdapter != null) {
            mRemoteViewsAdapter.saveRemoteViewsCache();
        }
        return new SavedState(superState, mWhichChild);
    }

@@ -984,6 +987,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        mDeferNotifyDataSetChanged = false;
        // Otherwise, create a new RemoteViewsAdapter for binding
        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
        if (mRemoteViewsAdapter.isDataReady()) {
            setAdapter(mRemoteViewsAdapter);
        }
    }

    @Override
+200 −5
Original line number Diff line number Diff line
@@ -29,12 +29,16 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.RemoteViewsService.RemoteViewsFactory;

import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -83,6 +87,26 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
    private Handler mWorkerQueue;
    private Handler mMainQueue;

    // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
    // structures;
    private static final HashMap<Pair<Intent.FilterComparison, Integer>, Parcel>
            sCachedRemoteViewsCaches = new HashMap<Pair<Intent.FilterComparison, Integer>,
            Parcel>();
    private static final HashMap<Pair<Intent.FilterComparison, Integer>, Runnable>
            sRemoteViewsCacheRemoveRunnables = new HashMap<Pair<Intent.FilterComparison, Integer>,
            Runnable>();
    private static HandlerThread sCacheRemovalThread;
    private static Handler sCacheRemovalQueue;

    // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
    // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
    // duration, the cache is dropped.
    private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;

    // Used to indicate to the AdapterView that it can use this Adapter immediately after
    // construction (happens when we have a cached FixedSizeRemoteViewsCache).
    private boolean mDataReady = false;

    /**
     * An interface for the RemoteAdapter to notify other classes when adapters
     * are actually connected to/disconnected from their actual services.
@@ -331,7 +355,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
    /**
     * The meta-data associated with the cache in it's current state.
     */
    private static class RemoteViewsMetaData {
    private static class RemoteViewsMetaData implements Parcelable {
        int count;
        int viewTypeCount;
        boolean hasStableIds;
@@ -350,6 +374,51 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            reset();
        }

        public RemoteViewsMetaData(Parcel src) {
            count = src.readInt();
            viewTypeCount = src.readInt();
            hasStableIds = src.readInt() == 0 ? false : true;
            mFirstViewHeight = src.readInt();
            if (src.readInt() != 0) {
                mUserLoadingView = new RemoteViews(src);
            }
            if (src.readInt() != 0) {
                mFirstView = new RemoteViews(src);
            }
            int count = src.readInt();
            for (int i = 0; i < count; i++) {
                mTypeIdIndexMap.put(src.readInt(), src.readInt());
            }
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(count);
            dest.writeInt(viewTypeCount);
            dest.writeInt(hasStableIds ? 1 : 0);
            dest.writeInt(mFirstViewHeight);
            dest.writeInt(mUserLoadingView != null ? 1 : 0);
            if (mUserLoadingView != null) {
                mUserLoadingView.writeToParcel(dest, flags);
            }
            dest.writeInt(mFirstView != null ? 1 : 0);
            if (mFirstView != null) {
                mFirstView.writeToParcel(dest, flags);
            }

            int count = mTypeIdIndexMap.size();
            dest.writeInt(count);
            for (Integer key: mTypeIdIndexMap.keySet()) {
                dest.writeInt(key);
                dest.writeInt(mTypeIdIndexMap.get(key));
            }
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public void set(RemoteViewsMetaData d) {
            synchronized (d) {
                count = d.count;
@@ -460,7 +529,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
    /**
     * The meta-data associated with a single item in the cache.
     */
    private static class RemoteViewsIndexMetaData {
    private static class RemoteViewsIndexMetaData implements Parcelable {
        int typeId;
        long itemId;
        boolean isRequested;
@@ -469,6 +538,22 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            set(v, itemId, requested);
        }

        public RemoteViewsIndexMetaData(Parcel src) {
            typeId = src.readInt();
            itemId = src.readLong();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(typeId);
            dest.writeLong(itemId);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public void set(RemoteViews v, long id, boolean requested) {
            itemId = id;
            if (v != null) {
@@ -478,12 +563,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            }
            isRequested = requested;
        }


    }

    /**
     *
     */
    private static class FixedSizeRemoteViewsCache {
    private static class FixedSizeRemoteViewsCache implements Parcelable {
        private static final String TAG = "FixedSizeRemoteViewsCache";

        // The meta data related to all the RemoteViews, ie. count, is stable, etc.
@@ -545,6 +632,57 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
            mLoadIndices = new HashSet<Integer>();
        }

        public FixedSizeRemoteViewsCache(Parcel src) {
            mMaxCount = src.readInt();
            mMaxCountSlack = src.readInt();
            mPreloadLowerBound = src.readInt();
            mPreloadUpperBound = src.readInt();
            mMetaData = new RemoteViewsMetaData(src);
            int count = src.readInt();
            mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
            for (int i = 0; i < count; i++) {
                mIndexMetaData.put(src.readInt(), new RemoteViewsIndexMetaData(src));
            }
            count = src.readInt();
            mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
            for (int i = 0; i < count; i++) {
                mIndexRemoteViews.put(src.readInt(), new RemoteViews(src));
            }

            mTemporaryMetaData = new RemoteViewsMetaData();
            mRequestedIndices = new HashSet<Integer>();
            mLastRequestedIndex = -1;
            mLoadIndices = new HashSet<Integer>();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mMaxCount);
            dest.writeInt(mMaxCountSlack);
            dest.writeInt(mPreloadLowerBound);
            dest.writeInt(mPreloadUpperBound);
            mMetaData.writeToParcel(dest, 0);

            // We write the index data and cache
            int count = mIndexMetaData.size();
            dest.writeInt(count);
            for (Integer key: mIndexMetaData.keySet()) {
                dest.writeInt(key);
                mIndexMetaData.get(key).writeToParcel(dest, flags);
            }
            count = mIndexRemoteViews.size();
            dest.writeInt(count);
            for (Integer key: mIndexRemoteViews.keySet()) {
                dest.writeInt(key);
                mIndexRemoteViews.get(key).writeToParcel(dest, flags);
            }
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public void insert(int position, RemoteViews v, long itemId, boolean isRequested) {
            // Trim the cache if we go beyond the count
            if (mIndexRemoteViews.size() >= mMaxCount) {
@@ -747,12 +885,31 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
        mWorkerQueue = new Handler(mWorkerThread.getLooper());
        mMainQueue = new Handler(Looper.myLooper(), this);

        if (sCacheRemovalThread == null) {
            sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
            sCacheRemovalThread.start();
            sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
        }

        // Initialize the cache and the service connection on startup
        mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
        mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
        mServiceConnection = new RemoteViewsAdapterServiceConnection(this);

        Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison, Integer>
                (new Intent.FilterComparison(mIntent), mAppWidgetId);

        synchronized(sCachedRemoteViewsCaches) {
            if (sCachedRemoteViewsCaches.containsKey(key)) {
                Parcel src = sCachedRemoteViewsCaches.get(key);
                src.setDataPosition(0);
                mCache = new FixedSizeRemoteViewsCache(src);
                mDataReady = true;
            } else {
                mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
                requestBindService();
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
@@ -765,6 +922,44 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
        }
    }

    public boolean isDataReady() {
        return mDataReady;
    }

    public void saveRemoteViewsCache() {
        final Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison,
                Integer> (new Intent.FilterComparison(mIntent), mAppWidgetId);

        synchronized(sCachedRemoteViewsCaches) {
            // If we already have a remove runnable posted for this key, remove it.
            if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
                sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
                sRemoteViewsCacheRemoveRunnables.remove(key);
            }

            Parcel p = Parcel.obtain();
            synchronized(mCache) {
                mCache.writeToParcel(p, 0);
            }
            sCachedRemoteViewsCaches.put(key, p);
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    synchronized (sCachedRemoteViewsCaches) {
                        if (sCachedRemoteViewsCaches.containsKey(key)) {
                            sCachedRemoteViewsCaches.remove(key);
                        }
                        if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
                            sRemoteViewsCacheRemoveRunnables.remove(key);
                        }
                    }
                }
            };
            sRemoteViewsCacheRemoveRunnables.put(key, r);
            sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
        }
    }

    private void loadNextIndexInBackground() {
        mWorkerQueue.post(new Runnable() {
            @Override