mobile/android/base/home/HomePager.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 java.util.ArrayList;
michael@0 9 import java.util.EnumSet;
michael@0 10 import java.util.List;
michael@0 11
michael@0 12 import org.mozilla.gecko.R;
michael@0 13 import org.mozilla.gecko.Telemetry;
michael@0 14 import org.mozilla.gecko.TelemetryContract;
michael@0 15 import org.mozilla.gecko.animation.PropertyAnimator;
michael@0 16 import org.mozilla.gecko.animation.ViewHelper;
michael@0 17 import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
michael@0 18 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
michael@0 19 import org.mozilla.gecko.util.ThreadUtils;
michael@0 20
michael@0 21 import android.content.Context;
michael@0 22 import android.graphics.drawable.Drawable;
michael@0 23 import android.os.Build;
michael@0 24 import android.os.Bundle;
michael@0 25 import android.support.v4.app.FragmentManager;
michael@0 26 import android.support.v4.app.LoaderManager;
michael@0 27 import android.support.v4.app.LoaderManager.LoaderCallbacks;
michael@0 28 import android.support.v4.content.Loader;
michael@0 29 import android.support.v4.view.ViewPager;
michael@0 30 import android.util.AttributeSet;
michael@0 31 import android.view.MotionEvent;
michael@0 32 import android.view.View;
michael@0 33 import android.view.ViewGroup;
michael@0 34
michael@0 35 public class HomePager extends ViewPager {
michael@0 36 private static final int LOADER_ID_CONFIG = 0;
michael@0 37
michael@0 38 private final Context mContext;
michael@0 39 private volatile boolean mVisible;
michael@0 40 private Decor mDecor;
michael@0 41 private View mTabStrip;
michael@0 42 private HomeBanner mHomeBanner;
michael@0 43 private int mDefaultPageIndex = -1;
michael@0 44
michael@0 45 private final OnAddPanelListener mAddPanelListener;
michael@0 46
michael@0 47 private final HomeConfig mConfig;
michael@0 48 private ConfigLoaderCallbacks mConfigLoaderCallbacks;
michael@0 49
michael@0 50 private String mInitialPanelId;
michael@0 51
michael@0 52 // Cached original ViewPager background.
michael@0 53 private final Drawable mOriginalBackground;
michael@0 54
michael@0 55 // Telemetry session for current panel.
michael@0 56 private String mCurrentPanelSession;
michael@0 57
michael@0 58 // Current load state of HomePager.
michael@0 59 private LoadState mLoadState;
michael@0 60
michael@0 61 // Listens for when the current panel changes.
michael@0 62 private OnPanelChangeListener mPanelChangedListener;
michael@0 63
michael@0 64 // This is mostly used by UI tests to easily fetch
michael@0 65 // specific list views at runtime.
michael@0 66 static final String LIST_TAG_HISTORY = "history";
michael@0 67 static final String LIST_TAG_BOOKMARKS = "bookmarks";
michael@0 68 static final String LIST_TAG_READING_LIST = "reading_list";
michael@0 69 static final String LIST_TAG_TOP_SITES = "top_sites";
michael@0 70 static final String LIST_TAG_MOST_RECENT = "most_recent";
michael@0 71 static final String LIST_TAG_LAST_TABS = "last_tabs";
michael@0 72 static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
michael@0 73
michael@0 74 public interface OnUrlOpenListener {
michael@0 75 public enum Flags {
michael@0 76 ALLOW_SWITCH_TO_TAB,
michael@0 77 OPEN_WITH_INTENT
michael@0 78 }
michael@0 79
michael@0 80 public void onUrlOpen(String url, EnumSet<Flags> flags);
michael@0 81 }
michael@0 82
michael@0 83 public interface OnNewTabsListener {
michael@0 84 public void onNewTabs(String[] urls);
michael@0 85 }
michael@0 86
michael@0 87 /**
michael@0 88 * Interface for listening into ViewPager panel changes
michael@0 89 */
michael@0 90 public interface OnPanelChangeListener {
michael@0 91 /**
michael@0 92 * Called when a new panel is selected.
michael@0 93 *
michael@0 94 * @param panelId of the newly selected panel
michael@0 95 */
michael@0 96 public void onPanelSelected(String panelId);
michael@0 97 }
michael@0 98
michael@0 99 interface OnTitleClickListener {
michael@0 100 public void onTitleClicked(int index);
michael@0 101 }
michael@0 102
michael@0 103 /**
michael@0 104 * Special type of child views that could be added as pager decorations by default.
michael@0 105 */
michael@0 106 interface Decor {
michael@0 107 public void onAddPagerView(String title);
michael@0 108 public void removeAllPagerViews();
michael@0 109 public void onPageSelected(int position);
michael@0 110 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
michael@0 111 public void setOnTitleClickListener(OnTitleClickListener onTitleClickListener);
michael@0 112 }
michael@0 113
michael@0 114 /**
michael@0 115 * State of HomePager with respect to loading its configuration.
michael@0 116 */
michael@0 117 private enum LoadState {
michael@0 118 UNLOADED,
michael@0 119 LOADING,
michael@0 120 LOADED
michael@0 121 }
michael@0 122
michael@0 123 static final String CAN_LOAD_ARG = "canLoad";
michael@0 124 static final String PANEL_CONFIG_ARG = "panelConfig";
michael@0 125
michael@0 126 public HomePager(Context context) {
michael@0 127 this(context, null);
michael@0 128 }
michael@0 129
michael@0 130 public HomePager(Context context, AttributeSet attrs) {
michael@0 131 super(context, attrs);
michael@0 132 mContext = context;
michael@0 133
michael@0 134 mConfig = HomeConfig.getDefault(mContext);
michael@0 135 mConfigLoaderCallbacks = new ConfigLoaderCallbacks();
michael@0 136
michael@0 137 mAddPanelListener = new OnAddPanelListener() {
michael@0 138 @Override
michael@0 139 public void onAddPanel(String title) {
michael@0 140 if (mDecor != null) {
michael@0 141 mDecor.onAddPagerView(title);
michael@0 142 }
michael@0 143 }
michael@0 144 };
michael@0 145
michael@0 146 // This is to keep all 4 panels in memory after they are
michael@0 147 // selected in the pager.
michael@0 148 setOffscreenPageLimit(3);
michael@0 149
michael@0 150 // We can call HomePager.requestFocus to steal focus from the URL bar and drop the soft
michael@0 151 // keyboard. However, if there are no focusable views (e.g. an empty reading list), the
michael@0 152 // URL bar will be refocused. Therefore, we make the HomePager container focusable to
michael@0 153 // ensure there is always a focusable view. This would ordinarily be done via an XML
michael@0 154 // attribute, but it is not working properly.
michael@0 155 setFocusableInTouchMode(true);
michael@0 156
michael@0 157 mOriginalBackground = getBackground();
michael@0 158 setOnPageChangeListener(new PageChangeListener());
michael@0 159
michael@0 160 mLoadState = LoadState.UNLOADED;
michael@0 161 }
michael@0 162
michael@0 163 @Override
michael@0 164 public void addView(View child, int index, ViewGroup.LayoutParams params) {
michael@0 165 if (child instanceof Decor) {
michael@0 166 ((ViewPager.LayoutParams) params).isDecor = true;
michael@0 167 mDecor = (Decor) child;
michael@0 168 mTabStrip = child;
michael@0 169
michael@0 170 mDecor.setOnTitleClickListener(new OnTitleClickListener() {
michael@0 171 @Override
michael@0 172 public void onTitleClicked(int index) {
michael@0 173 setCurrentItem(index, true);
michael@0 174 }
michael@0 175 });
michael@0 176 } else if (child instanceof HomePagerTabStrip) {
michael@0 177 mTabStrip = child;
michael@0 178 }
michael@0 179
michael@0 180 super.addView(child, index, params);
michael@0 181 }
michael@0 182
michael@0 183 /**
michael@0 184 * Loads and initializes the pager.
michael@0 185 *
michael@0 186 * @param fm FragmentManager for the adapter
michael@0 187 */
michael@0 188 public void load(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
michael@0 189 mLoadState = LoadState.LOADING;
michael@0 190
michael@0 191 mVisible = true;
michael@0 192 mInitialPanelId = panelId;
michael@0 193
michael@0 194 // Update the home banner message each time the HomePager is loaded.
michael@0 195 if (mHomeBanner != null) {
michael@0 196 mHomeBanner.update();
michael@0 197 }
michael@0 198
michael@0 199 // Only animate on post-HC devices, when a non-null animator is given
michael@0 200 final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
michael@0 201
michael@0 202 final HomeAdapter adapter = new HomeAdapter(mContext, fm);
michael@0 203 adapter.setOnAddPanelListener(mAddPanelListener);
michael@0 204 adapter.setCanLoadHint(!shouldAnimate);
michael@0 205 setAdapter(adapter);
michael@0 206
michael@0 207 // Don't show the tabs strip until we have the
michael@0 208 // list of panels in place.
michael@0 209 mTabStrip.setVisibility(View.INVISIBLE);
michael@0 210
michael@0 211 // Load list of panels from configuration
michael@0 212 lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
michael@0 213
michael@0 214 if (shouldAnimate) {
michael@0 215 animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
michael@0 216 @Override
michael@0 217 public void onPropertyAnimationStart() {
michael@0 218 setLayerType(View.LAYER_TYPE_HARDWARE, null);
michael@0 219 }
michael@0 220
michael@0 221 @Override
michael@0 222 public void onPropertyAnimationEnd() {
michael@0 223 setLayerType(View.LAYER_TYPE_NONE, null);
michael@0 224 adapter.setCanLoadHint(true);
michael@0 225 }
michael@0 226 });
michael@0 227
michael@0 228 ViewHelper.setAlpha(this, 0.0f);
michael@0 229
michael@0 230 animator.attach(this,
michael@0 231 PropertyAnimator.Property.ALPHA,
michael@0 232 1.0f);
michael@0 233 }
michael@0 234 Telemetry.startUISession(TelemetryContract.Session.HOME);
michael@0 235 }
michael@0 236
michael@0 237 /**
michael@0 238 * Removes all child fragments to free memory.
michael@0 239 */
michael@0 240 public void unload() {
michael@0 241 mVisible = false;
michael@0 242 setAdapter(null);
michael@0 243 mLoadState = LoadState.UNLOADED;
michael@0 244
michael@0 245 // Stop UI Telemetry sessions.
michael@0 246 stopCurrentPanelTelemetrySession();
michael@0 247 Telemetry.stopUISession(TelemetryContract.Session.HOME);
michael@0 248 }
michael@0 249
michael@0 250 /**
michael@0 251 * Determines whether the pager is visible.
michael@0 252 *
michael@0 253 * Unlike getVisibility(), this method does not need to be called on the UI
michael@0 254 * thread.
michael@0 255 *
michael@0 256 * @return Whether the pager and its fragments are loaded
michael@0 257 */
michael@0 258 public boolean isVisible() {
michael@0 259 return mVisible;
michael@0 260 }
michael@0 261
michael@0 262 @Override
michael@0 263 public void setCurrentItem(int item, boolean smoothScroll) {
michael@0 264 super.setCurrentItem(item, smoothScroll);
michael@0 265
michael@0 266 if (mDecor != null) {
michael@0 267 mDecor.onPageSelected(item);
michael@0 268 }
michael@0 269
michael@0 270 if (mHomeBanner != null) {
michael@0 271 mHomeBanner.setActive(item == mDefaultPageIndex);
michael@0 272 }
michael@0 273 }
michael@0 274
michael@0 275 /**
michael@0 276 * Shows a home panel. If the given panelId is null,
michael@0 277 * the default panel will be shown. No action will be taken if:
michael@0 278 * * HomePager has not loaded yet
michael@0 279 * * Panel with the given panelId cannot be found
michael@0 280 *
michael@0 281 * @param panelId of the home panel to be shown.
michael@0 282 */
michael@0 283 public void showPanel(String panelId) {
michael@0 284 if (!mVisible) {
michael@0 285 return;
michael@0 286 }
michael@0 287
michael@0 288 switch (mLoadState) {
michael@0 289 case LOADING:
michael@0 290 mInitialPanelId = panelId;
michael@0 291 break;
michael@0 292
michael@0 293 case LOADED:
michael@0 294 int position = mDefaultPageIndex;
michael@0 295 if (panelId != null) {
michael@0 296 position = ((HomeAdapter) getAdapter()).getItemPosition(panelId);
michael@0 297 }
michael@0 298
michael@0 299 if (position > -1) {
michael@0 300 setCurrentItem(position);
michael@0 301 }
michael@0 302 break;
michael@0 303
michael@0 304 default:
michael@0 305 // Do nothing.
michael@0 306 }
michael@0 307 }
michael@0 308
michael@0 309 @Override
michael@0 310 public boolean onInterceptTouchEvent(MotionEvent event) {
michael@0 311 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
michael@0 312 // Drop the soft keyboard by stealing focus from the URL bar.
michael@0 313 requestFocus();
michael@0 314 }
michael@0 315
michael@0 316 return super.onInterceptTouchEvent(event);
michael@0 317 }
michael@0 318
michael@0 319 public void setBanner(HomeBanner banner) {
michael@0 320 mHomeBanner = banner;
michael@0 321 }
michael@0 322
michael@0 323 @Override
michael@0 324 public boolean dispatchTouchEvent(MotionEvent event) {
michael@0 325 if (mHomeBanner != null) {
michael@0 326 mHomeBanner.handleHomeTouch(event);
michael@0 327 }
michael@0 328
michael@0 329 return super.dispatchTouchEvent(event);
michael@0 330 }
michael@0 331
michael@0 332 public void onToolbarFocusChange(boolean hasFocus) {
michael@0 333 if (mHomeBanner == null) {
michael@0 334 return;
michael@0 335 }
michael@0 336
michael@0 337 // We should only make the banner active if the toolbar is not focused and we are on the default page
michael@0 338 final boolean active = !hasFocus && getCurrentItem() == mDefaultPageIndex;
michael@0 339 mHomeBanner.setActive(active);
michael@0 340 }
michael@0 341
michael@0 342 private void updateUiFromConfigState(HomeConfig.State configState) {
michael@0 343 // We only care about the adapter if HomePager is currently
michael@0 344 // loaded, which means it's visible in the activity.
michael@0 345 if (!mVisible) {
michael@0 346 return;
michael@0 347 }
michael@0 348
michael@0 349 if (mDecor != null) {
michael@0 350 mDecor.removeAllPagerViews();
michael@0 351 }
michael@0 352
michael@0 353 final HomeAdapter adapter = (HomeAdapter) getAdapter();
michael@0 354
michael@0 355 // Disable any fragment loading until we have the initial
michael@0 356 // panel selection done. Store previous value to restore
michael@0 357 // it if necessary once the UI is fully updated.
michael@0 358 final boolean canLoadHint = adapter.getCanLoadHint();
michael@0 359 adapter.setCanLoadHint(false);
michael@0 360
michael@0 361 // Destroy any existing panels currently loaded
michael@0 362 // in the pager.
michael@0 363 setAdapter(null);
michael@0 364
michael@0 365 // Only keep enabled panels.
michael@0 366 final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
michael@0 367
michael@0 368 for (PanelConfig panelConfig : configState) {
michael@0 369 if (!panelConfig.isDisabled()) {
michael@0 370 enabledPanels.add(panelConfig);
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 // Update the adapter with the new panel configs
michael@0 375 adapter.update(enabledPanels);
michael@0 376
michael@0 377 final int count = enabledPanels.size();
michael@0 378 if (count == 0) {
michael@0 379 // Set firefox watermark as background.
michael@0 380 setBackgroundResource(R.drawable.home_pager_empty_state);
michael@0 381 // Hide the tab strip as there are no panels.
michael@0 382 mTabStrip.setVisibility(View.INVISIBLE);
michael@0 383 } else {
michael@0 384 mTabStrip.setVisibility(View.VISIBLE);
michael@0 385 // Restore original background.
michael@0 386 setBackgroundDrawable(mOriginalBackground);
michael@0 387 }
michael@0 388
michael@0 389 // Re-install the adapter with the final state
michael@0 390 // in the pager.
michael@0 391 setAdapter(adapter);
michael@0 392
michael@0 393 if (count == 0) {
michael@0 394 mDefaultPageIndex = -1;
michael@0 395
michael@0 396 // Hide the banner if there are no enabled panels.
michael@0 397 if (mHomeBanner != null) {
michael@0 398 mHomeBanner.setActive(false);
michael@0 399 }
michael@0 400 } else {
michael@0 401 for (int i = 0; i < count; i++) {
michael@0 402 if (enabledPanels.get(i).isDefault()) {
michael@0 403 mDefaultPageIndex = i;
michael@0 404 break;
michael@0 405 }
michael@0 406 }
michael@0 407
michael@0 408 // Use the default panel if the initial panel wasn't explicitly set by the
michael@0 409 // load() caller, or if the initial panel is not found in the adapter.
michael@0 410 final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
michael@0 411 if (itemPosition > -1) {
michael@0 412 setCurrentItem(itemPosition, false);
michael@0 413 mInitialPanelId = null;
michael@0 414 } else {
michael@0 415 setCurrentItem(mDefaultPageIndex, false);
michael@0 416 }
michael@0 417 }
michael@0 418
michael@0 419 // If the load hint was originally true, this means the pager
michael@0 420 // is not animating and it's fine to restore the load hint back.
michael@0 421 if (canLoadHint) {
michael@0 422 // The selection is updated asynchronously so we need to post to
michael@0 423 // UI thread to give the pager time to commit the new page selection
michael@0 424 // internally and load the right initial panel.
michael@0 425 ThreadUtils.getUiHandler().post(new Runnable() {
michael@0 426 @Override
michael@0 427 public void run() {
michael@0 428 adapter.setCanLoadHint(true);
michael@0 429 }
michael@0 430 });
michael@0 431 }
michael@0 432 }
michael@0 433
michael@0 434 public void setOnPanelChangeListener(OnPanelChangeListener listener) {
michael@0 435 mPanelChangedListener = listener;
michael@0 436 }
michael@0 437
michael@0 438 /**
michael@0 439 * Notify listeners of newly selected panel.
michael@0 440 *
michael@0 441 * @param position of the newly selected panel
michael@0 442 */
michael@0 443 private void notifyPanelSelected(int position) {
michael@0 444 if (mDecor != null) {
michael@0 445 mDecor.onPageSelected(position);
michael@0 446 }
michael@0 447
michael@0 448 if (mPanelChangedListener != null) {
michael@0 449 final String panelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
michael@0 450 mPanelChangedListener.onPanelSelected(panelId);
michael@0 451 }
michael@0 452 }
michael@0 453
michael@0 454 private class ConfigLoaderCallbacks implements LoaderCallbacks<HomeConfig.State> {
michael@0 455 @Override
michael@0 456 public Loader<HomeConfig.State> onCreateLoader(int id, Bundle args) {
michael@0 457 return new HomeConfigLoader(mContext, mConfig);
michael@0 458 }
michael@0 459
michael@0 460 @Override
michael@0 461 public void onLoadFinished(Loader<HomeConfig.State> loader, HomeConfig.State configState) {
michael@0 462 mLoadState = LoadState.LOADED;
michael@0 463 updateUiFromConfigState(configState);
michael@0 464 }
michael@0 465
michael@0 466 @Override
michael@0 467 public void onLoaderReset(Loader<HomeConfig.State> loader) {
michael@0 468 mLoadState = LoadState.UNLOADED;
michael@0 469 }
michael@0 470 }
michael@0 471
michael@0 472 private class PageChangeListener implements ViewPager.OnPageChangeListener {
michael@0 473 @Override
michael@0 474 public void onPageSelected(int position) {
michael@0 475 notifyPanelSelected(position);
michael@0 476
michael@0 477 if (mHomeBanner != null) {
michael@0 478 mHomeBanner.setActive(position == mDefaultPageIndex);
michael@0 479 }
michael@0 480
michael@0 481 // Start a UI telemetry session for the newly selected panel.
michael@0 482 final String newPanelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
michael@0 483 startNewPanelTelemetrySession(newPanelId);
michael@0 484 }
michael@0 485
michael@0 486 @Override
michael@0 487 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
michael@0 488 if (mDecor != null) {
michael@0 489 mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
michael@0 490 }
michael@0 491
michael@0 492 if (mHomeBanner != null) {
michael@0 493 mHomeBanner.setScrollingPages(positionOffsetPixels != 0);
michael@0 494 }
michael@0 495 }
michael@0 496
michael@0 497 @Override
michael@0 498 public void onPageScrollStateChanged(int state) { }
michael@0 499 }
michael@0 500
michael@0 501 /**
michael@0 502 * Start UI telemetry session for the a panel.
michael@0 503 * If there is currently a session open for a panel,
michael@0 504 * it will be stopped before a new one is started.
michael@0 505 *
michael@0 506 * @param panelId of panel to start a session for
michael@0 507 */
michael@0 508 private void startNewPanelTelemetrySession(String panelId) {
michael@0 509 // Stop the current panel's session if we have one.
michael@0 510 stopCurrentPanelTelemetrySession();
michael@0 511
michael@0 512 mCurrentPanelSession = TelemetryContract.Session.HOME_PANEL + panelId;
michael@0 513 Telemetry.startUISession(mCurrentPanelSession);
michael@0 514 }
michael@0 515
michael@0 516 /**
michael@0 517 * Stop the current panel telemetry session if one exists.
michael@0 518 */
michael@0 519 private void stopCurrentPanelTelemetrySession() {
michael@0 520 if (mCurrentPanelSession != null) {
michael@0 521 Telemetry.stopUISession(mCurrentPanelSession);
michael@0 522 mCurrentPanelSession = null;
michael@0 523 }
michael@0 524 }
michael@0 525 }

mercurial