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

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

mercurial