Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.home; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.R; |
michael@0 | 9 | import org.mozilla.gecko.db.BrowserContract.HomeItems; |
michael@0 | 10 | import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; |
michael@0 | 11 | import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig; |
michael@0 | 12 | import org.mozilla.gecko.home.HomeConfig.ItemHandler; |
michael@0 | 13 | import org.mozilla.gecko.home.HomeConfig.PanelConfig; |
michael@0 | 14 | import org.mozilla.gecko.home.HomeConfig.ViewConfig; |
michael@0 | 15 | import org.mozilla.gecko.util.StringUtils; |
michael@0 | 16 | |
michael@0 | 17 | import android.content.Context; |
michael@0 | 18 | import android.database.Cursor; |
michael@0 | 19 | import android.os.Parcel; |
michael@0 | 20 | import android.os.Parcelable; |
michael@0 | 21 | import android.text.TextUtils; |
michael@0 | 22 | import android.util.Log; |
michael@0 | 23 | import android.util.SparseArray; |
michael@0 | 24 | import android.view.KeyEvent; |
michael@0 | 25 | import android.view.LayoutInflater; |
michael@0 | 26 | import android.view.View; |
michael@0 | 27 | import android.view.ViewGroup; |
michael@0 | 28 | import android.widget.FrameLayout; |
michael@0 | 29 | import android.widget.ImageView; |
michael@0 | 30 | import android.widget.LinearLayout; |
michael@0 | 31 | import android.widget.TextView; |
michael@0 | 32 | |
michael@0 | 33 | import java.lang.ref.SoftReference; |
michael@0 | 34 | import java.util.EnumSet; |
michael@0 | 35 | import java.util.LinkedList; |
michael@0 | 36 | import java.util.Map; |
michael@0 | 37 | import java.util.WeakHashMap; |
michael@0 | 38 | |
michael@0 | 39 | import com.squareup.picasso.Picasso; |
michael@0 | 40 | |
michael@0 | 41 | /** |
michael@0 | 42 | * {@code PanelLayout} is the base class for custom layouts to be |
michael@0 | 43 | * used in {@code DynamicPanel}. It provides the basic framework |
michael@0 | 44 | * that enables custom layouts to request and reset datasets and |
michael@0 | 45 | * create panel views. Furthermore, it automates most of the process |
michael@0 | 46 | * of binding panel views with their respective datasets. |
michael@0 | 47 | * |
michael@0 | 48 | * {@code PanelLayout} abstracts the implemention details of how |
michael@0 | 49 | * datasets are actually loaded through the {@DatasetHandler} interface. |
michael@0 | 50 | * {@code DatasetHandler} provides two operations: request and reset. |
michael@0 | 51 | * The results of the dataset requests done via the {@code DatasetHandler} |
michael@0 | 52 | * are delivered to the {@code PanelLayout} with the {@code deliverDataset()} |
michael@0 | 53 | * method. |
michael@0 | 54 | * |
michael@0 | 55 | * Subclasses of {@code PanelLayout} should simply use the utilities |
michael@0 | 56 | * provided by {@code PanelLayout}. Namely: |
michael@0 | 57 | * |
michael@0 | 58 | * {@code requestDataset()} - To fetch datasets and auto-bind them to |
michael@0 | 59 | * the existing panel views backed by them. |
michael@0 | 60 | * |
michael@0 | 61 | * {@code resetDataset()} - To release any resources associated with a |
michael@0 | 62 | * previously loaded dataset. |
michael@0 | 63 | * |
michael@0 | 64 | * {@code createPanelView()} - To create a panel view for a ViewConfig |
michael@0 | 65 | * associated with the panel. |
michael@0 | 66 | * |
michael@0 | 67 | * {@code disposePanelView()} - To dispose any dataset references associated |
michael@0 | 68 | * with the given view. |
michael@0 | 69 | * |
michael@0 | 70 | * {@code PanelLayout} subclasses should always use {@code createPanelView()} |
michael@0 | 71 | * to create the views dynamically created based on {@code ViewConfig}. This |
michael@0 | 72 | * allows {@code PanelLayout} to auto-bind datasets with panel views. |
michael@0 | 73 | * {@code PanelLayout} subclasses are free to have any type of views to arrange |
michael@0 | 74 | * the panel views in different ways. |
michael@0 | 75 | */ |
michael@0 | 76 | abstract class PanelLayout extends FrameLayout { |
michael@0 | 77 | private static final String LOGTAG = "GeckoPanelLayout"; |
michael@0 | 78 | |
michael@0 | 79 | protected final SparseArray<ViewState> mViewStates; |
michael@0 | 80 | private final PanelConfig mPanelConfig; |
michael@0 | 81 | private final DatasetHandler mDatasetHandler; |
michael@0 | 82 | private final OnUrlOpenListener mUrlOpenListener; |
michael@0 | 83 | private final ContextMenuRegistry mContextMenuRegistry; |
michael@0 | 84 | |
michael@0 | 85 | /** |
michael@0 | 86 | * To be used by panel views to express that they are |
michael@0 | 87 | * backed by datasets. |
michael@0 | 88 | */ |
michael@0 | 89 | public interface DatasetBacked { |
michael@0 | 90 | public void setDataset(Cursor cursor); |
michael@0 | 91 | public void setFilterManager(FilterManager manager); |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | /** |
michael@0 | 95 | * To be used by requests made to {@code DatasetHandler}s to couple dataset ID with current |
michael@0 | 96 | * filter for queries on the database. |
michael@0 | 97 | */ |
michael@0 | 98 | public static class DatasetRequest implements Parcelable { |
michael@0 | 99 | public enum Type implements Parcelable { |
michael@0 | 100 | DATASET_LOAD, |
michael@0 | 101 | FILTER_PUSH, |
michael@0 | 102 | FILTER_POP; |
michael@0 | 103 | |
michael@0 | 104 | @Override |
michael@0 | 105 | public int describeContents() { |
michael@0 | 106 | return 0; |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | @Override |
michael@0 | 110 | public void writeToParcel(Parcel dest, int flags) { |
michael@0 | 111 | dest.writeInt(ordinal()); |
michael@0 | 112 | } |
michael@0 | 113 | |
michael@0 | 114 | public static final Creator<Type> CREATOR = new Creator<Type>() { |
michael@0 | 115 | @Override |
michael@0 | 116 | public Type createFromParcel(final Parcel source) { |
michael@0 | 117 | return Type.values()[source.readInt()]; |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | @Override |
michael@0 | 121 | public Type[] newArray(final int size) { |
michael@0 | 122 | return new Type[size]; |
michael@0 | 123 | } |
michael@0 | 124 | }; |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | private final int mViewIndex; |
michael@0 | 128 | private final Type mType; |
michael@0 | 129 | private final String mDatasetId; |
michael@0 | 130 | private final FilterDetail mFilterDetail; |
michael@0 | 131 | |
michael@0 | 132 | private DatasetRequest(Parcel in) { |
michael@0 | 133 | this.mViewIndex = in.readInt(); |
michael@0 | 134 | this.mType = (Type) in.readParcelable(getClass().getClassLoader()); |
michael@0 | 135 | this.mDatasetId = in.readString(); |
michael@0 | 136 | this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader()); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) { |
michael@0 | 140 | this(index, Type.DATASET_LOAD, datasetId, filterDetail); |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) { |
michael@0 | 144 | this.mViewIndex = index; |
michael@0 | 145 | this.mType = type; |
michael@0 | 146 | this.mDatasetId = datasetId; |
michael@0 | 147 | this.mFilterDetail = filterDetail; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | public int getViewIndex() { |
michael@0 | 151 | return mViewIndex; |
michael@0 | 152 | } |
michael@0 | 153 | |
michael@0 | 154 | public Type getType() { |
michael@0 | 155 | return mType; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | public String getDatasetId() { |
michael@0 | 159 | return mDatasetId; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | public String getFilter() { |
michael@0 | 163 | return (mFilterDetail != null ? mFilterDetail.filter : null); |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | public FilterDetail getFilterDetail() { |
michael@0 | 167 | return mFilterDetail; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | @Override |
michael@0 | 171 | public int describeContents() { |
michael@0 | 172 | return 0; |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | @Override |
michael@0 | 176 | public void writeToParcel(Parcel dest, int flags) { |
michael@0 | 177 | dest.writeInt(mViewIndex); |
michael@0 | 178 | dest.writeParcelable(mType, 0); |
michael@0 | 179 | dest.writeString(mDatasetId); |
michael@0 | 180 | dest.writeParcelable(mFilterDetail, 0); |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | public String toString() { |
michael@0 | 184 | return "{ index: " + mViewIndex + |
michael@0 | 185 | ", type: " + mType + |
michael@0 | 186 | ", dataset: " + mDatasetId + |
michael@0 | 187 | ", filter: " + mFilterDetail + |
michael@0 | 188 | " }"; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() { |
michael@0 | 192 | public DatasetRequest createFromParcel(Parcel in) { |
michael@0 | 193 | return new DatasetRequest(in); |
michael@0 | 194 | } |
michael@0 | 195 | |
michael@0 | 196 | public DatasetRequest[] newArray(int size) { |
michael@0 | 197 | return new DatasetRequest[size]; |
michael@0 | 198 | } |
michael@0 | 199 | }; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | /** |
michael@0 | 203 | * Defines the contract with the component that is responsible |
michael@0 | 204 | * for handling datasets requests. |
michael@0 | 205 | */ |
michael@0 | 206 | public interface DatasetHandler { |
michael@0 | 207 | /** |
michael@0 | 208 | * Requests a dataset to be fetched and auto-bound to the |
michael@0 | 209 | * panel views backed by it. |
michael@0 | 210 | */ |
michael@0 | 211 | public void requestDataset(DatasetRequest request); |
michael@0 | 212 | |
michael@0 | 213 | /** |
michael@0 | 214 | * Releases any resources associated with a panel view. It will |
michael@0 | 215 | * do nothing if the view with the given index been created |
michael@0 | 216 | * before. |
michael@0 | 217 | */ |
michael@0 | 218 | public void resetDataset(int viewIndex); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | public interface PanelView { |
michael@0 | 222 | public void setOnItemOpenListener(OnItemOpenListener listener); |
michael@0 | 223 | public void setOnKeyListener(OnKeyListener listener); |
michael@0 | 224 | public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory); |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | public interface FilterManager { |
michael@0 | 228 | public FilterDetail getPreviousFilter(); |
michael@0 | 229 | public boolean canGoBack(); |
michael@0 | 230 | public void goBack(); |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | public interface ContextMenuRegistry { |
michael@0 | 234 | public void register(View view); |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, |
michael@0 | 238 | OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) { |
michael@0 | 239 | super(context); |
michael@0 | 240 | mViewStates = new SparseArray<ViewState>(); |
michael@0 | 241 | mPanelConfig = panelConfig; |
michael@0 | 242 | mDatasetHandler = datasetHandler; |
michael@0 | 243 | mUrlOpenListener = urlOpenListener; |
michael@0 | 244 | mContextMenuRegistry = contextMenuRegistry; |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | @Override |
michael@0 | 248 | public void onDetachedFromWindow() { |
michael@0 | 249 | super.onDetachedFromWindow(); |
michael@0 | 250 | |
michael@0 | 251 | final int count = mViewStates.size(); |
michael@0 | 252 | for (int i = 0; i < count; i++) { |
michael@0 | 253 | final ViewState viewState = mViewStates.valueAt(i); |
michael@0 | 254 | |
michael@0 | 255 | final View view = viewState.getView(); |
michael@0 | 256 | if (view != null) { |
michael@0 | 257 | maybeSetDataset(view, null); |
michael@0 | 258 | } |
michael@0 | 259 | } |
michael@0 | 260 | mViewStates.clear(); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | /** |
michael@0 | 264 | * Delivers the dataset as a {@code Cursor} to be bound to the |
michael@0 | 265 | * panel view backed by it. This is used by the {@code DatasetHandler} |
michael@0 | 266 | * in response to a dataset request. |
michael@0 | 267 | */ |
michael@0 | 268 | public final void deliverDataset(DatasetRequest request, Cursor cursor) { |
michael@0 | 269 | Log.d(LOGTAG, "Delivering request: " + request); |
michael@0 | 270 | final ViewState viewState = mViewStates.get(request.getViewIndex()); |
michael@0 | 271 | if (viewState == null) { |
michael@0 | 272 | return; |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | switch (request.getType()) { |
michael@0 | 276 | case FILTER_PUSH: |
michael@0 | 277 | viewState.pushFilter(request.getFilterDetail()); |
michael@0 | 278 | break; |
michael@0 | 279 | case FILTER_POP: |
michael@0 | 280 | viewState.popFilter(); |
michael@0 | 281 | break; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | final View activeView = viewState.getActiveView(); |
michael@0 | 285 | if (activeView == null) { |
michael@0 | 286 | throw new IllegalStateException("No active view for view state: " + viewState.getIndex()); |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | final ViewConfig viewConfig = viewState.getViewConfig(); |
michael@0 | 290 | |
michael@0 | 291 | final View newView; |
michael@0 | 292 | if (cursor == null || cursor.getCount() == 0) { |
michael@0 | 293 | newView = createEmptyView(viewConfig); |
michael@0 | 294 | maybeSetDataset(activeView, null); |
michael@0 | 295 | } else { |
michael@0 | 296 | newView = createPanelView(viewConfig); |
michael@0 | 297 | maybeSetDataset(newView, cursor); |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | if (activeView != newView) { |
michael@0 | 301 | replacePanelView(activeView, newView); |
michael@0 | 302 | } |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | /** |
michael@0 | 306 | * Releases any references to the given dataset from all |
michael@0 | 307 | * existing panel views. |
michael@0 | 308 | */ |
michael@0 | 309 | public final void releaseDataset(int viewIndex) { |
michael@0 | 310 | Log.d(LOGTAG, "Releasing dataset: " + viewIndex); |
michael@0 | 311 | final ViewState viewState = mViewStates.get(viewIndex); |
michael@0 | 312 | if (viewState == null) { |
michael@0 | 313 | return; |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | final View view = viewState.getView(); |
michael@0 | 317 | if (view != null) { |
michael@0 | 318 | maybeSetDataset(view, null); |
michael@0 | 319 | } |
michael@0 | 320 | } |
michael@0 | 321 | |
michael@0 | 322 | /** |
michael@0 | 323 | * Requests a dataset to be loaded and bound to any existing |
michael@0 | 324 | * panel view backed by it. |
michael@0 | 325 | */ |
michael@0 | 326 | protected final void requestDataset(DatasetRequest request) { |
michael@0 | 327 | Log.d(LOGTAG, "Requesting request: " + request); |
michael@0 | 328 | if (mViewStates.get(request.getViewIndex()) == null) { |
michael@0 | 329 | return; |
michael@0 | 330 | } |
michael@0 | 331 | |
michael@0 | 332 | mDatasetHandler.requestDataset(request); |
michael@0 | 333 | } |
michael@0 | 334 | |
michael@0 | 335 | /** |
michael@0 | 336 | * Releases any resources associated with a panel view. |
michael@0 | 337 | * e.g. close any associated {@code Cursor}. |
michael@0 | 338 | */ |
michael@0 | 339 | protected final void resetDataset(int viewIndex) { |
michael@0 | 340 | Log.d(LOGTAG, "Resetting view with index: " + viewIndex); |
michael@0 | 341 | if (mViewStates.get(viewIndex) == null) { |
michael@0 | 342 | return; |
michael@0 | 343 | } |
michael@0 | 344 | |
michael@0 | 345 | mDatasetHandler.resetDataset(viewIndex); |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | /** |
michael@0 | 349 | * Factory method to create instance of panels from a given |
michael@0 | 350 | * {@code ViewConfig}. All panel views defined in {@code PanelConfig} |
michael@0 | 351 | * should be created using this method so that {@PanelLayout} can |
michael@0 | 352 | * keep track of panel views and their associated datasets. |
michael@0 | 353 | */ |
michael@0 | 354 | protected final View createPanelView(ViewConfig viewConfig) { |
michael@0 | 355 | Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType()); |
michael@0 | 356 | |
michael@0 | 357 | ViewState viewState = mViewStates.get(viewConfig.getIndex()); |
michael@0 | 358 | if (viewState == null) { |
michael@0 | 359 | viewState = new ViewState(viewConfig); |
michael@0 | 360 | mViewStates.put(viewConfig.getIndex(), viewState); |
michael@0 | 361 | } |
michael@0 | 362 | |
michael@0 | 363 | View view = viewState.getView(); |
michael@0 | 364 | if (view == null) { |
michael@0 | 365 | switch(viewConfig.getType()) { |
michael@0 | 366 | case LIST: |
michael@0 | 367 | view = new PanelListView(getContext(), viewConfig); |
michael@0 | 368 | break; |
michael@0 | 369 | |
michael@0 | 370 | case GRID: |
michael@0 | 371 | view = new PanelGridView(getContext(), viewConfig); |
michael@0 | 372 | break; |
michael@0 | 373 | |
michael@0 | 374 | default: |
michael@0 | 375 | throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName()); |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | PanelView panelView = (PanelView) view; |
michael@0 | 379 | panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState)); |
michael@0 | 380 | panelView.setOnKeyListener(new PanelKeyListener(viewState)); |
michael@0 | 381 | panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() { |
michael@0 | 382 | @Override |
michael@0 | 383 | public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { |
michael@0 | 384 | final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); |
michael@0 | 385 | info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL)); |
michael@0 | 386 | info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE)); |
michael@0 | 387 | return info; |
michael@0 | 388 | } |
michael@0 | 389 | }); |
michael@0 | 390 | |
michael@0 | 391 | mContextMenuRegistry.register(view); |
michael@0 | 392 | |
michael@0 | 393 | if (view instanceof DatasetBacked) { |
michael@0 | 394 | DatasetBacked datasetBacked = (DatasetBacked) view; |
michael@0 | 395 | datasetBacked.setFilterManager(new PanelFilterManager(viewState)); |
michael@0 | 396 | |
michael@0 | 397 | // XXX: Disabled because of bug 1010986 |
michael@0 | 398 | // if (viewConfig.isRefreshEnabled()) { |
michael@0 | 399 | // view = new PanelRefreshLayout(getContext(), view, |
michael@0 | 400 | // mPanelConfig.getId(), viewConfig.getIndex()); |
michael@0 | 401 | // } |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | viewState.setView(view); |
michael@0 | 405 | } |
michael@0 | 406 | |
michael@0 | 407 | return view; |
michael@0 | 408 | } |
michael@0 | 409 | |
michael@0 | 410 | /** |
michael@0 | 411 | * Dispose any dataset references associated with the |
michael@0 | 412 | * given view. |
michael@0 | 413 | */ |
michael@0 | 414 | protected final void disposePanelView(View view) { |
michael@0 | 415 | Log.d(LOGTAG, "Disposing panel view"); |
michael@0 | 416 | final int count = mViewStates.size(); |
michael@0 | 417 | for (int i = 0; i < count; i++) { |
michael@0 | 418 | final ViewState viewState = mViewStates.valueAt(i); |
michael@0 | 419 | |
michael@0 | 420 | if (viewState.getView() == view) { |
michael@0 | 421 | maybeSetDataset(view, null); |
michael@0 | 422 | mViewStates.remove(viewState.getIndex()); |
michael@0 | 423 | break; |
michael@0 | 424 | } |
michael@0 | 425 | } |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | private void maybeSetDataset(View view, Cursor cursor) { |
michael@0 | 429 | if (view instanceof DatasetBacked) { |
michael@0 | 430 | final DatasetBacked dsb = (DatasetBacked) view; |
michael@0 | 431 | dsb.setDataset(cursor); |
michael@0 | 432 | } |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | private View createEmptyView(ViewConfig viewConfig) { |
michael@0 | 436 | Log.d(LOGTAG, "Creating empty view: " + viewConfig.getType()); |
michael@0 | 437 | |
michael@0 | 438 | ViewState viewState = mViewStates.get(viewConfig.getIndex()); |
michael@0 | 439 | if (viewState == null) { |
michael@0 | 440 | throw new IllegalStateException("No view state found for view index: " + viewConfig.getIndex()); |
michael@0 | 441 | } |
michael@0 | 442 | |
michael@0 | 443 | View view = viewState.getEmptyView(); |
michael@0 | 444 | if (view == null) { |
michael@0 | 445 | view = LayoutInflater.from(getContext()).inflate(R.layout.home_empty_panel, null); |
michael@0 | 446 | |
michael@0 | 447 | final EmptyViewConfig emptyViewConfig = viewConfig.getEmptyViewConfig(); |
michael@0 | 448 | |
michael@0 | 449 | // XXX: Refactor this into a custom view (bug 985134) |
michael@0 | 450 | final String text = (emptyViewConfig == null) ? null : emptyViewConfig.getText(); |
michael@0 | 451 | final TextView textView = (TextView) view.findViewById(R.id.home_empty_text); |
michael@0 | 452 | if (TextUtils.isEmpty(text)) { |
michael@0 | 453 | textView.setText(R.string.home_default_empty); |
michael@0 | 454 | } else { |
michael@0 | 455 | textView.setText(text); |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl(); |
michael@0 | 459 | final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image); |
michael@0 | 460 | |
michael@0 | 461 | if (TextUtils.isEmpty(imageUrl)) { |
michael@0 | 462 | imageView.setImageResource(R.drawable.icon_home_empty_firefox); |
michael@0 | 463 | } else { |
michael@0 | 464 | Picasso.with(getContext()) |
michael@0 | 465 | .load(imageUrl) |
michael@0 | 466 | .error(R.drawable.icon_home_empty_firefox) |
michael@0 | 467 | .into(imageView); |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | viewState.setEmptyView(view); |
michael@0 | 471 | } |
michael@0 | 472 | |
michael@0 | 473 | return view; |
michael@0 | 474 | } |
michael@0 | 475 | |
michael@0 | 476 | private void replacePanelView(View currentView, View newView) { |
michael@0 | 477 | final ViewGroup parent = (ViewGroup) currentView.getParent(); |
michael@0 | 478 | parent.addView(newView, parent.indexOfChild(currentView), currentView.getLayoutParams()); |
michael@0 | 479 | parent.removeView(currentView); |
michael@0 | 480 | } |
michael@0 | 481 | |
michael@0 | 482 | /** |
michael@0 | 483 | * Must be implemented by {@code PanelLayout} subclasses to define |
michael@0 | 484 | * what happens then the layout is first loaded. Should set initial |
michael@0 | 485 | * UI state and request any necessary datasets. |
michael@0 | 486 | */ |
michael@0 | 487 | public abstract void load(); |
michael@0 | 488 | |
michael@0 | 489 | /** |
michael@0 | 490 | * Represents a 'live' instance of a panel view associated with |
michael@0 | 491 | * the {@code PanelLayout}. Is responsible for tracking the history stack of filters. |
michael@0 | 492 | */ |
michael@0 | 493 | protected class ViewState { |
michael@0 | 494 | private final ViewConfig mViewConfig; |
michael@0 | 495 | private SoftReference<View> mView; |
michael@0 | 496 | private SoftReference<View> mEmptyView; |
michael@0 | 497 | private LinkedList<FilterDetail> mFilterStack; |
michael@0 | 498 | |
michael@0 | 499 | public ViewState(ViewConfig viewConfig) { |
michael@0 | 500 | mViewConfig = viewConfig; |
michael@0 | 501 | mView = new SoftReference<View>(null); |
michael@0 | 502 | mEmptyView = new SoftReference<View>(null); |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | public ViewConfig getViewConfig() { |
michael@0 | 506 | return mViewConfig; |
michael@0 | 507 | } |
michael@0 | 508 | |
michael@0 | 509 | public int getIndex() { |
michael@0 | 510 | return mViewConfig.getIndex(); |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | public View getView() { |
michael@0 | 514 | return mView.get(); |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | public void setView(View view) { |
michael@0 | 518 | mView = new SoftReference<View>(view); |
michael@0 | 519 | } |
michael@0 | 520 | |
michael@0 | 521 | public View getEmptyView() { |
michael@0 | 522 | return mEmptyView.get(); |
michael@0 | 523 | } |
michael@0 | 524 | |
michael@0 | 525 | public void setEmptyView(View view) { |
michael@0 | 526 | mEmptyView = new SoftReference<View>(view); |
michael@0 | 527 | } |
michael@0 | 528 | |
michael@0 | 529 | public View getActiveView() { |
michael@0 | 530 | final View view = getView(); |
michael@0 | 531 | if (view != null && view.getParent() != null) { |
michael@0 | 532 | return view; |
michael@0 | 533 | } |
michael@0 | 534 | |
michael@0 | 535 | final View emptyView = getEmptyView(); |
michael@0 | 536 | if (emptyView != null && emptyView.getParent() != null) { |
michael@0 | 537 | return emptyView; |
michael@0 | 538 | } |
michael@0 | 539 | |
michael@0 | 540 | return null; |
michael@0 | 541 | } |
michael@0 | 542 | |
michael@0 | 543 | public String getDatasetId() { |
michael@0 | 544 | return mViewConfig.getDatasetId(); |
michael@0 | 545 | } |
michael@0 | 546 | |
michael@0 | 547 | public ItemHandler getItemHandler() { |
michael@0 | 548 | return mViewConfig.getItemHandler(); |
michael@0 | 549 | } |
michael@0 | 550 | |
michael@0 | 551 | /** |
michael@0 | 552 | * Get the current filter that this view is displaying, or null if none. |
michael@0 | 553 | */ |
michael@0 | 554 | public FilterDetail getCurrentFilter() { |
michael@0 | 555 | if (mFilterStack == null) { |
michael@0 | 556 | return null; |
michael@0 | 557 | } else { |
michael@0 | 558 | return mFilterStack.peek(); |
michael@0 | 559 | } |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | /** |
michael@0 | 563 | * Get the previous filter that this view was displaying, or null if none. |
michael@0 | 564 | */ |
michael@0 | 565 | public FilterDetail getPreviousFilter() { |
michael@0 | 566 | if (!canPopFilter()) { |
michael@0 | 567 | return null; |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | return mFilterStack.get(1); |
michael@0 | 571 | } |
michael@0 | 572 | |
michael@0 | 573 | /** |
michael@0 | 574 | * Adds a filter to the history stack for this view. |
michael@0 | 575 | */ |
michael@0 | 576 | public void pushFilter(FilterDetail filter) { |
michael@0 | 577 | if (mFilterStack == null) { |
michael@0 | 578 | mFilterStack = new LinkedList<FilterDetail>(); |
michael@0 | 579 | |
michael@0 | 580 | // Initialize with the initial filter. |
michael@0 | 581 | mFilterStack.push(new FilterDetail(mViewConfig.getFilter(), |
michael@0 | 582 | mPanelConfig.getTitle())); |
michael@0 | 583 | } |
michael@0 | 584 | |
michael@0 | 585 | mFilterStack.push(filter); |
michael@0 | 586 | } |
michael@0 | 587 | |
michael@0 | 588 | /** |
michael@0 | 589 | * Remove the most recent filter from the stack. |
michael@0 | 590 | * |
michael@0 | 591 | * @return whether the filter was popped |
michael@0 | 592 | */ |
michael@0 | 593 | public boolean popFilter() { |
michael@0 | 594 | if (!canPopFilter()) { |
michael@0 | 595 | return false; |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | mFilterStack.pop(); |
michael@0 | 599 | return true; |
michael@0 | 600 | } |
michael@0 | 601 | |
michael@0 | 602 | public boolean canPopFilter() { |
michael@0 | 603 | return (mFilterStack != null && mFilterStack.size() > 1); |
michael@0 | 604 | } |
michael@0 | 605 | } |
michael@0 | 606 | |
michael@0 | 607 | static class FilterDetail implements Parcelable { |
michael@0 | 608 | final String filter; |
michael@0 | 609 | final String title; |
michael@0 | 610 | |
michael@0 | 611 | private FilterDetail(Parcel in) { |
michael@0 | 612 | this.filter = in.readString(); |
michael@0 | 613 | this.title = in.readString(); |
michael@0 | 614 | } |
michael@0 | 615 | |
michael@0 | 616 | public FilterDetail(String filter, String title) { |
michael@0 | 617 | this.filter = filter; |
michael@0 | 618 | this.title = title; |
michael@0 | 619 | } |
michael@0 | 620 | |
michael@0 | 621 | @Override |
michael@0 | 622 | public int describeContents() { |
michael@0 | 623 | return 0; |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | @Override |
michael@0 | 627 | public void writeToParcel(Parcel dest, int flags) { |
michael@0 | 628 | dest.writeString(filter); |
michael@0 | 629 | dest.writeString(title); |
michael@0 | 630 | } |
michael@0 | 631 | |
michael@0 | 632 | public static final Creator<FilterDetail> CREATOR = new Creator<FilterDetail>() { |
michael@0 | 633 | public FilterDetail createFromParcel(Parcel in) { |
michael@0 | 634 | return new FilterDetail(in); |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | public FilterDetail[] newArray(int size) { |
michael@0 | 638 | return new FilterDetail[size]; |
michael@0 | 639 | } |
michael@0 | 640 | }; |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | /** |
michael@0 | 644 | * Pushes filter to {@code ViewState}'s stack and makes request for new filter value. |
michael@0 | 645 | */ |
michael@0 | 646 | private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) { |
michael@0 | 647 | final int index = viewState.getIndex(); |
michael@0 | 648 | final String datasetId = viewState.getDatasetId(); |
michael@0 | 649 | |
michael@0 | 650 | mDatasetHandler.requestDataset(new DatasetRequest(index, |
michael@0 | 651 | DatasetRequest.Type.FILTER_PUSH, |
michael@0 | 652 | datasetId, |
michael@0 | 653 | filterDetail)); |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | /** |
michael@0 | 657 | * Pops filter from {@code ViewState}'s stack and makes request for previous filter value. |
michael@0 | 658 | * |
michael@0 | 659 | * @return whether the filter has changed |
michael@0 | 660 | */ |
michael@0 | 661 | private boolean popFilterOnView(ViewState viewState) { |
michael@0 | 662 | if (viewState.canPopFilter()) { |
michael@0 | 663 | final int index = viewState.getIndex(); |
michael@0 | 664 | final String datasetId = viewState.getDatasetId(); |
michael@0 | 665 | final FilterDetail filterDetail = viewState.getPreviousFilter(); |
michael@0 | 666 | |
michael@0 | 667 | mDatasetHandler.requestDataset(new DatasetRequest(index, |
michael@0 | 668 | DatasetRequest.Type.FILTER_POP, |
michael@0 | 669 | datasetId, |
michael@0 | 670 | filterDetail)); |
michael@0 | 671 | |
michael@0 | 672 | return true; |
michael@0 | 673 | } else { |
michael@0 | 674 | return false; |
michael@0 | 675 | } |
michael@0 | 676 | } |
michael@0 | 677 | |
michael@0 | 678 | public interface OnItemOpenListener { |
michael@0 | 679 | public void onItemOpen(String url, String title); |
michael@0 | 680 | } |
michael@0 | 681 | |
michael@0 | 682 | private class PanelOnItemOpenListener implements OnItemOpenListener { |
michael@0 | 683 | private ViewState mViewState; |
michael@0 | 684 | |
michael@0 | 685 | public PanelOnItemOpenListener(ViewState viewState) { |
michael@0 | 686 | mViewState = viewState; |
michael@0 | 687 | } |
michael@0 | 688 | |
michael@0 | 689 | @Override |
michael@0 | 690 | public void onItemOpen(String url, String title) { |
michael@0 | 691 | if (StringUtils.isFilterUrl(url)) { |
michael@0 | 692 | FilterDetail filterDetail = new FilterDetail(StringUtils.getFilterFromUrl(url), title); |
michael@0 | 693 | pushFilterOnView(mViewState, filterDetail); |
michael@0 | 694 | } else { |
michael@0 | 695 | EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class); |
michael@0 | 696 | if (mViewState.getItemHandler() == ItemHandler.INTENT) { |
michael@0 | 697 | flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT); |
michael@0 | 698 | } |
michael@0 | 699 | |
michael@0 | 700 | mUrlOpenListener.onUrlOpen(url, flags); |
michael@0 | 701 | } |
michael@0 | 702 | } |
michael@0 | 703 | } |
michael@0 | 704 | |
michael@0 | 705 | private class PanelKeyListener implements View.OnKeyListener { |
michael@0 | 706 | private ViewState mViewState; |
michael@0 | 707 | |
michael@0 | 708 | public PanelKeyListener(ViewState viewState) { |
michael@0 | 709 | mViewState = viewState; |
michael@0 | 710 | } |
michael@0 | 711 | |
michael@0 | 712 | @Override |
michael@0 | 713 | public boolean onKey(View v, int keyCode, KeyEvent event) { |
michael@0 | 714 | if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { |
michael@0 | 715 | return popFilterOnView(mViewState); |
michael@0 | 716 | } |
michael@0 | 717 | |
michael@0 | 718 | return false; |
michael@0 | 719 | } |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | private class PanelFilterManager implements FilterManager { |
michael@0 | 723 | private final ViewState mViewState; |
michael@0 | 724 | |
michael@0 | 725 | public PanelFilterManager(ViewState viewState) { |
michael@0 | 726 | mViewState = viewState; |
michael@0 | 727 | } |
michael@0 | 728 | |
michael@0 | 729 | @Override |
michael@0 | 730 | public FilterDetail getPreviousFilter() { |
michael@0 | 731 | return mViewState.getPreviousFilter(); |
michael@0 | 732 | } |
michael@0 | 733 | |
michael@0 | 734 | @Override |
michael@0 | 735 | public boolean canGoBack() { |
michael@0 | 736 | return mViewState.canPopFilter(); |
michael@0 | 737 | } |
michael@0 | 738 | |
michael@0 | 739 | @Override |
michael@0 | 740 | public void goBack() { |
michael@0 | 741 | popFilterOnView(mViewState); |
michael@0 | 742 | } |
michael@0 | 743 | } |
michael@0 | 744 | } |