mobile/android/base/home/DynamicPanel.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial