// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.toolbar; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnClickListener; import android.widget.FrameLayout; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.NativePage; import org.chromium.chrome.browser.TabLoadStatus; import org.chromium.chrome.browser.UrlConstants; import org.chromium.chrome.browser.WindowDelegate; import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper; import org.chromium.chrome.browser.appmenu.AppMenuHandler; import org.chromium.chrome.browser.appmenu.AppMenuObserver; import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; import org.chromium.chrome.browser.bookmarks.BookmarkBridge; import org.chromium.chrome.browser.compositor.Invalidator; import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver; import org.chromium.chrome.browser.compositor.layouts.Layout; import org.chromium.chrome.browser.compositor.layouts.LayoutManager; import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver; import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.ntp.IncognitoNewTabPage; import org.chromium.chrome.browser.ntp.NativePageFactory; import org.chromium.chrome.browser.ntp.NewTabPage; import org.chromium.chrome.browser.omnibox.LocationBar; import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener; import org.chromium.chrome.browser.partnercustomizations.HomepageManager; import org.chromium.chrome.browser.partnercustomizations.HomepageManager.HomepageStateListener; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.search_engines.TemplateUrlService; import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl; import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrlServiceObserver; import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabObserver; import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; import org.chromium.chrome.browser.tabmodel.TabModelObserver; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; import org.chromium.chrome.browser.toolbar.ActionModeController.ActionBarDelegate; import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager; import org.chromium.chrome.browser.widget.findinpage.FindToolbarObserver; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.NavigationController; import org.chromium.content_public.browser.NavigationEntry; import org.chromium.content_public.browser.WebContents; import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.base.PageTransition; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Contains logic for managing the toolbar visual component. This class manages the interactions * with the rest of the application to ensure the toolbar is always visually up to date. */ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListener { /** * Handle UI updates of menu icons. Only applicable for phones. */ public interface MenuDelegatePhone { /** * Called when current tab's loading status changes. * * @param isLoading Whether the current tab is loading. */ public void updateReloadButtonState(boolean isLoading); } /** * The number of ms to wait before reporting to UMA omnibox interaction metrics. */ private static final int RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS = 30000; private static final int MIN_FOCUS_TIME_FOR_UMA_HISTOGRAM_MS = 1000; private static final int MAX_FOCUS_TIME_FOR_UMA_HISTOGRAM_MS = 30000; /** * The minimum load progress that can be shown when a page is loading. This is not 0 so that * it's obvious to the user that something is attempting to load. */ public static final int MINIMUM_LOAD_PROGRESS = 5; private final ToolbarLayout mToolbar; private final ToolbarControlContainer mControlContainer; private TabModelSelector mTabModelSelector; private TabModelSelectorObserver mTabModelSelectorObserver; private TabModelObserver mTabModelObserver; private MenuDelegatePhone mMenuDelegatePhone; private final ToolbarModelImpl mToolbarModel; private Profile mCurrentProfile; private BookmarkBridge mBookmarkBridge; private TemplateUrlServiceObserver mTemplateUrlObserver; private final LocationBar mLocationBar; private FindToolbarManager mFindToolbarManager; private final AppMenuPropertiesDelegate mAppMenuPropertiesDelegate; private final TabObserver mTabObserver; private final BookmarkBridge.BookmarkModelObserver mBookmarksObserver; private final List<FindToolbarObserver> mFindToolbarObservers; private final OverviewModeObserver mOverviewModeObserver; private final SceneChangeObserver mSceneChangeObserver; private final ActionBarDelegate mActionBarDelegate; private final ActionModeController mActionModeController; private final LoadProgressSimulator mLoadProgressSimulator; private ChromeFullscreenManager mFullscreenManager; private int mFullscreenFocusToken = FullscreenManager.INVALID_TOKEN; private int mFullscreenFindInPageToken = FullscreenManager.INVALID_TOKEN; private int mFullscreenMenuToken = FullscreenManager.INVALID_TOKEN; private int mPreselectedTabId = Tab.INVALID_TAB_ID; private boolean mNativeLibraryReady; private boolean mTabRestoreCompleted; private AppMenuButtonHelper mAppMenuButtonHelper; private HomepageStateListener mHomepageStateListener; private boolean mInitializedWithNative; private boolean mShouldUpdateTabCount = true; private boolean mShouldUpdateToolbarPrimaryColor = true; /** * Creates a ToolbarManager object. * @param controlContainer The container of the toolbar. * @param menuHandler The handler for interacting with the menu. */ public ToolbarManager(final ChromeActivity activity, ToolbarControlContainer controlContainer, final AppMenuHandler menuHandler, AppMenuPropertiesDelegate appMenuPropertiesDelegate, Invalidator invalidator) { mActionBarDelegate = new ActionModeController.ActionBarDelegate() { @Override public void setControlTopMargin(int margin) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mControlContainer.getLayoutParams(); lp.topMargin = margin; mControlContainer.setLayoutParams(lp); } @Override public int getControlTopMargin() { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mControlContainer.getLayoutParams(); return lp.topMargin; } @Override public ActionBar getSupportActionBar() { return activity.getSupportActionBar(); } @Override public void setActionBarBackgroundVisibility(boolean visible) { int visibility = visible ? View.VISIBLE : View.GONE; activity.findViewById(R.id.action_bar_black_background).setVisibility(visibility); // TODO(tedchoc): Add support for changing the color based on the brand color. } }; mToolbarModel = new ToolbarModelImpl(); mControlContainer = controlContainer; assert mControlContainer != null; mToolbar = (ToolbarLayout) controlContainer.findViewById(R.id.toolbar); mToolbar.setPaintInvalidator(invalidator); mActionModeController = new ActionModeController(activity, mActionBarDelegate); mActionModeController.setCustomSelectionActionModeCallback( new ToolbarActionModeCallback()); mActionModeController.setTabStripHeight(mToolbar.getTabStripHeight()); MenuDelegatePhone menuDelegate = new MenuDelegatePhone() { @Override public void updateReloadButtonState(boolean isLoading) { if (mAppMenuPropertiesDelegate != null) { mAppMenuPropertiesDelegate.loadingStateChanged(isLoading); menuHandler.menuItemContentChanged(R.id.icon_row_menu_id); } } }; setMenuDelegatePhone(menuDelegate); mLocationBar = mToolbar.getLocationBar(); mLocationBar.setToolbarDataProvider(mToolbarModel); mLocationBar.setUrlFocusChangeListener(this); mLocationBar.setDefaultTextEditActionModeCallback( mActionModeController.getActionModeCallback()); mLocationBar.initializeControls( new WindowDelegate(activity.getWindow()), mActionModeController.getActionBarDelegate(), activity.getWindowAndroid()); setMenuHandler(menuHandler); mToolbar.initialize(mToolbarModel, this, mAppMenuButtonHelper); mAppMenuPropertiesDelegate = appMenuPropertiesDelegate; mHomepageStateListener = new HomepageStateListener() { @Override public void onHomepageStateUpdated() { mToolbar.onHomeButtonUpdate( HomepageManager.isHomepageEnabled(mToolbar.getContext())); } }; HomepageManager.getInstance(mToolbar.getContext()).addListener(mHomepageStateListener); mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() { @Override public void onTabModelSelected(TabModel newModel, TabModel oldModel) { refreshSelectedTab(); updateTabCount(); } @Override public void onTabStateInitialized() { mTabRestoreCompleted = true; handleTabRestoreCompleted(); } }; mTabModelObserver = new EmptyTabModelObserver() { @Override public void didAddTab(Tab tab, TabLaunchType type) { updateTabCount(); } @Override public void didSelectTab(Tab tab, TabSelectionType type, int lastId) { mPreselectedTabId = Tab.INVALID_TAB_ID; refreshSelectedTab(); } @Override public void tabClosureUndone(Tab tab) { updateTabCount(); refreshSelectedTab(); } @Override public void didCloseTab(int tabId, boolean incognito) { mLocationBar.setTitleToPageTitle(); updateTabCount(); refreshSelectedTab(); } @Override public void tabPendingClosure(Tab tab) { updateTabCount(); refreshSelectedTab(); } @Override public void allTabsPendingClosure(List<Integer> tabIds) { updateTabCount(); refreshSelectedTab(); } @Override public void tabRemoved(Tab tab) { updateTabCount(); refreshSelectedTab(); } }; mTabObserver = new EmptyTabObserver() { @Override public void onSSLStateUpdated(Tab tab) { super.onSSLStateUpdated(tab); assert tab == mToolbarModel.getTab(); mLocationBar.updateSecurityIcon(tab.getSecurityLevel()); } @Override public void onWebContentsInstantSupportDisabled() { mLocationBar.setUrlToPageUrl(); } @Override public void onDidNavigateMainFrame(Tab tab, String url, String baseUrl, boolean isNavigationToDifferentPage, boolean isFragmentNavigation, int statusCode) { if (isNavigationToDifferentPage) { mToolbar.onNavigatedToDifferentPage(); } } @Override public void onTitleUpdated(Tab tab) { mLocationBar.setTitleToPageTitle(); } @Override public void onUrlUpdated(Tab tab) { // Update the SSL security state as a result of this notification as it will // sometimes be the only update we receive. updateTabLoadingState(true); // A URL update is a decent enough indicator that the toolbar widget is in // a stable state to capture its bitmap for use in fullscreen. mControlContainer.setReadyForBitmapCapture(true); } @Override public void onCrash(Tab tab, boolean sadTabShown) { updateTabLoadingState(false); updateButtonStatus(); finishLoadProgress(false); } @Override public void onLoadStarted(Tab tab, boolean toDifferentDocument) { if (!toDifferentDocument) return; updateButtonStatus(); updateTabLoadingState(true); } @Override public void onLoadStopped(Tab tab, boolean toDifferentDocument) { if (!toDifferentDocument) return; updateTabLoadingState(true); // If we made some progress, fast-forward to complete, otherwise just dismiss any // MINIMUM_LOAD_PROGRESS that had been set. if (tab.getProgress() > MINIMUM_LOAD_PROGRESS && tab.getProgress() < 100) { updateLoadProgress(100); } finishLoadProgress(true); } @Override public void onLoadProgressChanged(Tab tab, int progress) { if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) return; // TODO(kkimlabs): Investigate using float progress all the way up to Blink. updateLoadProgress(progress); } @Override public void onContentChanged(Tab tab) { mToolbar.onTabContentViewChanged(); if (shouldShowCusrsorInLocationBar()) { mToolbar.getLocationBar().showUrlBarCursorWithoutFocusAnimations(); } } @Override public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) { if (!didStartLoad) return; mLocationBar.setUrlToPageUrl(); mLocationBar.updateSecurityIcon(tab.getSecurityLevel()); if (didFinishLoad) { mLoadProgressSimulator.start(); } } @Override public void onDidStartNavigationToPendingEntry(Tab tab, String url) { // Update URL as soon as it becomes available when it's a new tab. // But we want to update only when it's a new tab. So we check whether the current // navigation entry is initial, meaning whether it has the same target URL as the // initial URL of the tab. WebContents webContents = tab.getWebContents(); if (webContents == null) return; NavigationController navigationController = webContents.getNavigationController(); if (navigationController == null) return; if (navigationController.isInitialNavigation()) { mLocationBar.setUrlToPageUrl(); } } @Override public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) { NewTabPage ntp = mToolbarModel.getNewTabPageForCurrentTab(); if (ntp == null) return; if (!NewTabPage.isNTPUrl(params.getUrl()) && loadType != TabLoadStatus.PAGE_LOAD_FAILED) { ntp.setUrlFocusAnimationsDisabled(true); mToolbar.onTabOrModelChanged(); } } private boolean hasPendingNonNtpNavigation(Tab tab) { WebContents webContents = tab.getWebContents(); if (webContents == null) return false; NavigationController navigationController = webContents.getNavigationController(); if (navigationController == null) return false; NavigationEntry pendingEntry = navigationController.getPendingEntry(); if (pendingEntry == null) return false; return !NewTabPage.isNTPUrl(pendingEntry.getUrl()); } @Override public void onDidFailLoad(Tab tab, boolean isProvisionalLoad, boolean isMainFrame, int errorCode, String description, String failingUrl) { NewTabPage ntp = mToolbarModel.getNewTabPageForCurrentTab(); if (ntp == null) return; // If the load failed due to a different navigation, there is no need to reset the // location bar animations. if (isProvisionalLoad && isMainFrame && !hasPendingNonNtpNavigation(tab)) { ntp.setUrlFocusAnimationsDisabled(false); mToolbar.onTabOrModelChanged(); if (mToolbar.getProgressBar() != null) mToolbar.getProgressBar().finish(false); } } @Override public void onContextualActionBarVisibilityChanged(Tab tab, boolean visible) { if (visible) RecordUserAction.record("MobileActionBarShown"); ActionBar actionBar = mActionBarDelegate.getSupportActionBar(); if (!visible && actionBar != null) actionBar.hide(); if (DeviceFormFactor.isTablet(activity)) { if (visible) { mActionModeController.startShowAnimation(); } else { mActionModeController.startHideAnimation(); } } } @Override public void onDidStartProvisionalLoadForFrame(Tab tab, long frameId, long parentFrameId, boolean isMainFrame, String validatedUrl, boolean isErrorPage, boolean isIframeSrcdoc) { // This event is used as the primary trigger for the progress bar because it // is the earliest indication that a load has started for a particular frame. In // the case of the progress bar, it should only traverse the screen a single time // per page load. So if this event states the main frame has started loading the // progress bar is started. if (!isMainFrame) return; if (NativePageFactory.isNativePageUrl(validatedUrl, tab.isIncognito())) { finishLoadProgress(false); return; } mLoadProgressSimulator.cancel(); startLoadProgress(); updateLoadProgress(tab.getProgress()); } }; mBookmarksObserver = new BookmarkBridge.BookmarkModelObserver() { @Override public void bookmarkModelChanged() { updateBookmarkButtonStatus(); } }; mFindToolbarObservers = new ArrayList<>(); addFindToolbarObserver( new FindToolbarObserver() { @Override public void onFindToolbarShown() { mToolbar.handleFindToolbarStateChange(true); if (mFullscreenManager != null) { mFullscreenFindInPageToken = mFullscreenManager.showControlsPersistentAndClearOldToken( mFullscreenFindInPageToken); } } @Override public void onFindToolbarHidden() { mToolbar.handleFindToolbarStateChange(false); if (mFullscreenManager != null) { mFullscreenManager.hideControlsPersistent(mFullscreenFindInPageToken); mFullscreenFindInPageToken = FullscreenManager.INVALID_TOKEN; } } }); mOverviewModeObserver = new EmptyOverviewModeObserver() { @Override public void onOverviewModeStartedShowing(boolean showToolbar) { mToolbar.setTabSwitcherMode(true, showToolbar, false); updateButtonStatus(); } @Override public void onOverviewModeStartedHiding(boolean showToolbar, boolean delayAnimation) { mToolbar.setTabSwitcherMode(false, showToolbar, delayAnimation); updateButtonStatus(); } @Override public void onOverviewModeFinishedHiding() { mToolbar.onTabSwitcherTransitionFinished(); } }; mSceneChangeObserver = new SceneChangeObserver() { @Override public void onTabSelectionHinted(int tabId) { mPreselectedTabId = tabId; refreshSelectedTab(); if (mToolbar.setForceTextureCapture(true)) { mControlContainer.invalidateBitmap(); } } @Override public void onSceneChange(Layout layout) { mToolbar.setContentAttached(layout.shouldDisplayContentOverlay()); } }; mLoadProgressSimulator = new LoadProgressSimulator(this); } /** * Add an observer to the FindToolbarManager. * @param observer The observer to add. */ public void addFindToolbarObserver(FindToolbarObserver observer) { // Keep a list of all added observers in case the manager does not exist yet. mFindToolbarObservers.add(observer); if (mFindToolbarManager != null) { mFindToolbarManager.addObserver(observer); } } /** * Remove an observer from the FindToolbarManager. * @param observer The observer to remove. */ public void removeFindToolbarObserver(FindToolbarObserver observer) { mFindToolbarObservers.remove(observer); if (mFindToolbarManager != null) { mFindToolbarManager.removeObserver(observer); } } /** * Initialize the manager with the components that had native initialization dependencies. * <p> * Calling this must occur after the native library have completely loaded. * * @param tabModelSelector The selector that handles tab management. * @param fullscreenManager The manager in charge of interacting with the fullscreen feature. * @param findToolbarManager The manager for find in page. * @param overviewModeBehavior The overview mode manager. * @param layoutDriver A {@link LayoutManager} instance used to watch for scene changes. */ public void initializeWithNative(TabModelSelector tabModelSelector, ChromeFullscreenManager fullscreenManager, final FindToolbarManager findToolbarManager, final OverviewModeBehavior overviewModeBehavior, final LayoutManager layoutDriver, OnClickListener tabSwitcherClickHandler, OnClickListener newTabClickHandler, OnClickListener bookmarkClickHandler, OnClickListener customTabsBackClickHandler) { assert !mInitializedWithNative; mTabModelSelector = tabModelSelector; mToolbar.getLocationBar().updateVisualsForState(); mToolbar.getLocationBar().setUrlToPageUrl(); mToolbar.setFullscreenManager(fullscreenManager); mToolbar.setOnTabSwitcherClickHandler(tabSwitcherClickHandler); mToolbar.setOnNewTabClickHandler(newTabClickHandler); mToolbar.setBookmarkClickHandler(bookmarkClickHandler); mToolbar.setCustomTabCloseClickHandler(customTabsBackClickHandler); mToolbar.setLayoutUpdateHost(layoutDriver); mToolbarModel.initializeWithNative(); mToolbar.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { @Override public void onViewDetachedFromWindow(View v) { Context context = mToolbar.getContext(); HomepageManager.getInstance(context).removeListener(mHomepageStateListener); mTabModelSelector.removeObserver(mTabModelSelectorObserver); for (TabModel model : mTabModelSelector.getModels()) { model.removeObserver(mTabModelObserver); } if (mBookmarkBridge != null) { mBookmarkBridge.destroy(); mBookmarkBridge = null; } if (mTemplateUrlObserver != null) { TemplateUrlService.getInstance().removeObserver(mTemplateUrlObserver); mTemplateUrlObserver = null; } // Remove all previously attached observers from the FindToolbarManager. if (mFindToolbarManager != null) { for (FindToolbarObserver observer : mFindToolbarObservers) { mFindToolbarManager.removeObserver(observer); } } if (overviewModeBehavior != null) { overviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver); } if (layoutDriver != null) { layoutDriver.removeSceneChangeObserver(mSceneChangeObserver); } } @Override public void onViewAttachedToWindow(View v) { // As we have only just registered for notifications, any that were sent prior to // this may have been missed. // Calling refreshSelectedTab in case we missed the initial selection notification. refreshSelectedTab(); } }); mFindToolbarManager = findToolbarManager; assert fullscreenManager != null; mFullscreenManager = fullscreenManager; mNativeLibraryReady = false; // Add observers to the FindToolbarManager. if (mFindToolbarManager != null) { for (FindToolbarObserver observer : mFindToolbarObservers) { mFindToolbarManager.addObserver(observer); } } if (overviewModeBehavior != null) { overviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver); } if (layoutDriver != null) layoutDriver.addSceneChangeObserver(mSceneChangeObserver); onNativeLibraryReady(); mInitializedWithNative = true; } /** * @return The bookmarks bridge. */ public BookmarkBridge getBookmarkBridge() { return mBookmarkBridge; } /** * @return The toolbar interface that this manager handles. */ public Toolbar getToolbar() { return mToolbar; } /** * @return The controller for toolbar action mode. */ public ActionModeController getActionModeController() { return mActionModeController; } /** * @return Whether the UI has been initialized. */ public boolean isInitialized() { return mInitializedWithNative; } /** * @return The view that the pop up menu should be anchored to on the UI. */ public View getMenuAnchor() { return mToolbar.getMenuButtonWrapper(); } /** * Sets/adds a custom action button to the {@link Toolbar} if it is supported. If there is * already an action button, update the button instead. * @param drawable The {@link Drawable} to use as the background for the button. * @param description The content description for the custom action button. * @param listener The {@link OnClickListener} to use for clicks to the button. */ public void setCustomActionButton(Drawable drawable, String description, OnClickListener listener) { mToolbar.setCustomActionButton(drawable, description, listener); } /** * Call to tear down all of the toolbar dependencies. */ public void destroy() { Tab currentTab = mToolbarModel.getTab(); if (currentTab != null) currentTab.removeObserver(mTabObserver); mFindToolbarObservers.clear(); mToolbar.destroy(); } /** * Called when the orientation of the activity has changed. */ public void onOrientationChange() { mActionModeController.showControlsOnOrientationChange(); } /** * Called when the accessibility enabled state changes. * @param enabled Whether accessibility is enabled. */ public void onAccessibilityStatusChanged(boolean enabled) { mToolbar.onAccessibilityStatusChanged(enabled); } private void registerTemplateUrlObserver() { final TemplateUrlService templateUrlService = TemplateUrlService.getInstance(); assert mTemplateUrlObserver == null; mTemplateUrlObserver = new TemplateUrlServiceObserver() { private TemplateUrl mSearchEngine = templateUrlService.getDefaultSearchEngineTemplateUrl(); @Override public void onTemplateURLServiceChanged() { TemplateUrl searchEngine = templateUrlService.getDefaultSearchEngineTemplateUrl(); if ((mSearchEngine == null && searchEngine == null) || (mSearchEngine != null && mSearchEngine.equals(searchEngine))) { return; } mSearchEngine = searchEngine; mToolbar.onDefaultSearchEngineChanged(); } }; templateUrlService.addObserver(mTemplateUrlObserver); } private void onNativeLibraryReady() { mNativeLibraryReady = true; mToolbar.onNativeLibraryReady(); final TemplateUrlService templateUrlService = TemplateUrlService.getInstance(); TemplateUrlService.LoadListener mTemplateServiceLoadListener = new TemplateUrlService.LoadListener() { @Override public void onTemplateUrlServiceLoaded() { registerTemplateUrlObserver(); templateUrlService.unregisterLoadListener(this); } }; templateUrlService.registerLoadListener(mTemplateServiceLoadListener); if (templateUrlService.isLoaded()) { mTemplateServiceLoadListener.onTemplateUrlServiceLoaded(); } else { templateUrlService.load(); } mTabModelSelector.addObserver(mTabModelSelectorObserver); for (TabModel model : mTabModelSelector.getModels()) model.addObserver(mTabModelObserver); refreshSelectedTab(); if (mTabModelSelector.isTabStateInitialized()) mTabRestoreCompleted = true; handleTabRestoreCompleted(); } private void handleTabRestoreCompleted() { if (!mTabRestoreCompleted || !mNativeLibraryReady) return; mToolbar.onStateRestored(); updateTabCount(); } /** * Sets the handler for any special case handling related with the menu button. * @param menuHandler The handler to be used. */ private void setMenuHandler(AppMenuHandler menuHandler) { menuHandler.addObserver(new AppMenuObserver() { @Override public void onMenuVisibilityChanged(boolean isVisible) { if (mFullscreenManager == null) return; if (isVisible) { mFullscreenMenuToken = mFullscreenManager.showControlsPersistentAndClearOldToken( mFullscreenMenuToken); } else { mFullscreenManager.hideControlsPersistent(mFullscreenMenuToken); mFullscreenMenuToken = FullscreenManager.INVALID_TOKEN; } } }); mAppMenuButtonHelper = new AppMenuButtonHelper(menuHandler); mAppMenuButtonHelper.setOnAppMenuShownListener(new Runnable() { @Override public void run() { RecordUserAction.record("MobileToolbarShowMenu"); } }); mLocationBar.setMenuButtonHelper(mAppMenuButtonHelper); } /** * Set the delegate that will handle updates from toolbar driven state changes. * @param menuDelegatePhone The menu delegate to be updated (only applicable to phones). */ public void setMenuDelegatePhone(MenuDelegatePhone menuDelegatePhone) { mMenuDelegatePhone = menuDelegatePhone; } @Override public boolean back() { Tab tab = mToolbarModel.getTab(); if (tab != null && tab.canGoBack()) { tab.goBack(); updateButtonStatus(); return true; } return false; } @Override public boolean forward() { Tab tab = mToolbarModel.getTab(); if (tab != null && tab.canGoForward()) { tab.goForward(); updateButtonStatus(); return true; } return false; } @Override public void stopOrReloadCurrentTab() { Tab currentTab = mToolbarModel.getTab(); if (currentTab != null) { if (currentTab.isLoading()) { currentTab.stopLoading(); RecordUserAction.record("MobileToolbarStop"); } else { currentTab.reload(); RecordUserAction.record("MobileToolbarReload"); } } updateButtonStatus(); } @Override public void openHomepage() { RecordUserAction.record("Home"); Tab currentTab = mToolbarModel.getTab(); if (currentTab == null) return; Context context = mToolbar.getContext(); String homePageUrl = HomepageManager.getHomepageUri(context); if (TextUtils.isEmpty(homePageUrl)) { homePageUrl = UrlConstants.NTP_URL; } currentTab.loadUrl(new LoadUrlParams(homePageUrl, PageTransition.HOME_PAGE)); } /** * Triggered when the URL input field has gained or lost focus. * @param hasFocus Whether the URL field has gained focus. */ @Override public void onUrlFocusChange(boolean hasFocus) { mToolbar.onUrlFocusChange(hasFocus); if (mFindToolbarManager != null && hasFocus) mFindToolbarManager.hideToolbar(); if (mFullscreenManager == null) return; if (hasFocus) { mFullscreenFocusToken = mFullscreenManager.showControlsPersistentAndClearOldToken( mFullscreenFocusToken); } else { mFullscreenManager.hideControlsPersistent(mFullscreenFocusToken); mFullscreenFocusToken = FullscreenManager.INVALID_TOKEN; } } /** * Updates the primary color used by the model to the given color. * @param color The primary color for the current tab. * @param shouldAnimate Whether the change of color should be animated. */ public void updatePrimaryColor(int color, boolean shouldAnimate) { if (!mShouldUpdateToolbarPrimaryColor) return; boolean colorChanged = mToolbarModel.getPrimaryColor() != color; if (!colorChanged) return; mToolbarModel.setPrimaryColor(color); mToolbar.onPrimaryColorChanged(shouldAnimate); } /** * @param shouldUpdate Whether we should be updating the toolbar primary color based on updates * from the Tab. */ public void setShouldUpdateToolbarPrimaryColor(boolean shouldUpdate) { mShouldUpdateToolbarPrimaryColor = shouldUpdate; } /** * Sets the drawable that the close button shows. */ public void setCloseButtonDrawable(Drawable drawable) { mToolbar.setCloseButtonImageResource(drawable); } /** * Sets whether a title should be shown within the Toolbar. * @param showTitle Whether a title should be shown. */ public void setShowTitle(boolean showTitle) { mLocationBar.setShowTitle(showTitle); } /** * @see ToolbarLayout#setUrlBarHidden(boolean) */ public void setUrlBarHidden(boolean hidden) { mToolbar.setUrlBarHidden(hidden); } /** * @see ToolbarLayout#getContentPublisher() */ public String getContentPublisher() { return mToolbar.getContentPublisher(); } /** * Focuses or unfocuses the URL bar. * * If you request focus and the UrlBar was already focused, this will select all of the text. * * @param focused Whether URL bar should be focused. */ public void setUrlBarFocus(boolean focused) { if (!isInitialized()) return; boolean wasFocused = mToolbar.getLocationBar().isUrlBarFocused(); mToolbar.getLocationBar().setUrlBarFocus(focused); if (wasFocused && focused) { mToolbar.getLocationBar().selectAll(); } } /** * Reverts any pending edits of the location bar and reset to the page state. This does not * change the focus state of the location bar. */ public void revertLocationBarChanges() { mLocationBar.revertChanges(); } /** * Handle all necessary tasks that can be delayed until initialization completes. * @param activityCreationTimeMs The time of creation for the activity this toolbar belongs to. * @param activityName Simple class name for the activity this toolbar belongs to. */ public void onDeferredStartup(final long activityCreationTimeMs, final String activityName) { // Record startup performance statistics long elapsedTime = SystemClock.elapsedRealtime() - activityCreationTimeMs; if (elapsedTime < RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS) { ThreadUtils.postOnUiThreadDelayed(new Runnable() { @Override public void run() { onDeferredStartup(activityCreationTimeMs, activityName); } }, RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime); } RecordHistogram.recordTimesHistogram("MobileStartup.ToolbarFirstDrawTime." + activityName, mToolbar.getFirstDrawTime() - activityCreationTimeMs, TimeUnit.MILLISECONDS); long firstFocusTime = mToolbar.getLocationBar().getFirstUrlBarFocusTime(); if (firstFocusTime != 0) { RecordHistogram.recordCustomTimesHistogram( "MobileStartup.ToolbarFirstFocusTime." + activityName, firstFocusTime - activityCreationTimeMs, MIN_FOCUS_TIME_FOR_UMA_HISTOGRAM_MS, MAX_FOCUS_TIME_FOR_UMA_HISTOGRAM_MS, TimeUnit.MILLISECONDS, 50); } } /** * Finish any toolbar animations. */ public void finishAnimations() { if (isInitialized()) mToolbar.finishAnimations(); } /** * Updates the current number of Tabs based on the TabModel this Toolbar contains. */ private void updateTabCount() { if (!mTabRestoreCompleted || !mShouldUpdateTabCount) return; mToolbar.updateTabCountVisuals(mTabModelSelector.getCurrentModel().getCount()); } /** * Updates the current button states and calls appropriate abstract visibility methods, giving * inheriting classes the chance to update the button visuals as well. */ private void updateButtonStatus() { Tab currentTab = mToolbarModel.getTab(); boolean tabCrashed = currentTab != null && currentTab.isShowingSadTab(); mToolbar.updateButtonVisibility(); mToolbar.updateBackButtonVisibility(currentTab != null && currentTab.canGoBack()); mToolbar.updateForwardButtonVisibility(currentTab != null && currentTab.canGoForward()); updateReloadState(tabCrashed); updateBookmarkButtonStatus(); mToolbar.getMenuButtonWrapper().setVisibility(View.VISIBLE); } private void updateBookmarkButtonStatus() { Tab currentTab = mToolbarModel.getTab(); boolean isBookmarked = currentTab != null && currentTab.getBookmarkId() != Tab.INVALID_BOOKMARK_ID; boolean editingAllowed = currentTab == null || mBookmarkBridge == null || mBookmarkBridge.isEditBookmarksEnabled(); mToolbar.updateBookmarkButton(isBookmarked, editingAllowed); } private void updateReloadState(boolean tabCrashed) { Tab currentTab = mToolbarModel.getTab(); boolean isLoading = false; if (!tabCrashed) { isLoading = (currentTab != null && currentTab.isLoading()) || !mNativeLibraryReady; } mToolbar.updateReloadButtonVisibility(isLoading); if (mMenuDelegatePhone != null) mMenuDelegatePhone.updateReloadButtonState(isLoading); } /** * Triggered when the selected tab has changed. */ private void refreshSelectedTab() { Tab tab = null; if (mPreselectedTabId != Tab.INVALID_TAB_ID) { tab = mTabModelSelector.getTabById(mPreselectedTabId); } if (tab == null) tab = mTabModelSelector.getCurrentTab(); boolean wasIncognito = mToolbarModel.isIncognito(); Tab previousTab = mToolbarModel.getTab(); boolean isIncognito = tab != null ? tab.isIncognito() : mTabModelSelector.isIncognitoSelected(); mToolbarModel.setTab(tab, isIncognito); updateCurrentTabDisplayStatus(); if (previousTab != tab || wasIncognito != isIncognito) { if (previousTab != tab) { if (previousTab != null) { previousTab.removeObserver(mTabObserver); previousTab.setIsAllowedToReturnToExternalApp(false); } if (tab != null) tab.addObserver(mTabObserver); } int defaultPrimaryColor = isIncognito ? ApiCompatibilityUtils.getColor(mToolbar.getResources(), R.color.incognito_primary_color) : ApiCompatibilityUtils.getColor(mToolbar.getResources(), R.color.default_primary_color); int primaryColor = tab != null ? tab.getThemeColor() : defaultPrimaryColor; updatePrimaryColor(primaryColor, false); mToolbar.onTabOrModelChanged(); if (tab != null && tab.getWebContents() != null && tab.getWebContents().isLoadingToDifferentDocument()) { mToolbar.onNavigatedToDifferentPage(); } // Ensure the URL bar loses focus if the tab it was interacting with is changed from // underneath it. setUrlBarFocus(false); // Place the cursor in the Omnibox if applicable. We always clear the focus above to // ensure the shield placed over the content is dismissed when switching tabs. But if // needed, we will refocus the omnibox and make the cursor visible here. if (shouldShowCusrsorInLocationBar()) { mToolbar.getLocationBar().showUrlBarCursorWithoutFocusAnimations(); } } Profile profile = mTabModelSelector.getModel(isIncognito).getProfile(); if (mCurrentProfile != profile) { if (mBookmarkBridge != null) mBookmarkBridge.destroy(); mBookmarkBridge = new BookmarkBridge(profile); mBookmarkBridge.addObserver(mBookmarksObserver); mAppMenuPropertiesDelegate.setBookmarkBridge(mBookmarkBridge); mLocationBar.setAutocompleteProfile(profile); mCurrentProfile = profile; } updateButtonStatus(); } private void updateCurrentTabDisplayStatus() { Tab tab = mToolbarModel.getTab(); mLocationBar.setUrlToPageUrl(); updateTabLoadingState(true); if (tab == null) { finishLoadProgress(false); return; } mLoadProgressSimulator.cancel(); if (tab.isLoading()) { if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) { finishLoadProgress(false); } else { startLoadProgress(); updateLoadProgress(tab.getProgress()); } } else { finishLoadProgress(false); } } private void updateTabLoadingState(boolean updateUrl) { mLocationBar.updateLoadingState(updateUrl); if (updateUrl) updateButtonStatus(); } private void updateLoadProgress(int progress) { // If it's a native page, progress bar is already hidden or being hidden, so don't update // the value. // TODO(kkimlabs): Investigate back/forward navigation with native page & web content and // figure out the correct progress bar presentation. Tab tab = mToolbarModel.getTab(); if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) return; progress = Math.max(progress, MINIMUM_LOAD_PROGRESS); mToolbar.setLoadProgress(progress / 100f); if (progress == 100) finishLoadProgress(true); } private void finishLoadProgress(boolean delayed) { mLoadProgressSimulator.cancel(); mToolbar.finishLoadProgress(delayed); } /** * Only start showing the progress bar if it is not already started. */ private void startLoadProgress() { if (mToolbar.isProgressStarted()) return; mToolbar.startLoadProgress(); } private boolean shouldShowCusrsorInLocationBar() { Tab tab = mToolbarModel.getTab(); if (tab == null) return false; NativePage nativePage = tab.getNativePage(); if (!(nativePage instanceof NewTabPage) && !(nativePage instanceof IncognitoNewTabPage)) { return false; } Context context = mToolbar.getContext(); return DeviceFormFactor.isTablet(context) && context.getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY; } private static class LoadProgressSimulator { private static final int MSG_ID_UPDATE_PROGRESS = 1; private static final int PROGRESS_INCREMENT = 10; private static final int PROGRESS_INCREMENT_DELAY_MS = 10; private final ToolbarManager mToolbarManager; private final Handler mHandler; private int mProgress; public LoadProgressSimulator(ToolbarManager toolbar) { mToolbarManager = toolbar; mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { assert msg.what == MSG_ID_UPDATE_PROGRESS; mProgress = Math.min(100, mProgress += PROGRESS_INCREMENT); mToolbarManager.updateLoadProgress(mProgress); if (mProgress == 100) { mToolbarManager.mToolbar.finishLoadProgress(true); return; } sendEmptyMessageDelayed(MSG_ID_UPDATE_PROGRESS, PROGRESS_INCREMENT_DELAY_MS); } }; } /** * Start simulating load progress from a baseline of 0. */ public void start() { mProgress = 0; mToolbarManager.mToolbar.startLoadProgress(); mToolbarManager.updateLoadProgress(mProgress); mHandler.sendEmptyMessage(MSG_ID_UPDATE_PROGRESS); } /** * Cancels simulating load progress. */ public void cancel() { mHandler.removeMessages(MSG_ID_UPDATE_PROGRESS); } } }