1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/home/PanelLayout.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,744 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.home; 1.10 + 1.11 +import org.mozilla.gecko.R; 1.12 +import org.mozilla.gecko.db.BrowserContract.HomeItems; 1.13 +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; 1.14 +import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig; 1.15 +import org.mozilla.gecko.home.HomeConfig.ItemHandler; 1.16 +import org.mozilla.gecko.home.HomeConfig.PanelConfig; 1.17 +import org.mozilla.gecko.home.HomeConfig.ViewConfig; 1.18 +import org.mozilla.gecko.util.StringUtils; 1.19 + 1.20 +import android.content.Context; 1.21 +import android.database.Cursor; 1.22 +import android.os.Parcel; 1.23 +import android.os.Parcelable; 1.24 +import android.text.TextUtils; 1.25 +import android.util.Log; 1.26 +import android.util.SparseArray; 1.27 +import android.view.KeyEvent; 1.28 +import android.view.LayoutInflater; 1.29 +import android.view.View; 1.30 +import android.view.ViewGroup; 1.31 +import android.widget.FrameLayout; 1.32 +import android.widget.ImageView; 1.33 +import android.widget.LinearLayout; 1.34 +import android.widget.TextView; 1.35 + 1.36 +import java.lang.ref.SoftReference; 1.37 +import java.util.EnumSet; 1.38 +import java.util.LinkedList; 1.39 +import java.util.Map; 1.40 +import java.util.WeakHashMap; 1.41 + 1.42 +import com.squareup.picasso.Picasso; 1.43 + 1.44 +/** 1.45 + * {@code PanelLayout} is the base class for custom layouts to be 1.46 + * used in {@code DynamicPanel}. It provides the basic framework 1.47 + * that enables custom layouts to request and reset datasets and 1.48 + * create panel views. Furthermore, it automates most of the process 1.49 + * of binding panel views with their respective datasets. 1.50 + * 1.51 + * {@code PanelLayout} abstracts the implemention details of how 1.52 + * datasets are actually loaded through the {@DatasetHandler} interface. 1.53 + * {@code DatasetHandler} provides two operations: request and reset. 1.54 + * The results of the dataset requests done via the {@code DatasetHandler} 1.55 + * are delivered to the {@code PanelLayout} with the {@code deliverDataset()} 1.56 + * method. 1.57 + * 1.58 + * Subclasses of {@code PanelLayout} should simply use the utilities 1.59 + * provided by {@code PanelLayout}. Namely: 1.60 + * 1.61 + * {@code requestDataset()} - To fetch datasets and auto-bind them to 1.62 + * the existing panel views backed by them. 1.63 + * 1.64 + * {@code resetDataset()} - To release any resources associated with a 1.65 + * previously loaded dataset. 1.66 + * 1.67 + * {@code createPanelView()} - To create a panel view for a ViewConfig 1.68 + * associated with the panel. 1.69 + * 1.70 + * {@code disposePanelView()} - To dispose any dataset references associated 1.71 + * with the given view. 1.72 + * 1.73 + * {@code PanelLayout} subclasses should always use {@code createPanelView()} 1.74 + * to create the views dynamically created based on {@code ViewConfig}. This 1.75 + * allows {@code PanelLayout} to auto-bind datasets with panel views. 1.76 + * {@code PanelLayout} subclasses are free to have any type of views to arrange 1.77 + * the panel views in different ways. 1.78 + */ 1.79 +abstract class PanelLayout extends FrameLayout { 1.80 + private static final String LOGTAG = "GeckoPanelLayout"; 1.81 + 1.82 + protected final SparseArray<ViewState> mViewStates; 1.83 + private final PanelConfig mPanelConfig; 1.84 + private final DatasetHandler mDatasetHandler; 1.85 + private final OnUrlOpenListener mUrlOpenListener; 1.86 + private final ContextMenuRegistry mContextMenuRegistry; 1.87 + 1.88 + /** 1.89 + * To be used by panel views to express that they are 1.90 + * backed by datasets. 1.91 + */ 1.92 + public interface DatasetBacked { 1.93 + public void setDataset(Cursor cursor); 1.94 + public void setFilterManager(FilterManager manager); 1.95 + } 1.96 + 1.97 + /** 1.98 + * To be used by requests made to {@code DatasetHandler}s to couple dataset ID with current 1.99 + * filter for queries on the database. 1.100 + */ 1.101 + public static class DatasetRequest implements Parcelable { 1.102 + public enum Type implements Parcelable { 1.103 + DATASET_LOAD, 1.104 + FILTER_PUSH, 1.105 + FILTER_POP; 1.106 + 1.107 + @Override 1.108 + public int describeContents() { 1.109 + return 0; 1.110 + } 1.111 + 1.112 + @Override 1.113 + public void writeToParcel(Parcel dest, int flags) { 1.114 + dest.writeInt(ordinal()); 1.115 + } 1.116 + 1.117 + public static final Creator<Type> CREATOR = new Creator<Type>() { 1.118 + @Override 1.119 + public Type createFromParcel(final Parcel source) { 1.120 + return Type.values()[source.readInt()]; 1.121 + } 1.122 + 1.123 + @Override 1.124 + public Type[] newArray(final int size) { 1.125 + return new Type[size]; 1.126 + } 1.127 + }; 1.128 + } 1.129 + 1.130 + private final int mViewIndex; 1.131 + private final Type mType; 1.132 + private final String mDatasetId; 1.133 + private final FilterDetail mFilterDetail; 1.134 + 1.135 + private DatasetRequest(Parcel in) { 1.136 + this.mViewIndex = in.readInt(); 1.137 + this.mType = (Type) in.readParcelable(getClass().getClassLoader()); 1.138 + this.mDatasetId = in.readString(); 1.139 + this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader()); 1.140 + } 1.141 + 1.142 + public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) { 1.143 + this(index, Type.DATASET_LOAD, datasetId, filterDetail); 1.144 + } 1.145 + 1.146 + public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) { 1.147 + this.mViewIndex = index; 1.148 + this.mType = type; 1.149 + this.mDatasetId = datasetId; 1.150 + this.mFilterDetail = filterDetail; 1.151 + } 1.152 + 1.153 + public int getViewIndex() { 1.154 + return mViewIndex; 1.155 + } 1.156 + 1.157 + public Type getType() { 1.158 + return mType; 1.159 + } 1.160 + 1.161 + public String getDatasetId() { 1.162 + return mDatasetId; 1.163 + } 1.164 + 1.165 + public String getFilter() { 1.166 + return (mFilterDetail != null ? mFilterDetail.filter : null); 1.167 + } 1.168 + 1.169 + public FilterDetail getFilterDetail() { 1.170 + return mFilterDetail; 1.171 + } 1.172 + 1.173 + @Override 1.174 + public int describeContents() { 1.175 + return 0; 1.176 + } 1.177 + 1.178 + @Override 1.179 + public void writeToParcel(Parcel dest, int flags) { 1.180 + dest.writeInt(mViewIndex); 1.181 + dest.writeParcelable(mType, 0); 1.182 + dest.writeString(mDatasetId); 1.183 + dest.writeParcelable(mFilterDetail, 0); 1.184 + } 1.185 + 1.186 + public String toString() { 1.187 + return "{ index: " + mViewIndex + 1.188 + ", type: " + mType + 1.189 + ", dataset: " + mDatasetId + 1.190 + ", filter: " + mFilterDetail + 1.191 + " }"; 1.192 + } 1.193 + 1.194 + public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() { 1.195 + public DatasetRequest createFromParcel(Parcel in) { 1.196 + return new DatasetRequest(in); 1.197 + } 1.198 + 1.199 + public DatasetRequest[] newArray(int size) { 1.200 + return new DatasetRequest[size]; 1.201 + } 1.202 + }; 1.203 + } 1.204 + 1.205 + /** 1.206 + * Defines the contract with the component that is responsible 1.207 + * for handling datasets requests. 1.208 + */ 1.209 + public interface DatasetHandler { 1.210 + /** 1.211 + * Requests a dataset to be fetched and auto-bound to the 1.212 + * panel views backed by it. 1.213 + */ 1.214 + public void requestDataset(DatasetRequest request); 1.215 + 1.216 + /** 1.217 + * Releases any resources associated with a panel view. It will 1.218 + * do nothing if the view with the given index been created 1.219 + * before. 1.220 + */ 1.221 + public void resetDataset(int viewIndex); 1.222 + } 1.223 + 1.224 + public interface PanelView { 1.225 + public void setOnItemOpenListener(OnItemOpenListener listener); 1.226 + public void setOnKeyListener(OnKeyListener listener); 1.227 + public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory); 1.228 + } 1.229 + 1.230 + public interface FilterManager { 1.231 + public FilterDetail getPreviousFilter(); 1.232 + public boolean canGoBack(); 1.233 + public void goBack(); 1.234 + } 1.235 + 1.236 + public interface ContextMenuRegistry { 1.237 + public void register(View view); 1.238 + } 1.239 + 1.240 + public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, 1.241 + OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) { 1.242 + super(context); 1.243 + mViewStates = new SparseArray<ViewState>(); 1.244 + mPanelConfig = panelConfig; 1.245 + mDatasetHandler = datasetHandler; 1.246 + mUrlOpenListener = urlOpenListener; 1.247 + mContextMenuRegistry = contextMenuRegistry; 1.248 + } 1.249 + 1.250 + @Override 1.251 + public void onDetachedFromWindow() { 1.252 + super.onDetachedFromWindow(); 1.253 + 1.254 + final int count = mViewStates.size(); 1.255 + for (int i = 0; i < count; i++) { 1.256 + final ViewState viewState = mViewStates.valueAt(i); 1.257 + 1.258 + final View view = viewState.getView(); 1.259 + if (view != null) { 1.260 + maybeSetDataset(view, null); 1.261 + } 1.262 + } 1.263 + mViewStates.clear(); 1.264 + } 1.265 + 1.266 + /** 1.267 + * Delivers the dataset as a {@code Cursor} to be bound to the 1.268 + * panel view backed by it. This is used by the {@code DatasetHandler} 1.269 + * in response to a dataset request. 1.270 + */ 1.271 + public final void deliverDataset(DatasetRequest request, Cursor cursor) { 1.272 + Log.d(LOGTAG, "Delivering request: " + request); 1.273 + final ViewState viewState = mViewStates.get(request.getViewIndex()); 1.274 + if (viewState == null) { 1.275 + return; 1.276 + } 1.277 + 1.278 + switch (request.getType()) { 1.279 + case FILTER_PUSH: 1.280 + viewState.pushFilter(request.getFilterDetail()); 1.281 + break; 1.282 + case FILTER_POP: 1.283 + viewState.popFilter(); 1.284 + break; 1.285 + } 1.286 + 1.287 + final View activeView = viewState.getActiveView(); 1.288 + if (activeView == null) { 1.289 + throw new IllegalStateException("No active view for view state: " + viewState.getIndex()); 1.290 + } 1.291 + 1.292 + final ViewConfig viewConfig = viewState.getViewConfig(); 1.293 + 1.294 + final View newView; 1.295 + if (cursor == null || cursor.getCount() == 0) { 1.296 + newView = createEmptyView(viewConfig); 1.297 + maybeSetDataset(activeView, null); 1.298 + } else { 1.299 + newView = createPanelView(viewConfig); 1.300 + maybeSetDataset(newView, cursor); 1.301 + } 1.302 + 1.303 + if (activeView != newView) { 1.304 + replacePanelView(activeView, newView); 1.305 + } 1.306 + } 1.307 + 1.308 + /** 1.309 + * Releases any references to the given dataset from all 1.310 + * existing panel views. 1.311 + */ 1.312 + public final void releaseDataset(int viewIndex) { 1.313 + Log.d(LOGTAG, "Releasing dataset: " + viewIndex); 1.314 + final ViewState viewState = mViewStates.get(viewIndex); 1.315 + if (viewState == null) { 1.316 + return; 1.317 + } 1.318 + 1.319 + final View view = viewState.getView(); 1.320 + if (view != null) { 1.321 + maybeSetDataset(view, null); 1.322 + } 1.323 + } 1.324 + 1.325 + /** 1.326 + * Requests a dataset to be loaded and bound to any existing 1.327 + * panel view backed by it. 1.328 + */ 1.329 + protected final void requestDataset(DatasetRequest request) { 1.330 + Log.d(LOGTAG, "Requesting request: " + request); 1.331 + if (mViewStates.get(request.getViewIndex()) == null) { 1.332 + return; 1.333 + } 1.334 + 1.335 + mDatasetHandler.requestDataset(request); 1.336 + } 1.337 + 1.338 + /** 1.339 + * Releases any resources associated with a panel view. 1.340 + * e.g. close any associated {@code Cursor}. 1.341 + */ 1.342 + protected final void resetDataset(int viewIndex) { 1.343 + Log.d(LOGTAG, "Resetting view with index: " + viewIndex); 1.344 + if (mViewStates.get(viewIndex) == null) { 1.345 + return; 1.346 + } 1.347 + 1.348 + mDatasetHandler.resetDataset(viewIndex); 1.349 + } 1.350 + 1.351 + /** 1.352 + * Factory method to create instance of panels from a given 1.353 + * {@code ViewConfig}. All panel views defined in {@code PanelConfig} 1.354 + * should be created using this method so that {@PanelLayout} can 1.355 + * keep track of panel views and their associated datasets. 1.356 + */ 1.357 + protected final View createPanelView(ViewConfig viewConfig) { 1.358 + Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType()); 1.359 + 1.360 + ViewState viewState = mViewStates.get(viewConfig.getIndex()); 1.361 + if (viewState == null) { 1.362 + viewState = new ViewState(viewConfig); 1.363 + mViewStates.put(viewConfig.getIndex(), viewState); 1.364 + } 1.365 + 1.366 + View view = viewState.getView(); 1.367 + if (view == null) { 1.368 + switch(viewConfig.getType()) { 1.369 + case LIST: 1.370 + view = new PanelListView(getContext(), viewConfig); 1.371 + break; 1.372 + 1.373 + case GRID: 1.374 + view = new PanelGridView(getContext(), viewConfig); 1.375 + break; 1.376 + 1.377 + default: 1.378 + throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName()); 1.379 + } 1.380 + 1.381 + PanelView panelView = (PanelView) view; 1.382 + panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState)); 1.383 + panelView.setOnKeyListener(new PanelKeyListener(viewState)); 1.384 + panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() { 1.385 + @Override 1.386 + public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { 1.387 + final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); 1.388 + info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL)); 1.389 + info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE)); 1.390 + return info; 1.391 + } 1.392 + }); 1.393 + 1.394 + mContextMenuRegistry.register(view); 1.395 + 1.396 + if (view instanceof DatasetBacked) { 1.397 + DatasetBacked datasetBacked = (DatasetBacked) view; 1.398 + datasetBacked.setFilterManager(new PanelFilterManager(viewState)); 1.399 + 1.400 + // XXX: Disabled because of bug 1010986 1.401 + // if (viewConfig.isRefreshEnabled()) { 1.402 + // view = new PanelRefreshLayout(getContext(), view, 1.403 + // mPanelConfig.getId(), viewConfig.getIndex()); 1.404 + // } 1.405 + } 1.406 + 1.407 + viewState.setView(view); 1.408 + } 1.409 + 1.410 + return view; 1.411 + } 1.412 + 1.413 + /** 1.414 + * Dispose any dataset references associated with the 1.415 + * given view. 1.416 + */ 1.417 + protected final void disposePanelView(View view) { 1.418 + Log.d(LOGTAG, "Disposing panel view"); 1.419 + final int count = mViewStates.size(); 1.420 + for (int i = 0; i < count; i++) { 1.421 + final ViewState viewState = mViewStates.valueAt(i); 1.422 + 1.423 + if (viewState.getView() == view) { 1.424 + maybeSetDataset(view, null); 1.425 + mViewStates.remove(viewState.getIndex()); 1.426 + break; 1.427 + } 1.428 + } 1.429 + } 1.430 + 1.431 + private void maybeSetDataset(View view, Cursor cursor) { 1.432 + if (view instanceof DatasetBacked) { 1.433 + final DatasetBacked dsb = (DatasetBacked) view; 1.434 + dsb.setDataset(cursor); 1.435 + } 1.436 + } 1.437 + 1.438 + private View createEmptyView(ViewConfig viewConfig) { 1.439 + Log.d(LOGTAG, "Creating empty view: " + viewConfig.getType()); 1.440 + 1.441 + ViewState viewState = mViewStates.get(viewConfig.getIndex()); 1.442 + if (viewState == null) { 1.443 + throw new IllegalStateException("No view state found for view index: " + viewConfig.getIndex()); 1.444 + } 1.445 + 1.446 + View view = viewState.getEmptyView(); 1.447 + if (view == null) { 1.448 + view = LayoutInflater.from(getContext()).inflate(R.layout.home_empty_panel, null); 1.449 + 1.450 + final EmptyViewConfig emptyViewConfig = viewConfig.getEmptyViewConfig(); 1.451 + 1.452 + // XXX: Refactor this into a custom view (bug 985134) 1.453 + final String text = (emptyViewConfig == null) ? null : emptyViewConfig.getText(); 1.454 + final TextView textView = (TextView) view.findViewById(R.id.home_empty_text); 1.455 + if (TextUtils.isEmpty(text)) { 1.456 + textView.setText(R.string.home_default_empty); 1.457 + } else { 1.458 + textView.setText(text); 1.459 + } 1.460 + 1.461 + final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl(); 1.462 + final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image); 1.463 + 1.464 + if (TextUtils.isEmpty(imageUrl)) { 1.465 + imageView.setImageResource(R.drawable.icon_home_empty_firefox); 1.466 + } else { 1.467 + Picasso.with(getContext()) 1.468 + .load(imageUrl) 1.469 + .error(R.drawable.icon_home_empty_firefox) 1.470 + .into(imageView); 1.471 + } 1.472 + 1.473 + viewState.setEmptyView(view); 1.474 + } 1.475 + 1.476 + return view; 1.477 + } 1.478 + 1.479 + private void replacePanelView(View currentView, View newView) { 1.480 + final ViewGroup parent = (ViewGroup) currentView.getParent(); 1.481 + parent.addView(newView, parent.indexOfChild(currentView), currentView.getLayoutParams()); 1.482 + parent.removeView(currentView); 1.483 + } 1.484 + 1.485 + /** 1.486 + * Must be implemented by {@code PanelLayout} subclasses to define 1.487 + * what happens then the layout is first loaded. Should set initial 1.488 + * UI state and request any necessary datasets. 1.489 + */ 1.490 + public abstract void load(); 1.491 + 1.492 + /** 1.493 + * Represents a 'live' instance of a panel view associated with 1.494 + * the {@code PanelLayout}. Is responsible for tracking the history stack of filters. 1.495 + */ 1.496 + protected class ViewState { 1.497 + private final ViewConfig mViewConfig; 1.498 + private SoftReference<View> mView; 1.499 + private SoftReference<View> mEmptyView; 1.500 + private LinkedList<FilterDetail> mFilterStack; 1.501 + 1.502 + public ViewState(ViewConfig viewConfig) { 1.503 + mViewConfig = viewConfig; 1.504 + mView = new SoftReference<View>(null); 1.505 + mEmptyView = new SoftReference<View>(null); 1.506 + } 1.507 + 1.508 + public ViewConfig getViewConfig() { 1.509 + return mViewConfig; 1.510 + } 1.511 + 1.512 + public int getIndex() { 1.513 + return mViewConfig.getIndex(); 1.514 + } 1.515 + 1.516 + public View getView() { 1.517 + return mView.get(); 1.518 + } 1.519 + 1.520 + public void setView(View view) { 1.521 + mView = new SoftReference<View>(view); 1.522 + } 1.523 + 1.524 + public View getEmptyView() { 1.525 + return mEmptyView.get(); 1.526 + } 1.527 + 1.528 + public void setEmptyView(View view) { 1.529 + mEmptyView = new SoftReference<View>(view); 1.530 + } 1.531 + 1.532 + public View getActiveView() { 1.533 + final View view = getView(); 1.534 + if (view != null && view.getParent() != null) { 1.535 + return view; 1.536 + } 1.537 + 1.538 + final View emptyView = getEmptyView(); 1.539 + if (emptyView != null && emptyView.getParent() != null) { 1.540 + return emptyView; 1.541 + } 1.542 + 1.543 + return null; 1.544 + } 1.545 + 1.546 + public String getDatasetId() { 1.547 + return mViewConfig.getDatasetId(); 1.548 + } 1.549 + 1.550 + public ItemHandler getItemHandler() { 1.551 + return mViewConfig.getItemHandler(); 1.552 + } 1.553 + 1.554 + /** 1.555 + * Get the current filter that this view is displaying, or null if none. 1.556 + */ 1.557 + public FilterDetail getCurrentFilter() { 1.558 + if (mFilterStack == null) { 1.559 + return null; 1.560 + } else { 1.561 + return mFilterStack.peek(); 1.562 + } 1.563 + } 1.564 + 1.565 + /** 1.566 + * Get the previous filter that this view was displaying, or null if none. 1.567 + */ 1.568 + public FilterDetail getPreviousFilter() { 1.569 + if (!canPopFilter()) { 1.570 + return null; 1.571 + } 1.572 + 1.573 + return mFilterStack.get(1); 1.574 + } 1.575 + 1.576 + /** 1.577 + * Adds a filter to the history stack for this view. 1.578 + */ 1.579 + public void pushFilter(FilterDetail filter) { 1.580 + if (mFilterStack == null) { 1.581 + mFilterStack = new LinkedList<FilterDetail>(); 1.582 + 1.583 + // Initialize with the initial filter. 1.584 + mFilterStack.push(new FilterDetail(mViewConfig.getFilter(), 1.585 + mPanelConfig.getTitle())); 1.586 + } 1.587 + 1.588 + mFilterStack.push(filter); 1.589 + } 1.590 + 1.591 + /** 1.592 + * Remove the most recent filter from the stack. 1.593 + * 1.594 + * @return whether the filter was popped 1.595 + */ 1.596 + public boolean popFilter() { 1.597 + if (!canPopFilter()) { 1.598 + return false; 1.599 + } 1.600 + 1.601 + mFilterStack.pop(); 1.602 + return true; 1.603 + } 1.604 + 1.605 + public boolean canPopFilter() { 1.606 + return (mFilterStack != null && mFilterStack.size() > 1); 1.607 + } 1.608 + } 1.609 + 1.610 + static class FilterDetail implements Parcelable { 1.611 + final String filter; 1.612 + final String title; 1.613 + 1.614 + private FilterDetail(Parcel in) { 1.615 + this.filter = in.readString(); 1.616 + this.title = in.readString(); 1.617 + } 1.618 + 1.619 + public FilterDetail(String filter, String title) { 1.620 + this.filter = filter; 1.621 + this.title = title; 1.622 + } 1.623 + 1.624 + @Override 1.625 + public int describeContents() { 1.626 + return 0; 1.627 + } 1.628 + 1.629 + @Override 1.630 + public void writeToParcel(Parcel dest, int flags) { 1.631 + dest.writeString(filter); 1.632 + dest.writeString(title); 1.633 + } 1.634 + 1.635 + public static final Creator<FilterDetail> CREATOR = new Creator<FilterDetail>() { 1.636 + public FilterDetail createFromParcel(Parcel in) { 1.637 + return new FilterDetail(in); 1.638 + } 1.639 + 1.640 + public FilterDetail[] newArray(int size) { 1.641 + return new FilterDetail[size]; 1.642 + } 1.643 + }; 1.644 + } 1.645 + 1.646 + /** 1.647 + * Pushes filter to {@code ViewState}'s stack and makes request for new filter value. 1.648 + */ 1.649 + private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) { 1.650 + final int index = viewState.getIndex(); 1.651 + final String datasetId = viewState.getDatasetId(); 1.652 + 1.653 + mDatasetHandler.requestDataset(new DatasetRequest(index, 1.654 + DatasetRequest.Type.FILTER_PUSH, 1.655 + datasetId, 1.656 + filterDetail)); 1.657 + } 1.658 + 1.659 + /** 1.660 + * Pops filter from {@code ViewState}'s stack and makes request for previous filter value. 1.661 + * 1.662 + * @return whether the filter has changed 1.663 + */ 1.664 + private boolean popFilterOnView(ViewState viewState) { 1.665 + if (viewState.canPopFilter()) { 1.666 + final int index = viewState.getIndex(); 1.667 + final String datasetId = viewState.getDatasetId(); 1.668 + final FilterDetail filterDetail = viewState.getPreviousFilter(); 1.669 + 1.670 + mDatasetHandler.requestDataset(new DatasetRequest(index, 1.671 + DatasetRequest.Type.FILTER_POP, 1.672 + datasetId, 1.673 + filterDetail)); 1.674 + 1.675 + return true; 1.676 + } else { 1.677 + return false; 1.678 + } 1.679 + } 1.680 + 1.681 + public interface OnItemOpenListener { 1.682 + public void onItemOpen(String url, String title); 1.683 + } 1.684 + 1.685 + private class PanelOnItemOpenListener implements OnItemOpenListener { 1.686 + private ViewState mViewState; 1.687 + 1.688 + public PanelOnItemOpenListener(ViewState viewState) { 1.689 + mViewState = viewState; 1.690 + } 1.691 + 1.692 + @Override 1.693 + public void onItemOpen(String url, String title) { 1.694 + if (StringUtils.isFilterUrl(url)) { 1.695 + FilterDetail filterDetail = new FilterDetail(StringUtils.getFilterFromUrl(url), title); 1.696 + pushFilterOnView(mViewState, filterDetail); 1.697 + } else { 1.698 + EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class); 1.699 + if (mViewState.getItemHandler() == ItemHandler.INTENT) { 1.700 + flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT); 1.701 + } 1.702 + 1.703 + mUrlOpenListener.onUrlOpen(url, flags); 1.704 + } 1.705 + } 1.706 + } 1.707 + 1.708 + private class PanelKeyListener implements View.OnKeyListener { 1.709 + private ViewState mViewState; 1.710 + 1.711 + public PanelKeyListener(ViewState viewState) { 1.712 + mViewState = viewState; 1.713 + } 1.714 + 1.715 + @Override 1.716 + public boolean onKey(View v, int keyCode, KeyEvent event) { 1.717 + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { 1.718 + return popFilterOnView(mViewState); 1.719 + } 1.720 + 1.721 + return false; 1.722 + } 1.723 + } 1.724 + 1.725 + private class PanelFilterManager implements FilterManager { 1.726 + private final ViewState mViewState; 1.727 + 1.728 + public PanelFilterManager(ViewState viewState) { 1.729 + mViewState = viewState; 1.730 + } 1.731 + 1.732 + @Override 1.733 + public FilterDetail getPreviousFilter() { 1.734 + return mViewState.getPreviousFilter(); 1.735 + } 1.736 + 1.737 + @Override 1.738 + public boolean canGoBack() { 1.739 + return mViewState.canPopFilter(); 1.740 + } 1.741 + 1.742 + @Override 1.743 + public void goBack() { 1.744 + popFilterOnView(mViewState); 1.745 + } 1.746 + } 1.747 +}