Bug 976064 - Create a loader per panel view instead of per panel id. r=margaret a=lsblakk
authorLucas Rocha <lucasr@mozilla.com>
Thu, 10 Apr 2014 16:19:36 +0100
changeset 192130 6eb15f7225985fdbf30337955b4a34f1fdd925c4
parent 192129 e3c2a55f23e3fcf1f7d936a20d485d777fd51536
child 192131 9dbdb733f3ad9dd07f63b8a5f3a08346414f6d43
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, lsblakk
bugs976064
milestone30.0a2
Bug 976064 - Create a loader per panel view instead of per panel id. r=margaret a=lsblakk * * * Bug 976064 - Add 'index' property to ViewConfig (r=margaret) * * * Bug 976064 - Create a loader per panel view instead of per dataset id (r=margaret)
mobile/android/base/home/DynamicPanel.java
mobile/android/base/home/FramePanelLayout.java
mobile/android/base/home/HomeConfig.java
mobile/android/base/home/PanelLayout.java
--- a/mobile/android/base/home/DynamicPanel.java
+++ b/mobile/android/base/home/DynamicPanel.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.home.PanelLayou
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -169,65 +170,42 @@ public class DynamicPanel extends HomeFr
                 @Override
                 public void run() {
                     handleDatasetRefreshRequest(message);
                 }
             });
         }
     }
 
-    private static int generateLoaderId(String datasetId) {
-        return datasetId.hashCode();
-    }
-
     /**
      * Handles a dataset refresh request from Gecko. This is usually
      * triggered by a HomeStorage.save() call in an add-on.
      */
     private void handleDatasetRefreshRequest(JSONObject message) {
         final String datasetId;
         try {
             datasetId = message.getString("datasetId");
         } catch (JSONException e) {
             Log.e(LOGTAG, "Failed to handle dataset refresh", e);
             return;
         }
 
+        final Activity activity = getActivity();
+        if (activity == null) {
+            return;
+        }
+
         Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
 
-        final int loaderId = generateLoaderId(datasetId);
-
-        final LoaderManager lm = getLoaderManager();
-        final Loader<?> loader = (Loader<?>) lm.getLoader(loaderId);
-
-        // Only restart a loader if there's already an active one
-        // for the given dataset ID. Do nothing otherwise.
-        if (loader != null) {
-            final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
-            final DatasetRequest request = datasetLoader.getRequest();
-
-            // Ensure the refresh request doesn't affect the view's filter
-            // stack (i.e. use DATASET_LOAD type) but keep the current
-            // dataset ID and filter.
-            final DatasetRequest newRequest =
-                   new DatasetRequest(DatasetRequest.Type.DATASET_LOAD,
-                                      request.getDatasetId(),
-                                      request.getFilterDetail());
-
-            restartDatasetLoader(newRequest);
-        }
+        final ContentResolver cr = activity.getContentResolver();
+        cr.notifyChange(getDatasetNotificationUri(datasetId), null);
     }
 
