summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java393
1 files changed, 393 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
new file mode 100644
index 000000000..d2c136219
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
@@ -0,0 +1,393 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserContract.HomeItems;
+import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+import org.mozilla.gecko.home.PanelLayout.ContextMenuRegistry;
+import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
+import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UIAsyncTask;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * Fragment that displays dynamic content specified by a {@code PanelConfig}.
+ * The {@code DynamicPanel} UI is built based on the given {@code LayoutType}
+ * and its associated list of {@code ViewConfig}.
+ *
+ * {@code DynamicPanel} manages all necessary Loaders to load panel datasets
+ * from their respective content providers. Each panel dataset has its own
+ * associated Loader. This is enforced by defining the Loader IDs based on
+ * their associated dataset IDs.
+ *
+ * The {@code PanelLayout} can make load and reset requests on datasets via
+ * the provided {@code DatasetHandler}. This way it doesn't need to know the
+ * details of how datasets are loaded and reset. Each time a dataset is
+ * requested, {@code DynamicPanel} restarts a Loader with the respective ID (see
+ * {@code PanelDatasetHandler}).
+ *
+ * See {@code PanelLayout} for more details on how {@code DynamicPanel}
+ * receives dataset requests and delivers them back to the {@code PanelLayout}.
+ */
+public class DynamicPanel extends HomeFragment {
+ private static final String LOGTAG = "GeckoDynamicPanel";
+
+ // Dataset ID to be used by the loader
+ private static final String DATASET_REQUEST = "dataset_request";
+
+ // Max number of items to display in the panel
+ private static final int RESULT_LIMIT = 100;
+
+ // The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
+ private FrameLayout mView;
+
+ // The panel layout associated with this panel
+ private PanelLayout mPanelLayout;
+
+ // The layout used to show authentication UI for this panel
+ private PanelAuthLayout mPanelAuthLayout;
+
+ // Cache used to keep track of whether or not the user has been authenticated.
+ private PanelAuthCache mPanelAuthCache;
+
+ // Hold a reference to the UiAsyncTask we use to check the state of the
+ // PanelAuthCache, so that we can cancel it if necessary.
+ private UIAsyncTask.WithoutParams<Boolean> mAuthStateTask;
+
+ // The configuration associated with this panel
+ private PanelConfig mPanelConfig;
+
+ // Callbacks used for the loader
+ private PanelLoaderCallbacks mLoaderCallbacks;
+
+ // The current UI mode in the fragment
+ private UIMode mUIMode;
+
+ /*
+ * Different UI modes to display depending on the authentication state.
+ *
+ * PANEL: Layout to display panel data.
+ * AUTH: Authentication UI.
+ */
+ private enum UIMode {
+ PANEL,
+ AUTH
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Bundle args = getArguments();
+ if (args != null) {
+ mPanelConfig = (PanelConfig) args.getParcelable(HomePager.PANEL_CONFIG_ARG);
+ }
+
+ if (mPanelConfig == null) {
+ throw new IllegalStateException("Can't create a DynamicPanel without a PanelConfig");
+ }
+
+ mPanelAuthCache = new PanelAuthCache(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mView = new FrameLayout(getActivity());
+ return mView;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ // Restore whatever the UI mode the fragment had before
+ // a device rotation.
+ if (mUIMode != null) {
+ setUIMode(mUIMode);
+ }
+
+ mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mView = null;
+ mPanelLayout = null;
+ mPanelAuthLayout = null;
+
+ mPanelAuthCache.setOnChangeListener(null);
+
+ if (mAuthStateTask != null) {
+ mAuthStateTask.cancel();
+ mAuthStateTask = null;
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Create callbacks before the initial loader is started.
+ mLoaderCallbacks = new PanelLoaderCallbacks();
+ loadIfVisible();
+ }
+
+ @Override
+ protected void load() {
+ Log.d(LOGTAG, "Loading layout");
+
+ if (requiresAuth()) {
+ mAuthStateTask = new UIAsyncTask.WithoutParams<Boolean>(ThreadUtils.getBackgroundHandler()) {
+ @Override
+ public synchronized Boolean doInBackground() {
+ return mPanelAuthCache.isAuthenticated(mPanelConfig.getId());
+ }
+
+ @Override
+ public void onPostExecute(Boolean isAuthenticated) {
+ mAuthStateTask = null;
+ setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
+ }
+ };
+ mAuthStateTask.execute();
+ } else {
+ setUIMode(UIMode.PANEL);
+ }
+ }
+
+ /**
+ * @return true if this panel requires authentication.
+ */
+ private boolean requiresAuth() {
+ return mPanelConfig.getAuthConfig() != null;
+ }
+
+ /**
+ * Lazily creates layout for panel data.
+ */
+ private void createPanelLayout() {
+ final ContextMenuRegistry contextMenuRegistry = new ContextMenuRegistry() {
+ @Override
+ public void register(View view) {
+ registerForContextMenu(view);
+ }
+ };
+
+ switch (mPanelConfig.getLayoutType()) {
+ case FRAME:
+ final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
+ mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler,
+ mUrlOpenListener, contextMenuRegistry);
+ break;
+
+ default:
+ throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
+ }
+
+ Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
+ mView.addView(mPanelLayout);
+ }
+
+ /**
+ * Lazily creates layout for authentication UI.
+ */
+ private void createPanelAuthLayout() {
+ mPanelAuthLayout = new PanelAuthLayout(getActivity(), mPanelConfig);
+ mView.addView(mPanelAuthLayout, 0);
+ }
+
+ private void setUIMode(UIMode mode) {
+ switch (mode) {
+ case PANEL:
+ if (mPanelAuthLayout != null) {
+ mPanelAuthLayout.setVisibility(View.GONE);
+ }
+ if (mPanelLayout == null) {
+ createPanelLayout();
+ }
+ mPanelLayout.setVisibility(View.VISIBLE);
+
+ // Only trigger a reload if the UI mode has changed
+ // (e.g. auth cache changes) and the fragment is allowed
+ // to load its contents. Any loaders associated with the
+ // panel layout will be automatically re-bound after a
+ // device rotation, no need to explicitly load it again.
+ if (mUIMode != mode && canLoad()) {
+ mPanelLayout.load();
+ }
+ break;
+
+ case AUTH:
+ if (mPanelLayout != null) {
+ mPanelLayout.setVisibility(View.GONE);
+ }
+ if (mPanelAuthLayout == null) {
+ createPanelAuthLayout();
+ }
+ mPanelAuthLayout.setVisibility(View.VISIBLE);
+ break;
+
+ default:
+ throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
+ }
+
+ mUIMode = mode;
+ }
+
+ /**
+ * Used by the PanelLayout to make load and reset requests to
+ * the holding fragment.
+ */
+ private class PanelDatasetHandler implements DatasetHandler {
+ @Override
+ public void requestDataset(DatasetRequest request) {
+ Log.d(LOGTAG, "Requesting request: " + request);
+
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(DATASET_REQUEST, request);
+
+ getLoaderManager().restartLoader(request.getViewIndex(),
+ bundle, mLoaderCallbacks);
+ }
+
+ @Override
+ public void resetDataset(int viewIndex) {
+ Log.d(LOGTAG, "Resetting dataset: " + viewIndex);
+
+ final LoaderManager lm = getLoaderManager();
+
+ // Release any resources associated with the dataset if
+ // it's currently loaded in memory.
+ final Loader<?> datasetLoader = lm.getLoader(viewIndex);
+ if (datasetLoader != null) {
+ datasetLoader.reset();
+ }
+ }
+ }
+
+ /**
+ * Cursor loader for the panel datasets.
+ */
+ private static class PanelDatasetLoader extends SimpleCursorLoader {
+ 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 = HomeItems.FILTER + " IS NULL";
+ selectionArgs = null;
+ } else {
+ selection = HomeItems.FILTER + " = ?";
+ selectionArgs = new String[] { mRequest.getFilter() };
+ }
+
+ final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
+ .appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
+ mRequest.getDatasetId())
+ .appendQueryParameter(BrowserContract.PARAM_LIMIT,
+ String.valueOf(RESULT_LIMIT))
+ .build();
+
+ // XXX: You can use HomeItems.CONTENT_FAKE_URI for development
+ // to pull items from fake_home_items.json.
+ return cr.query(queryUri, null, selection, selectionArgs, null);
+ }
+ }
+
+ /**
+ * LoaderCallbacks implementation that interacts with the LoaderManager.
+ */
+ private class PanelLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ final DatasetRequest request = (DatasetRequest) args.getParcelable(DATASET_REQUEST);
+
+ 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);
+
+ if (mPanelLayout != null) {
+ mPanelLayout.deliverDataset(request, cursor);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ final DatasetRequest request = getRequestFromLoader(loader);
+ Log.d(LOGTAG, "Resetting loader for request: " + request);
+
+ if (mPanelLayout != null) {
+ mPanelLayout.releaseDataset(request.getViewIndex());
+ }
+ }
+
+ private DatasetRequest getRequestFromLoader(Loader<Cursor> loader) {
+ final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
+ return datasetLoader.getRequest();
+ }
+ }
+
+ private class PanelAuthChangeListener implements PanelAuthCache.OnChangeListener {
+ @Override
+ public void onChange(String panelId, boolean isAuthenticated) {
+ if (!mPanelConfig.getId().equals(panelId)) {
+ return;
+ }
+
+ setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
+ }
+ }
+}