/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.tabs; import android.support.v4.content.ContextCompat; import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoApplication; import org.mozilla.gecko.R; import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.lwt.LightweightTheme; import org.mozilla.gecko.lwt.LightweightThemeDrawable; import org.mozilla.gecko.restrictions.Restrictable; import org.mozilla.gecko.restrictions.Restrictions; import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.widget.GeckoPopupMenu; import org.mozilla.gecko.widget.IconTabWidget; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import org.mozilla.gecko.widget.themed.ThemedImageButton; public class TabsPanel extends LinearLayout implements GeckoPopupMenu.OnMenuItemClickListener, LightweightTheme.OnChangeListener, IconTabWidget.OnTabChangedListener { private static final String LOGTAG = "Gecko" + TabsPanel.class.getSimpleName(); public enum Panel { NORMAL_TABS, PRIVATE_TABS, } public interface PanelView { void setTabsPanel(TabsPanel panel); void show(); void hide(); boolean shouldExpand(); } public interface CloseAllPanelView extends PanelView { void closeAll(); } public interface TabsLayout extends CloseAllPanelView { void setEmptyView(View view); } public interface TabsLayoutChangeListener { void onTabsLayoutChange(int width, int height); } public static View createTabsLayout(final Context context, final AttributeSet attrs) { final boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; if (HardwareUtils.isTablet() || isLandscape) { return new TabsGridLayout(context, attrs); } else { return new TabsListLayout(context, attrs); } } private final Context mContext; private final GeckoApp mActivity; private final LightweightTheme mTheme; private RelativeLayout mHeader; private FrameLayout mTabsContainer; private PanelView mPanel; private PanelView mPanelNormal; private PanelView mPanelPrivate; private TabsLayoutChangeListener mLayoutChangeListener; private IconTabWidget mTabWidget; private View mMenuButton; private ImageButton mAddTab; private ImageButton mNavBackButton; private Panel mCurrentPanel; private boolean mVisible; private boolean mHeaderVisible; private final GeckoPopupMenu mPopupMenu; public TabsPanel(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mActivity = (GeckoApp) context; mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); mCurrentPanel = Panel.NORMAL_TABS; mPopupMenu = new GeckoPopupMenu(context); mPopupMenu.inflate(R.menu.tabs_menu); mPopupMenu.setOnMenuItemClickListener(this); inflateLayout(context); initialize(); } private void inflateLayout(Context context) { LayoutInflater.from(context).inflate(R.layout.tabs_panel_default, this); } private void initialize() { mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header); mTabsContainer = (FrameLayout) findViewById(R.id.tabs_container); mPanelNormal = (PanelView) findViewById(R.id.normal_tabs); mPanelNormal.setTabsPanel(this); mPanelPrivate = (PanelView) findViewById(R.id.private_tabs_panel); mPanelPrivate.setTabsPanel(this); mAddTab = (ImageButton) findViewById(R.id.add_tab); mAddTab.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { TabsPanel.this.addTab(); } }); mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget); mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal); final ThemedImageButton privateTabsPanel = (ThemedImageButton) mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private); privateTabsPanel.setPrivateMode(true); if (!Restrictions.isAllowed(mContext, Restrictable.PRIVATE_BROWSING)) { mTabWidget.setVisibility(View.GONE); } mTabWidget.setTabSelectionListener(this); mMenuButton = findViewById(R.id.menu); mMenuButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View view) { showMenu(); } }); mNavBackButton = (ImageButton) findViewById(R.id.nav_back); mNavBackButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View view) { mActivity.onBackPressed(); } }); } public void showMenu() { final Menu menu = mPopupMenu.getMenu(); // Each panel has a "+" shortcut button, so don't show it for that panel. menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS); menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS && Restrictions.isAllowed(mContext, Restrictable.PRIVATE_BROWSING)); // Only show "Clear * tabs" for current panel. menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS); menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS); mPopupMenu.show(); } private void addTab() { Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab"); if (mCurrentPanel == Panel.NORMAL_TABS) { mActivity.addTab(); } else { mActivity.addPrivateTab(); } mActivity.autoHideTabs(); } @Override public void onTabChanged(int index) { if (index == 0) { show(Panel.NORMAL_TABS); } else { show(Panel.PRIVATE_TABS); } } @Override public boolean onMenuItemClick(MenuItem item) { final int itemId = item.getItemId(); if (itemId == R.id.close_all_tabs) { if (mCurrentPanel == Panel.NORMAL_TABS) { Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "close_all_tabs"); // Disable the menu button so that the menu won't interfere with the tab close animation. mMenuButton.setEnabled(false); ((CloseAllPanelView) mPanelNormal).closeAll(); } else { Log.e(LOGTAG, "Close all tabs menu item should only be visible for normal tabs panel"); } return true; } if (itemId == R.id.close_private_tabs) { if (mCurrentPanel == Panel.PRIVATE_TABS) { // Mask private browsing Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "close_all_tabs"); ((CloseAllPanelView) mPanelPrivate).closeAll(); } else { Log.e(LOGTAG, "Close private tabs menu item should only be visible for private tabs panel"); } return true; } if (itemId == R.id.new_tab || itemId == R.id.new_private_tab) { hide(); } return mActivity.onOptionsItemSelected(item); } private static int getTabContainerHeight(FrameLayout tabsContainer) { final Resources resources = tabsContainer.getContext().getResources(); final int screenHeight = resources.getDisplayMetrics().heightPixels; final int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); return screenHeight - actionBarHeight; } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mTheme.addListener(this); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); mTheme.removeListener(this); } @Override @SuppressWarnings("deprecation") // setBackgroundDrawable deprecated by API level 16 public void onLightweightThemeChanged() { final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey); final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background, true); if (drawable == null) return; drawable.setAlpha(34, 0); setBackgroundDrawable(drawable); } @Override public void onLightweightThemeReset() { setBackgroundColor(ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); onLightweightThemeChanged(); } // Tabs Panel Toolbar contains the Buttons static class TabsPanelToolbar extends LinearLayout implements LightweightTheme.OnChangeListener { private final LightweightTheme mTheme; public TabsPanelToolbar(Context context, AttributeSet attrs) { super(context, attrs); mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mTheme.addListener(this); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); mTheme.removeListener(this); } @Override @SuppressWarnings("deprecation") // setBackgroundDrawable deprecated by API level 16 public void onLightweightThemeChanged() { final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey); final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background); if (drawable == null) return; drawable.setAlpha(34, 34); setBackgroundDrawable(drawable); } @Override public void onLightweightThemeReset() { setBackgroundColor(ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); onLightweightThemeChanged(); } } public void show(Panel panelToShow) { prepareToShow(panelToShow); int height = getVerticalPanelHeight(); dispatchLayoutChange(getWidth(), height); mHeaderVisible = true; } public void prepareToShow(Panel panelToShow) { if (!isShown()) { setVisibility(View.VISIBLE); } if (mPanel != null) { // Hide the old panel. mPanel.hide(); } mVisible = true; mCurrentPanel = panelToShow; int index = panelToShow.ordinal(); mTabWidget.setCurrentTab(index); switch (panelToShow) { case NORMAL_TABS: mPanel = mPanelNormal; break; case PRIVATE_TABS: mPanel = mPanelPrivate; break; default: throw new IllegalArgumentException("Unknown panel type " + panelToShow); } mPanel.show(); mAddTab.setVisibility(View.VISIBLE); mMenuButton.setEnabled(true); mPopupMenu.setAnchor(mMenuButton); } public int getVerticalPanelHeight() { final int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height); final int height = actionBarHeight + getTabContainerHeight(mTabsContainer); return height; } public void hide() { mHeaderVisible = false; if (mVisible) { mVisible = false; mPopupMenu.dismiss(); dispatchLayoutChange(0, 0); } } public void refresh() { removeAllViews(); inflateLayout(mContext); initialize(); if (mVisible) show(mCurrentPanel); } public void autoHidePanel() { mActivity.autoHideTabs(); } @Override public boolean isShown() { return mVisible; } public void setHWLayerEnabled(boolean enabled) { if (enabled) { mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null); mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); } else { mHeader.setLayerType(View.LAYER_TYPE_NONE, null); mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null); } } public void prepareTabsAnimation(PropertyAnimator animator) { if (!mHeaderVisible) { final Resources resources = getContext().getResources(); final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); final int translationY = (mVisible ? 0 : -toolbarHeight); if (mVisible) { ViewHelper.setTranslationY(mHeader, -toolbarHeight); ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight); ViewHelper.setAlpha(mTabsContainer, 0.0f); } animator.attach(mTabsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f); animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY); animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY); } setHWLayerEnabled(true); } public void finishTabsAnimation() { setHWLayerEnabled(false); // If the tray is now hidden, call hide() on current panel and unset it as the current panel // to avoid hide() being called again when the layout is opened next. if (!mVisible && mPanel != null) { mPanel.hide(); mPanel = null; } } public void setTabsLayoutChangeListener(TabsLayoutChangeListener listener) { mLayoutChangeListener = listener; } private void dispatchLayoutChange(int width, int height) { if (mLayoutChangeListener != null) mLayoutChangeListener.onTabsLayoutChange(width, height); } }