-    private void restartDatasetLoader(DatasetRequest request) {
-        final Bundle bundle = new Bundle();
-        bundle.putParcelable(DATASET_REQUEST, request);
-
-        // Ensure one loader per dataset
-        final int loaderId = generateLoaderId(request.getDatasetId());
-        getLoaderManager().restartLoader(loaderId, bundle, mLoaderCallbacks);
+    private static Uri getDatasetNotificationUri(String datasetId) {
+        return Uri.withAppendedPath(HomeItems.CONTENT_URI, datasetId);
     }
 
     /**
      * Used by the PanelLayout to make load and reset requests to
      * the holding fragment.
      */
     private class PanelDatasetHandler implements DatasetHandler {
         @Override
@@ -235,68 +213,91 @@ public class DynamicPanel extends HomeFr
             Log.d(LOGTAG, "Requesting request: " + request);
 
             // Ignore dataset requests while the fragment is not
             // allowed to load its content.
             if (!getCanLoadHint()) {
                 return;
             }
 
-            restartDatasetLoader(request);
+            final Bundle bundle = new Bundle();
+            bundle.putParcelable(DATASET_REQUEST, request);
+
+            getLoaderManager().restartLoader(request.getViewIndex(),
+                                             bundle, mLoaderCallbacks);
         }
 
         @Override
-        public void resetDataset(String datasetId) {
-            Log.d(LOGTAG, "Resetting dataset: " + datasetId);
+        public void resetDataset(int viewIndex) {
+            Log.d(LOGTAG, "Resetting dataset: " + viewIndex);
 
             final LoaderManager lm = getLoaderManager();
-            final int loaderId = generateLoaderId(datasetId);
 
             // Release any resources associated with the dataset if
             // it's currently loaded in memory.
-            final Loader<?> datasetLoader = lm.getLoader(loaderId);
+            final Loader<?> datasetLoader = lm.getLoader(viewIndex);
             if (datasetLoader != null) {
                 datasetLoader.reset();
             }
         }
     }
 
     /**
      * Cursor loader for the panel datasets.
      */
     private static class PanelDatasetLoader extends SimpleCursorLoader {
-        private final DatasetRequest mRequest;
+        private DatasetRequest mRequest;
 
         public PanelDatasetLoader(Context context, DatasetRequest request) {
             super(context);
             mRequest = request;
         }
 
         public DatasetRequest getRequest() {
             return mRequest;
         }
 
         @Override
+        public void onContentChanged() {
+            // Ensure the refresh request doesn't affect the view's filter
+            // stack (i.e. use DATASET_LOAD type) but keep the current
+            // dataset ID and filter.
+            final DatasetRequest newRequest =
+                   new DatasetRequest(mRequest.getViewIndex(),
+                                      DatasetRequest.Type.DATASET_LOAD,
+                                      mRequest.getDatasetId(),
+                                      mRequest.getFilterDetail());
+
+            mRequest = newRequest;
+            super.onContentChanged();
+        }
+
+        @Override
         public Cursor loadCursor() {
             final ContentResolver cr = getContext().getContentResolver();
 
             final String selection;
             final String[] selectionArgs;
 
             // Null represents the root filter
             if (mRequest.getFilter() == null) {
                 selection = DBUtils.concatenateWhere(HomeItems.DATASET_ID + " = ?", HomeItems.FILTER + " IS NULL");
                 selectionArgs = new String[] { mRequest.getDatasetId() };
             } else {
                 selection = DBUtils.concatenateWhere(HomeItems.DATASET_ID + " = ?", HomeItems.FILTER + " = ?");
                 selectionArgs = new String[] { mRequest.getDatasetId(), mRequest.getFilter() };
             }
 
             // XXX: You can use CONTENT_FAKE_URI for development to pull items from fake_home_items.json.
-            return cr.query(HomeItems.CONTENT_URI, null, selection, selectionArgs, null);
+            final Cursor c = cr.query(HomeItems.CONTENT_URI, null, selection, selectionArgs, null);
+
+            final Uri notificationUri = getDatasetNotificationUri(mRequest.getDatasetId());
+            c.setNotificationUri(cr, notificationUri);
+
+            return c;
         }
     }
 
     /**
      * LoaderCallbacks implementation that interacts with the LoaderManager.
      */
     private class PanelLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
@@ -305,27 +306,28 @@ public class DynamicPanel extends HomeFr
 
             Log.d(LOGTAG, "Creating loader for request: " + request);
             return new PanelDatasetLoader(getActivity(), request);
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
             final DatasetRequest request = getRequestFromLoader(loader);
+            Log.d(LOGTAG, "Finished loader for request: " + request);
 
-            Log.d(LOGTAG, "Finished loader for request: " + request);
             mLayout.deliverDataset(request, cursor);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
             final DatasetRequest request = getRequestFromLoader(loader);
             Log.d(LOGTAG, "Resetting loader for request: " + request);
+
             if (mLayout != null) {
-                mLayout.releaseDataset(request.getDatasetId());
+                mLayout.releaseDataset(request.getViewIndex());
             }
         }
 
         private DatasetRequest getRequestFromLoader(Loader<Cursor> loader) {
             final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
             return datasetLoader.getRequest();
         }
     }
--- a/mobile/android/base/home/FramePanelLayout.java
+++ b/mobile/android/base/home/FramePanelLayout.java
@@ -34,14 +34,18 @@ class FramePanelLayout extends PanelLayo
     }
 
     @Override
     public void load() {
         Log.d(LOGTAG, "Loading");
 
         if (mChildView instanceof DatasetBacked) {
             final FilterDetail filter = new FilterDetail(mChildConfig.getFilter(), null);
-            final DatasetRequest request = new DatasetRequest(mChildConfig.getDatasetId(), filter);
+
+            final DatasetRequest request = new DatasetRequest(mChildConfig.getIndex(),
+                                                              mChildConfig.getDatasetId(),
+                                                              filter);
+
             Log.d(LOGTAG, "Requesting child request: " + request);
             requestDataset(request);
         }
     }
 }
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -137,17 +137,17 @@ public final class HomeConfig {
 
             final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
             if (jsonViews != null) {
                 mViews = new ArrayList<ViewConfig>();
 
                 final int viewCount = jsonViews.length();
                 for (int i = 0; i < viewCount; i++) {
                     final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
-                    final ViewConfig viewConfig = new ViewConfig(jsonViewConfig);
+                    final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig);
                     mViews.add(viewConfig);
                 }
             } else {
                 mViews = null;
             }
 
             mFlags = EnumSet.noneOf(Flags.class);
 
