mobile/android/base/home/DynamicPanel.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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.json.JSONException;
michael@0 9 import org.json.JSONObject;
michael@0 10
michael@0 11 import org.mozilla.gecko.GeckoAppShell;
michael@0 12 import org.mozilla.gecko.db.BrowserContract;
michael@0 13 import org.mozilla.gecko.db.BrowserContract.HomeItems;
michael@0 14 import org.mozilla.gecko.db.DBUtils;
michael@0 15 import org.mozilla.gecko.db.HomeProvider;
michael@0 16 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
michael@0 17 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
michael@0 18 import org.mozilla.gecko.home.PanelLayout.ContextMenuRegistry;
michael@0 19 import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
michael@0 20 import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
michael@0 21 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 22 import org.mozilla.gecko.util.ThreadUtils;
michael@0 23 import org.mozilla.gecko.util.UiAsyncTask;
michael@0 24
michael@0 25 import android.app.Activity;
michael@0 26 import android.content.ContentResolver;
michael@0 27 import android.content.Context;
michael@0 28 import android.content.res.Configuration;
michael@0 29 import android.database.Cursor;
michael@0 30 import android.net.Uri;
michael@0 31 import android.os.Bundle;
michael@0 32 import android.support.v4.app.LoaderManager;
michael@0 33 import android.support.v4.app.LoaderManager.LoaderCallbacks;
michael@0 34 import android.support.v4.content.Loader;
michael@0 35 import android.util.Log;
michael@0 36 import android.view.LayoutInflater;
michael@0 37 import android.view.View;
michael@0 38 import android.view.ViewGroup;
michael@0 39 import android.widget.FrameLayout;
michael@0 40
michael@0 41 /**
michael@0 42 * Fragment that displays dynamic content specified by a {@code PanelConfig}.
michael@0 43 * The {@code DynamicPanel} UI is built based on the given {@code LayoutType}
michael@0 44 * and its associated list of {@code ViewConfig}.
michael@0 45 *
michael@0 46 * {@code DynamicPanel} manages all necessary Loaders to load panel datasets
michael@0 47 * from their respective content providers. Each panel dataset has its own
michael@0 48 * associated Loader. This is enforced by defining the Loader IDs based on
michael@0 49 * their associated dataset IDs.
michael@0 50 *
michael@0 51 * The {@code PanelLayout} can make load and reset requests on datasets via
michael@0 52 * the provided {@code DatasetHandler}. This way it doesn't need to know the
michael@0 53 * details of how datasets are loaded and reset. Each time a dataset is
michael@0 54 * requested, {@code DynamicPanel} restarts a Loader with the respective ID (see
michael@0 55 * {@code PanelDatasetHandler}).
michael@0 56 *
michael@0 57 * See {@code PanelLayout} for more details on how {@code DynamicPanel}
michael@0 58 * receives dataset requests and delivers them back to the {@code PanelLayout}.
michael@0 59 */
michael@0 60 public class DynamicPanel extends HomeFragment {
michael@0 61 private static final String LOGTAG = "GeckoDynamicPanel";
michael@0 62
michael@0 63 // Dataset ID to be used by the loader
michael@0 64 private static final String DATASET_REQUEST = "dataset_request";
michael@0 65
michael@0 66 // The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
michael@0 67 private FrameLayout mView;
michael@0 68
michael@0 69 // The panel layout associated with this panel
michael@0 70 private PanelLayout mPanelLayout;
michael@0 71
michael@0 72 // The layout used to show authentication UI for this panel
michael@0 73 private PanelAuthLayout mPanelAuthLayout;
michael@0 74
michael@0 75 // Cache used to keep track of whether or not the user has been authenticated.
michael@0 76 private PanelAuthCache mPanelAuthCache;
michael@0 77
michael@0 78 // Hold a reference to the UiAsyncTask we use to check the state of the
michael@0 79 // PanelAuthCache, so that we can cancel it if necessary.
michael@0 80 private UiAsyncTask<Void, Void, Boolean> mAuthStateTask;
michael@0 81
michael@0 82 // The configuration associated with this panel
michael@0 83 private PanelConfig mPanelConfig;
michael@0 84
michael@0 85 // Callbacks used for the loader
michael@0 86 private PanelLoaderCallbacks mLoaderCallbacks;
michael@0 87
michael@0 88 // On URL open listener
michael@0 89 private OnUrlOpenListener mUrlOpenListener;
michael@0 90
michael@0 91 // The current UI mode in the fragment
michael@0 92 private UIMode mUIMode;
michael@0 93
michael@0 94 /*
michael@0 95 * Different UI modes to display depending on the authentication state.
michael@0 96 *
michael@0 97 * PANEL: Layout to display panel data.
michael@0 98 * AUTH: Authentication UI.
michael@0 99 */
michael@0 100 private enum UIMode {
michael@0 101 PANEL,
michael@0 102 AUTH
michael@0 103 }
michael@0 104
michael@0 105 @Override
michael@0 106 public void onAttach(Activity activity) {
michael@0 107 super.onAttach(activity);
michael@0 108
michael@0 109 try {
michael@0 110 mUrlOpenListener = (OnUrlOpenListener) activity;
michael@0 111 } catch (ClassCastException e) {
michael@0 112 throw new ClassCastException(activity.toString()
michael@0 113 + " must implement HomePager.OnUrlOpenListener");
michael@0 114 }
michael@0 115 }
michael@0 116
michael@0 117 @Override
michael@0 118 public void onDetach() {
michael@0 119 super.onDetach();
michael@0 120
michael@0 121 mUrlOpenListener = null;
michael@0 122 }
michael@0 123
michael@0 124 @Override
michael@0 125 public void onCreate(Bundle savedInstanceState) {
michael@0 126 super.onCreate(savedInstanceState);
michael@0 127
michael@0 128 final Bundle args = getArguments();
michael@0 129 if (args != null) {
michael@0 130 mPanelConfig = (PanelConfig) args.getParcelable(HomePager.PANEL_CONFIG_ARG);
michael@0 131 }
michael@0 132
michael@0 133 if (mPanelConfig == null) {
michael@0 134 throw new IllegalStateException("Can't create a DynamicPanel without a PanelConfig");
michael@0 135 }
michael@0 136
michael@0 137 mPanelAuthCache = new PanelAuthCache(getActivity());
michael@0 138 }
michael@0 139
michael@0 140 @Override
michael@0 141 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
michael@0 142 mView = new FrameLayout(getActivity());
michael@0 143 return mView;
michael@0 144 }
michael@0 145
michael@0 146 @Override
michael@0 147 public void onViewCreated(View view, Bundle savedInstanceState) {
michael@0 148 super.onViewCreated(view, savedInstanceState);
michael@0 149
michael@0 150 // Restore whatever the UI mode the fragment had before
michael@0 151 // a device rotation.
michael@0 152 if (mUIMode != null) {
michael@0 153 setUIMode(mUIMode);
michael@0 154 }
michael@0 155
michael@0 156 mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
michael@0 157 }
michael@0 158
michael@0 159 @Override
michael@0 160 public void onDestroyView() {
michael@0 161 super.onDestroyView();
michael@0 162 mView = null;
michael@0 163 mPanelLayout = null;
michael@0 164 mPanelAuthLayout = null;
michael@0 165
michael@0 166 mPanelAuthCache.setOnChangeListener(null);
michael@0 167
michael@0 168 if (mAuthStateTask != null) {
michael@0 169 mAuthStateTask.cancel(true);
michael@0 170 mAuthStateTask = null;
michael@0 171 }
michael@0 172 }
michael@0 173
michael@0 174 @Override
michael@0 175 public void onConfigurationChanged(Configuration newConfig) {
michael@0 176 super.onConfigurationChanged(newConfig);
michael@0 177
michael@0 178 // Detach and reattach the fragment as the layout changes.
michael@0 179 if (isVisible()) {
michael@0 180 getFragmentManager().beginTransaction()
michael@0 181 .detach(this)
michael@0 182 .attach(this)
michael@0 183 .commitAllowingStateLoss();
michael@0 184 }
michael@0 185 }
michael@0 186
michael@0 187 @Override
michael@0 188 public void onActivityCreated(Bundle savedInstanceState) {
michael@0 189 super.onActivityCreated(savedInstanceState);
michael@0 190
michael@0 191 // Create callbacks before the initial loader is started.
michael@0 192 mLoaderCallbacks = new PanelLoaderCallbacks();
michael@0 193 loadIfVisible();
michael@0 194 }
michael@0 195
michael@0 196 @Override
michael@0 197 protected void load() {
michael@0 198 Log.d(LOGTAG, "Loading layout");
michael@0 199
michael@0 200 if (requiresAuth()) {
michael@0 201 mAuthStateTask = new UiAsyncTask<Void, Void, Boolean>(ThreadUtils.getBackgroundHandler()) {
michael@0 202 @Override
michael@0 203 public synchronized Boolean doInBackground(Void... params) {
michael@0 204 return mPanelAuthCache.isAuthenticated(mPanelConfig.getId());
michael@0 205 }
michael@0 206
michael@0 207 @Override
michael@0 208 public void onPostExecute(Boolean isAuthenticated) {
michael@0 209 mAuthStateTask = null;
michael@0 210 setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
michael@0 211 }
michael@0 212 };
michael@0 213 mAuthStateTask.execute();
michael@0 214 } else {
michael@0 215 setUIMode(UIMode.PANEL);
michael@0 216 }
michael@0 217 }
michael@0 218
michael@0 219 /**
michael@0 220 * @return true if this panel requires authentication.
michael@0 221 */
michael@0 222 private boolean requiresAuth() {
michael@0 223 return mPanelConfig.getAuthConfig() != null;
michael@0 224 }
michael@0 225
michael@0 226 /**
michael@0 227 * Lazily creates layout for panel data.
michael@0 228 */
michael@0 229 private void createPanelLayout() {
michael@0 230 final ContextMenuRegistry contextMenuRegistry = new ContextMenuRegistry() {
michael@0 231 @Override
michael@0 232 public void register(View view) {
michael@0 233 registerForContextMenu(view);
michael@0 234 }
michael@0 235 };
michael@0 236
michael@0 237 switch(mPanelConfig.getLayoutType()) {
michael@0 238 case FRAME:
michael@0 239 final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
michael@0 240 mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler,
michael@0 241 mUrlOpenListener, contextMenuRegistry);
michael@0 242 break;
michael@0 243
michael@0 244 default:
michael@0 245 throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
michael@0 246 }
michael@0 247
michael@0 248 Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
michael@0 249 mView.addView(mPanelLayout);
michael@0 250 }
michael@0 251
michael@0 252 /**
michael@0 253 * Lazily creates layout for authentication UI.
michael@0 254 */
michael@0 255 private void createPanelAuthLayout() {
michael@0 256 mPanelAuthLayout = new PanelAuthLayout(getActivity(), mPanelConfig);
michael@0 257 mView.addView(mPanelAuthLayout, 0);
michael@0 258 }
michael@0 259
michael@0 260 private void setUIMode(UIMode mode) {
michael@0 261 switch(mode) {
michael@0 262 case PANEL:
michael@0 263 if (mPanelAuthLayout != null) {
michael@0 264 mPanelAuthLayout.setVisibility(View.GONE);
michael@0 265 }
michael@0 266 if (mPanelLayout == null) {
michael@0 267 createPanelLayout();
michael@0 268 }
michael@0 269 mPanelLayout.setVisibility(View.VISIBLE);
michael@0 270
michael@0 271 // Only trigger a reload if the UI mode has changed
michael@0 272 // (e.g. auth cache changes) and the fragment is allowed
michael@0 273 // to load its contents. Any loaders associated with the
michael@0 274 // panel layout will be automatically re-bound after a
michael@0 275 // device rotation, no need to explicitly load it again.
michael@0 276 if (mUIMode != mode && canLoad()) {
michael@0 277 mPanelLayout.load();
michael@0 278 }
michael@0 279 break;
michael@0 280
michael@0 281 case AUTH:
michael@0 282 if (mPanelLayout != null) {
michael@0 283 mPanelLayout.setVisibility(View.GONE);
michael@0 284 }
michael@0 285 if (mPanelAuthLayout == null) {
michael@0 286 createPanelAuthLayout();
michael@0 287 }
michael@0 288 mPanelAuthLayout.setVisibility(View.VISIBLE);
michael@0 289 break;
michael@0 290
michael@0 291 default:
michael@0 292 throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
michael@0 293 }
michael@0 294
michael@0 295 mUIMode = mode;
michael@0 296 }
michael@0 297
michael@0 298 /**
michael@0 299 * Used by the PanelLayout to make load and reset requests to
michael@0 300 * the holding fragment.
michael@0 301 */
michael@0 302 private class PanelDatasetHandler implements DatasetHandler {
michael@0 303 @Override
michael@0 304 public void requestDataset(DatasetRequest request) {
michael@0 305 Log.d(LOGTAG, "Requesting request: " + request);
michael@0 306
michael@0 307 // Ignore dataset requests while the fragment is not
michael@0 308 // allowed to load its content.
michael@0 309 if (!getCanLoadHint()) {
michael@0 310 return;
michael@0 311 }
michael@0 312
michael@0 313 final Bundle bundle = new Bundle();
michael@0 314 bundle.putParcelable(DATASET_REQUEST, request);
michael@0 315
michael@0 316 getLoaderManager().restartLoader(request.getViewIndex(),
michael@0 317 bundle, mLoaderCallbacks);
michael@0 318 }
michael@0 319
michael@0 320 @Override
michael@0 321 public void resetDataset(int viewIndex) {
michael@0 322 Log.d(LOGTAG, "Resetting dataset: " + viewIndex);
michael@0 323
michael@0 324 final LoaderManager lm = getLoaderManager();
michael@0 325
michael@0 326 // Release any resources associated with the dataset if
michael@0 327 // it's currently loaded in memory.
michael@0 328 final Loader<?> datasetLoader = lm.getLoader(viewIndex);
michael@0 329 if (datasetLoader != null) {
michael@0 330 datasetLoader.reset();
michael@0 331 }
michael@0 332 }
michael@0 333 }
michael@0 334
michael@0 335 /**
michael@0 336 * Cursor loader for the panel datasets.
michael@0 337 */
michael@0 338 private static class PanelDatasetLoader extends SimpleCursorLoader {
michael@0 339 private DatasetRequest mRequest;
michael@0 340
michael@0 341 public PanelDatasetLoader(Context context, DatasetRequest request) {
michael@0 342 super(context);
michael@0 343 mRequest = request;
michael@0 344 }
michael@0 345
michael@0 346 public DatasetRequest getRequest() {
michael@0 347 return mRequest;
michael@0 348 }
michael@0 349
michael@0 350 @Override
michael@0 351 public void onContentChanged() {
michael@0 352 // Ensure the refresh request doesn't affect the view's filter
michael@0 353 // stack (i.e. use DATASET_LOAD type) but keep the current
michael@0 354 // dataset ID and filter.
michael@0 355 final DatasetRequest newRequest =
michael@0 356 new DatasetRequest(mRequest.getViewIndex(),
michael@0 357 DatasetRequest.Type.DATASET_LOAD,
michael@0 358 mRequest.getDatasetId(),
michael@0 359 mRequest.getFilterDetail());
michael@0 360
michael@0 361 mRequest = newRequest;
michael@0 362 super.onContentChanged();
michael@0 363 }
michael@0 364
michael@0 365 @Override
michael@0 366 public Cursor loadCursor() {
michael@0 367 final ContentResolver cr = getContext().getContentResolver();
michael@0 368
michael@0 369 final String selection;
michael@0 370 final String[] selectionArgs;
michael@0 371
michael@0 372 // Null represents the root filter
michael@0 373 if (mRequest.getFilter() == null) {
michael@0 374 selection = HomeItems.FILTER + " IS NULL";
michael@0 375 selectionArgs = null;
michael@0 376 } else {
michael@0 377 selection = HomeItems.FILTER + " = ?";
michael@0 378 selectionArgs = new String[] { mRequest.getFilter() };
michael@0 379 }
michael@0 380
michael@0 381 final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
michael@0 382 .appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
michael@0 383 mRequest.getDatasetId())
michael@0 384 .build();
michael@0 385
michael@0 386 // XXX: You can use HomeItems.CONTENT_FAKE_URI for development
michael@0 387 // to pull items from fake_home_items.json.
michael@0 388 return cr.query(queryUri, null, selection, selectionArgs, null);
michael@0 389 }
michael@0 390 }
michael@0 391
michael@0 392 /**
michael@0 393 * LoaderCallbacks implementation that interacts with the LoaderManager.
michael@0 394 */
michael@0 395 private class PanelLoaderCallbacks implements LoaderCallbacks<Cursor> {
michael@0 396 @Override
michael@0 397 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
michael@0 398 final DatasetRequest request = (DatasetRequest) args.getParcelable(DATASET_REQUEST);
michael@0 399
michael@0 400 Log.d(LOGTAG, "Creating loader for request: " + request);
michael@0 401 return new PanelDatasetLoader(getActivity(), request);
michael@0 402 }
michael@0 403
michael@0 404 @Override
michael@0 405 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
michael@0 406 final DatasetRequest request = getRequestFromLoader(loader);
michael@0 407 Log.d(LOGTAG, "Finished loader for request: " + request);
michael@0 408
michael@0 409 if (mPanelLayout != null) {
michael@0 410 mPanelLayout.deliverDataset(request, cursor);
michael@0 411 }
michael@0 412 }
michael@0 413
michael@0 414 @Override
michael@0 415 public void onLoaderReset(Loader<Cursor> loader) {
michael@0 416 final DatasetRequest request = getRequestFromLoader(loader);
michael@0 417 Log.d(LOGTAG, "Resetting loader for request: " + request);
michael@0 418
michael@0 419 if (mPanelLayout != null) {
michael@0 420 mPanelLayout.releaseDataset(request.getViewIndex());
michael@0 421 }
michael@0 422 }
michael@0 423
michael@0 424 private DatasetRequest getRequestFromLoader(Loader<Cursor> loader) {
michael@0 425 final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
michael@0 426 return datasetLoader.getRequest();
michael@0 427 }
michael@0 428 }
michael@0 429
michael@0 430 private class PanelAuthChangeListener implements PanelAuthCache.OnChangeListener {
michael@0 431 @Override
michael@0 432 public void onChange(String panelId, boolean isAuthenticated) {
michael@0 433 if (!mPanelConfig.getId().equals(panelId)) {
michael@0 434 return;
michael@0 435 }
michael@0 436
michael@0 437 setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
michael@0 438 }
michael@0 439 }
michael@0 440 }

mercurial