Wed, 31 Dec 2014 07:22:50 +0100
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 }