@@ -577,66 +577,71 @@ public final class HomeConfig {
             @Override
             public ItemHandler[] newArray(final int size) {
                 return new ItemHandler[size];
             }
         };
     }
 
     public static class ViewConfig implements Parcelable {
+        private final int mIndex;
         private final ViewType mType;
         private final String mDatasetId;
         private final ItemType mItemType;
         private final ItemHandler mItemHandler;
         private final String mBackImageUrl;
         private final String mFilter;
 
         private static final String JSON_KEY_TYPE = "type";
         private static final String JSON_KEY_DATASET = "dataset";
         private static final String JSON_KEY_ITEM_TYPE = "itemType";
         private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
         private static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
         private static final String JSON_KEY_FILTER = "filter";
 
-        public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
+        public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
+            mIndex = index;
             mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
             mDatasetId = json.getString(JSON_KEY_DATASET);
             mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
             mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
             mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null);
             mFilter = json.optString(JSON_KEY_FILTER, null);
 
             validate();
         }
 
         @SuppressWarnings("unchecked")
         public ViewConfig(Parcel in) {
+            mIndex = in.readInt();
             mType = (ViewType) in.readParcelable(getClass().getClassLoader());
             mDatasetId = in.readString();
             mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
             mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
             mBackImageUrl = in.readString();
             mFilter = in.readString();
 
             validate();
         }
 
         public ViewConfig(ViewConfig viewConfig) {
+            mIndex = viewConfig.mIndex;
             mType = viewConfig.mType;
             mDatasetId = viewConfig.mDatasetId;
             mItemType = viewConfig.mItemType;
             mItemHandler = viewConfig.mItemHandler;
             mBackImageUrl = viewConfig.mBackImageUrl;
             mFilter = viewConfig.mFilter;
 
             validate();
         }
 
-        public ViewConfig(ViewType type, String datasetId, ItemType itemType,
+        public ViewConfig(int index, ViewType type, String datasetId, ItemType itemType,
                           ItemHandler itemHandler, String backImageUrl, String filter) {
+            mIndex = index;
             mType = type;
             mDatasetId = datasetId;
             mItemType = itemType;
             mItemHandler = itemHandler;
             mBackImageUrl = backImageUrl;
             mFilter = filter;
 
             validate();
@@ -655,16 +660,20 @@ public final class HomeConfig {
                 throw new IllegalArgumentException("Can't create ViewConfig with null item type");
             }
 
             if (mItemHandler == null) {
                 throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
             }
         }
 
+        public int getIndex() {
+            return mIndex;
+        }
+
         public ViewType getType() {
             return mType;
         }
 
         public String getDatasetId() {
             return mDatasetId;
         }
 
@@ -705,16 +714,17 @@ public final class HomeConfig {
 
         @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mIndex);
             dest.writeParcelable(mType, 0);
             dest.writeString(mDatasetId);
             dest.writeParcelable(mItemType, 0);
             dest.writeParcelable(mItemHandler, 0);
             dest.writeString(mBackImageUrl);
             dest.writeString(mFilter);
         }
 
--- a/mobile/android/base/home/PanelLayout.java
+++ b/mobile/android/base/home/PanelLayout.java
@@ -13,20 +13,22 @@ import org.mozilla.gecko.home.HomeConfig
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import java.lang.ref.SoftReference;
 import java.util.EnumSet;
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.WeakHashMap;
 
 /**
  * {@code PanelLayout} is the base class for custom layouts to be
  * used in {@code DynamicPanel}. It provides the basic framework
@@ -60,17 +62,17 @@ import java.util.WeakHashMap;
  * to create the views dynamically created based on {@code ViewConfig}. This
  * allows {@code PanelLayout} to auto-bind datasets with panel views.
  * {@code PanelLayout} subclasses are free to have any type of views to arrange
  * the panel views in different ways.
  */
 abstract class PanelLayout extends FrameLayout {
     private static final String LOGTAG = "GeckoPanelLayout";
 
-    protected final Map<View, ViewState> mViewStateMap;
+    protected final SparseArray<ViewState> mViewStates;
     private final PanelConfig mPanelConfig;
     private final DatasetHandler mDatasetHandler;
     private final OnUrlOpenListener mUrlOpenListener;
 
     /**
      * To be used by panel views to express that they are
      * backed by datasets.
      */
@@ -107,36 +109,43 @@ abstract class PanelLayout extends Frame
 
                 @Override
                 public Type[] newArray(final int size) {
                     return new Type[size];
                 }
             };
         }
 
+        private final int mViewIndex;
         private final Type mType;
         private final String mDatasetId;
         private final FilterDetail mFilterDetail;
 
         private DatasetRequest(Parcel in) {
+            this.mViewIndex = in.readInt();
             this.mType = (Type) in.readParcelable(getClass().getClassLoader());
             this.mDatasetId = in.readString();
             this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader());
         }
 
-        public DatasetRequest(String datasetId, FilterDetail filterDetail) {
-            this(Type.DATASET_LOAD, datasetId, filterDetail);
+        public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) {
+            this(index, Type.DATASET_LOAD, datasetId, filterDetail);
         }
 
-        public DatasetRequest(Type type, String datasetId, FilterDetail filterDetail) {
+        public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) {
+            this.mViewIndex = index;
             this.mType = type;
             this.mDatasetId = datasetId;
             this.mFilterDetail = filterDetail;
         }
 
+        public int getViewIndex() {
+            return mViewIndex;
+        }
+
         public Type getType() {
             return mType;
         }
 
         public String getDatasetId() {
             return mDatasetId;
         }
 
@@ -150,23 +159,28 @@ abstract class PanelLayout extends Frame
 
         @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mViewIndex);
             dest.writeParcelable(mType, 0);
             dest.writeString(mDatasetId);
             dest.writeParcelable(mFilterDetail, 0);
         }
 
         public String toString() {
-            return "{type: " + mType + " dataset: " + mDatasetId + ", filter: " + mFilterDetail + "}";
+            return "{ index: " + mViewIndex +
+                   ", type: " + mType +
+                   ", dataset: " + mDatasetId +
+                   ", filter: " + mFilterDetail +
+                   " }";
         }
 
         public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() {
             public DatasetRequest createFromParcel(Parcel in) {
                 return new DatasetRequest(in);
             }
 
             public DatasetRequest[] newArray(int size) {
@@ -182,163 +196,187 @@ abstract class PanelLayout extends Frame
     public interface DatasetHandler {
         /**
          * Requests a dataset to be fetched and auto-bound to the
          * panel views backed by it.
          */
         public void requestDataset(DatasetRequest request);
 
         /**
-         * Releases any resources associated with a previously loaded
-         * dataset. It will do nothing if the dataset with the given ID
-         * hasn't been loaded before.
+         * Releases any resources associated with a panel view. It will
+         * do nothing if the view with the given index been created
+         * before.
          */
-        public void resetDataset(String datasetId);
+        public void resetDataset(int viewIndex);
     }
 
     public interface PanelView {
         public void setOnItemOpenListener(OnItemOpenListener listener);
         public void setOnKeyListener(OnKeyListener listener);
     }
 
     public interface FilterManager {
         public FilterDetail getPreviousFilter();
         public boolean canGoBack();
         public void goBack();
     }
 
     public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, OnUrlOpenListener urlOpenListener) {
         super(context);
-        mViewStateMap = new WeakHashMap<View, ViewState>();
+        mViewStates = new SparseArray<ViewState>();
         mPanelConfig = panelConfig;
         mDatasetHandler = datasetHandler;
         mUrlOpenListener = urlOpenListener;
     }
 
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        final int count = mViewStates.size();
+        for (int i = 0; i < count; i++) {
+            final ViewState viewState = mViewStates.valueAt(i);
+
+            final View view = viewState.getView();
+            if (view != null) {
+                maybeSetDataset(view, null);
+            }
+        }
+        mViewStates.clear();
+    }
+
     /**
      * Delivers the dataset as a {@code Cursor} to be bound to the
-     * panel views backed by it. This is used by the {@code DatasetHandler}
+     * panel view backed by it. This is used by the {@code DatasetHandler}
      * in response to a dataset request.
      */
     public final void deliverDataset(DatasetRequest request, Cursor cursor) {
         Log.d(LOGTAG, "Delivering request: " + request);
-        updateViewsFromRequest(request, cursor);
+        final ViewState viewState = mViewStates.get(request.getViewIndex());
+        if (viewState == null) {
+            return;
+        }
+
+        switch (request.getType()) {
+            case FILTER_PUSH:
+                viewState.pushFilter(request.getFilterDetail());
+                break;
+            case FILTER_POP:
+                viewState.popFilter();
+                break;
+        }
+
+        final View view = viewState.getView();
+        if (view != null) {
+            maybeSetDataset(view, cursor);
+        }
     }
 
     /**
      * Releases any references to the given dataset from all
      * existing panel views.
      */
-    public final void releaseDataset(String datasetId) {
-        Log.d(LOGTAG, "Releasing dataset: " + datasetId);
-        releaseViewsWithDataset(datasetId);
+    public final void releaseDataset(int viewIndex) {
+        Log.d(LOGTAG, "Releasing dataset: " + viewIndex);
+        final ViewState viewState = mViewStates.get(viewIndex);
+        if (viewState == null) {
+            return;
+        }
+
+        final View view = viewState.getView();
+        if (view != null) {
+            maybeSetDataset(view, null);
+        }
     }
 
     /**
      * Requests a dataset to be loaded and bound to any existing
      * panel view backed by it.
      */
     protected final void requestDataset(DatasetRequest request) {
         Log.d(LOGTAG, "Requesting request: " + request);
+        if (mViewStates.get(request.getViewIndex()) == null) {
+            return;
+        }
+
         mDatasetHandler.requestDataset(request);
     }
 
     /**
-     * Releases any resources associated with a previously
-     * loaded dataset e.g. close any associated {@code Cursor}.
+     * Releases any resources associated with a panel view.
+     * e.g. close any associated {@code Cursor}.
      */
-    protected final void resetDataset(String datasetId) {
-        mDatasetHandler.resetDataset(datasetId);
+    protected final void resetDataset(int viewIndex) {
+        Log.d(LOGTAG, "Resetting view with index: " + viewIndex);
+        if (mViewStates.get(viewIndex) == null) {
+            return;
+        }
+
+        mDatasetHandler.resetDataset(viewIndex);
     }
 
     /**
      * Factory method to create instance of panels from a given
      * {@code ViewConfig}. All panel views defined in {@code PanelConfig}
      * should be created using this method so that {@PanelLayout} can
      * keep track of panel views and their associated datasets.
      */
     protected final View createPanelView(ViewConfig viewConfig) {
-        final View view;
-
         Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType());
 
-        switch(viewConfig.getType()) {
-            case LIST:
-                view = new PanelListView(getContext(), viewConfig);
-                break;
-
-            case GRID:
-                view = new PanelGridView(getContext(), viewConfig);
-                break;
-
-            default:
-                throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName());
+        ViewState viewState = mViewStates.get(viewConfig.getIndex());
+        if (viewState == null) {
+            viewState = new ViewState(viewConfig);
+            mViewStates.put(viewConfig.getIndex(), viewState);
         }
 
-        final ViewState state = new ViewState(viewConfig);
-        // TODO: Push initial filter here onto ViewState
-        mViewStateMap.put(view, state);
+        View view = viewState.getView();
+        if (view == null) {
+            switch(viewConfig.getType()) {
+                case LIST:
+                    view = new PanelListView(getContext(), viewConfig);
+                    break;
+
+                case GRID:
+                    view = new PanelGridView(getContext(), viewConfig);
+                    break;
 
-        PanelView panelView = (PanelView) view;
-        panelView.setOnItemOpenListener(new PanelOnItemOpenListener(state));
-        panelView.setOnKeyListener(new PanelKeyListener(state));
+                default:
+                    throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName());
+            }
 
-        if (view instanceof DatasetBacked) {
-            DatasetBacked datasetBacked = (DatasetBacked) view;
-            datasetBacked.setFilterManager(new PanelFilterManager(state));
+            PanelView panelView = (PanelView) view;
+            panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState));
+            panelView.setOnKeyListener(new PanelKeyListener(viewState));
+
+            if (view instanceof DatasetBacked) {
+                DatasetBacked datasetBacked = (DatasetBacked) view;
+                datasetBacked.setFilterManager(new PanelFilterManager(viewState));
+            }
+
+            viewState.setView(view);
         }
 
         return view;
     }
 
     /**
      * Dispose any dataset references associated with the
      * given view.
      */
     protected final void disposePanelView(View view) {
         Log.d(LOGTAG, "Disposing panel view");
-        if (mViewStateMap.containsKey(view)) {
-            // Release any Cursor references from the view
-            // if it's backed by a dataset.
-            maybeSetDataset(view, null);
-
-            // Remove the view entry from the map
-            mViewStateMap.remove(view);
-        }
-    }
-
-    private void updateViewsFromRequest(DatasetRequest request, Cursor cursor) {
-        for (Map.Entry<View, ViewState> entry : mViewStateMap.entrySet()) {
-            final ViewState detail = entry.getValue();
+        final int count = mViewStates.size();
+        for (int i = 0; i < count; i++) {
+            final ViewState viewState = mViewStates.valueAt(i);
 
-            // Update any views associated with the given dataset ID
-            if (TextUtils.equals(detail.getDatasetId(), request.getDatasetId())) {
-                switch (request.getType()) {
-                    case FILTER_PUSH:
-                        detail.pushFilter(request.getFilterDetail());
-                        break;
-                    case FILTER_POP:
-                        detail.popFilter();
-                        break;
-                }
-
-                final View view = entry.getKey();
-                maybeSetDataset(view, cursor);
-            }
-        }
-    }
-
-    private void releaseViewsWithDataset(String datasetId) {
-        for (Map.Entry<View, ViewState> entry : mViewStateMap.entrySet()) {
-            final ViewState detail = entry.getValue();
-
-            // Release the cursor on views associated with the given dataset ID
-            if (TextUtils.equals(detail.getDatasetId(), datasetId)) {
-                final View view = entry.getKey();
+            if (viewState.getView() == view) {
                 maybeSetDataset(view, null);
+                mViewStates.remove(viewState.getIndex());
+                break;
             }
         }
     }
 
     private void maybeSetDataset(View view, Cursor cursor) {
         if (view instanceof DatasetBacked) {
             final DatasetBacked dsb = (DatasetBacked) view;
             dsb.setDataset(cursor);
@@ -353,20 +391,34 @@ abstract class PanelLayout extends Frame
     public abstract void load();
 
     /**
      * Represents a 'live' instance of a panel view associated with
      * the {@code PanelLayout}. Is responsible for tracking the history stack of filters.
      */
     protected class ViewState {
         private final ViewConfig mViewConfig;
+        private SoftReference<View> mView;
         private LinkedList<FilterDetail> mFilterStack;
 
         public ViewState(ViewConfig viewConfig) {
             mViewConfig = viewConfig;
+            mView = new SoftReference<View>(null);
+        }
+
+        public int getIndex() {
+            return mViewConfig.getIndex();
+        }
+
+        public View getView() {
+            return mView.get();
+        }
+
+        public void setView(View view) {
+            mView = new SoftReference<View>(view);
         }
 
         public String getDatasetId() {
             return mViewConfig.getDatasetId();
         }
 
         public ItemHandler getItemHandler() {
             return mViewConfig.getItemHandler();
@@ -463,32 +515,41 @@ abstract class PanelLayout extends Frame
             }
         };
     }
 
     /**
      * Pushes filter to {@code ViewState}'s stack and makes request for new filter value.
      */
     private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) {
+        final int index = viewState.getIndex();
         final String datasetId = viewState.getDatasetId();
-        mDatasetHandler.requestDataset(
-            new DatasetRequest(DatasetRequest.Type.FILTER_PUSH, datasetId, filterDetail));
+
+        mDatasetHandler.requestDataset(new DatasetRequest(index,
+                                                          DatasetRequest.Type.FILTER_PUSH,
+                                                          datasetId,
+                                                          filterDetail));
     }
 
     /**
      * Pops filter from {@code ViewState}'s stack and makes request for previous filter value.
      *
      * @return whether the filter has changed
      */
     private boolean popFilterOnView(ViewState viewState) {
         if (viewState.canPopFilter()) {
-            final FilterDetail filterDetail = viewState.getPreviousFilter();
+            final int index = viewState.getIndex();
             final String datasetId = viewState.getDatasetId();
-            mDatasetHandler.requestDataset(
-                new DatasetRequest(DatasetRequest.Type.FILTER_POP, datasetId, filterDetail));
+            final FilterDetail filterDetail = viewState.getPreviousFilter();
+
+            mDatasetHandler.requestDataset(new DatasetRequest(index,
+                                                              DatasetRequest.Type.FILTER_POP,
+                                                              datasetId,
+                                                              filterDetail));
+
             return true;
         } else {
             return false;
         }
     }
 
     public interface OnItemOpenListener {
         public void onItemOpen(String url, String title);