rename from mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java
rename to mobile/android/app/src/australis/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java
rename from mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java
rename to mobile/android/app/src/australis/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java
rename from mobile/android/base/java/org/mozilla/gecko/toolbar/ForwardButton.java
rename to mobile/android/app/src/australis/java/org/mozilla/gecko/toolbar/ForwardButton.java
rename from mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
rename to mobile/android/app/src/australis/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
rename from mobile/android/app/src/main/res/color/action_bar_menu_item_colors.xml
rename to mobile/android/app/src/australis/res/color/action_bar_menu_item_colors.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/australis/res/color/tab_new_tab_strip_colors.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+ <item gecko:state_light="true"
+ android:color="@color/toolbar_icon_grey"/>
+
+ <item android:color="@color/tabs_tray_icon_grey"/>
+
+</selector>
\ No newline at end of file
rename from mobile/android/app/src/main/res/drawable-large-v11/url_bar_nav_button.xml
rename to mobile/android/app/src/australis/res/drawable-large-v11/url_bar_nav_button.xml
deleted file mode 100644
--- a/mobile/android/app/src/main/res/color/tab_new_tab_strip_colors.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_light="true"
- android:color="@color/toolbar_icon_grey"/>
-
- <item android:color="@color/tabs_tray_icon_grey"/>
-
-</selector>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java
@@ -0,0 +1,211 @@
+/* -*- 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.toolbar;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.util.ViewUtil;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.MarginLayoutParamsCompat;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+
+/**
+ * The toolbar implementation for tablet.
+ */
+class BrowserToolbarTablet extends BrowserToolbarTabletBase {
+
+ private static final int FORWARD_ANIMATION_DURATION = 450;
+
+ private enum ForwardButtonState {
+ HIDDEN,
+ DISPLAYED,
+ TRANSITIONING,
+ }
+
+ private final int forwardButtonTranslationWidth;
+
+ private ForwardButtonState forwardButtonState;
+
+ private boolean backButtonWasEnabledOnStartEditing;
+
+ public BrowserToolbarTablet(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ forwardButtonTranslationWidth =
+ getResources().getDimensionPixelOffset(R.dimen.tablet_nav_button_width);
+
+ // The forward button is initially expanded (in the layout file)
+ // so translate it for start of the expansion animation; future
+ // iterations translate it to this position when hiding and will already be set up.
+ ViewHelper.setTranslationX(forwardButton, forwardButtonTranslationWidth * (isLayoutRtl() ? 1 : -1));
+
+ // TODO: Move this to *TabletBase when old tablet is removed.
+ // We don't want users clicking the forward button in transitions, but we don't want it to
+ // look disabled to avoid flickering complications (e.g. disabled in editing mode), so undo
+ // the work of the super class' constructor.
+ forwardButton.setEnabled(true);
+
+ updateForwardButtonState(ForwardButtonState.HIDDEN);
+ }
+
+ private boolean isLayoutRtl() {
+ return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ }
+
+ private void updateForwardButtonState(final ForwardButtonState state) {
+ forwardButtonState = state;
+ forwardButton.setEnabled(forwardButtonState == ForwardButtonState.DISPLAYED);
+ }
+
+ @Override
+ public boolean isAnimating() {
+ return false;
+ }
+
+ @Override
+ protected void triggerStartEditingTransition(final PropertyAnimator animator) {
+ showUrlEditLayout();
+ }
+
+ @Override
+ protected void triggerStopEditingTransition() {
+ hideUrlEditLayout();
+ }
+
+ @Override
+ protected void animateForwardButton(final ForwardButtonAnimation animation) {
+ final boolean willShowForward = (animation == ForwardButtonAnimation.SHOW);
+ if ((forwardButtonState != ForwardButtonState.HIDDEN && willShowForward) ||
+ (forwardButtonState != ForwardButtonState.DISPLAYED && !willShowForward)) {
+ return;
+ }
+ updateForwardButtonState(ForwardButtonState.TRANSITIONING);
+
+ // We want the forward button to show immediately when switching tabs
+ final PropertyAnimator forwardAnim =
+ new PropertyAnimator(isSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
+
+ forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
+ @Override
+ public void onPropertyAnimationStart() {
+ if (!willShowForward) {
+ // Set the margin before the transition when hiding the forward button. We
+ // have to do this so that the favicon isn't clipped during the transition
+ MarginLayoutParams layoutParams = (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
+ ViewUtil.setMarginStart(layoutParams, 0, isLayoutRtl());
+
+ // Do the same on the URL edit container
+ layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
+ ViewUtil.setMarginStart(layoutParams, 0, isLayoutRtl());
+
+ requestLayout();
+ // Note, we already translated the favicon, site security, and text field
+ // in prepareForwardAnimation, so they should appear to have not moved at
+ // all at this point.
+ }
+ }
+
+ @Override
+ public void onPropertyAnimationEnd() {
+ final ForwardButtonState newForwardButtonState;
+ if (willShowForward) {
+ // Increase the margins to ensure the text does not run outside the View.
+ MarginLayoutParams layoutParams = (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
+ ViewUtil.setMarginStart(layoutParams, forwardButtonTranslationWidth, isLayoutRtl());
+
+ layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
+ ViewUtil.setMarginStart(layoutParams, forwardButtonTranslationWidth, isLayoutRtl());
+
+ newForwardButtonState = ForwardButtonState.DISPLAYED;
+ } else {
+ newForwardButtonState = ForwardButtonState.HIDDEN;
+ }
+
+ urlDisplayLayout.finishForwardAnimation();
+ updateForwardButtonState(newForwardButtonState);
+
+ requestLayout();
+ }
+ });
+
+ prepareForwardAnimation(forwardAnim, animation, forwardButtonTranslationWidth);
+ forwardAnim.start();
+ }
+
+ private void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
+ boolean isLayoutRtl = isLayoutRtl();
+ if (animation == ForwardButtonAnimation.HIDE) {
+ anim.attach(forwardButton,
+ PropertyAnimator.Property.TRANSLATION_X,
+ width * (isLayoutRtl ? 1 : -1));
+ anim.attach(forwardButton,
+ PropertyAnimator.Property.ALPHA,
+ 0);
+
+ } else {
+ anim.attach(forwardButton,
+ PropertyAnimator.Property.TRANSLATION_X,
+ 0);
+ anim.attach(forwardButton,
+ PropertyAnimator.Property.ALPHA,
+ 1);
+ }
+
+ urlDisplayLayout.prepareForwardAnimation(anim, animation, width);
+ }
+
+ @Override
+ public void triggerTabsPanelTransition(final PropertyAnimator animator, final boolean areTabsShown) {
+ // Do nothing.
+ }
+
+ @Override
+ public void startEditing(final String url, final PropertyAnimator animator) {
+ // We already know the forward button state - no need to store it here.
+ backButtonWasEnabledOnStartEditing = backButton.isEnabled();
+
+ backButton.setEnabled(false);
+ forwardButton.setEnabled(false);
+
+ super.startEditing(url, animator);
+ }
+
+ @Override
+ public String commitEdit() {
+ stopEditingNewTablet();
+ return super.commitEdit();
+ }
+
+ @Override
+ public String cancelEdit() {
+ // This can get called when we're not editing but we only want
+ // to make these changes when leaving editing mode.
+ if (isEditing()) {
+ stopEditingNewTablet();
+
+ backButton.setEnabled(backButtonWasEnabledOnStartEditing);
+ updateForwardButtonState(forwardButtonState);
+ }
+
+ return super.cancelEdit();
+ }
+
+ private void stopEditingNewTablet() {
+ // Undo the changes caused by calling setEnabled for forwardButton in startEditing.
+ // Note that this should be called first so the enabled state of the
+ // forward button is set to the proper value.
+ forwardButton.setEnabled(true);
+ }
+
+ @Override
+ protected Drawable getLWTDefaultStateSetDrawable() {
+ return BrowserToolbar.getLightweightThemeDrawable(this, getTheme(), R.color.toolbar_grey);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java
@@ -0,0 +1,203 @@
+/* -*- 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.toolbar;
+
+import java.util.Arrays;
+
+import android.support.v4.content.ContextCompat;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.skin.SkinConfig;
+import org.mozilla.gecko.tabs.TabHistoryController;
+import org.mozilla.gecko.menu.MenuItemActionBar;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
+import org.mozilla.gecko.widget.themed.ThemedImageButton;
+import org.mozilla.gecko.widget.themed.ThemedTextView;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+/**
+ * A base implementations of the browser toolbar for tablets.
+ * This class manages any Views, variables, etc. that are exclusive to tablet.
+ */
+abstract class BrowserToolbarTabletBase extends BrowserToolbar {
+
+ protected enum ForwardButtonAnimation {
+ SHOW,
+ HIDE
+ }
+
+ protected final LinearLayout actionItemBar;
+
+ protected final BackButton backButton;
+ protected final ForwardButton forwardButton;
+
+ protected final View menuButtonMarginView;
+
+ private final PorterDuffColorFilter privateBrowsingTabletMenuItemColorFilter;
+
+ protected abstract void animateForwardButton(ForwardButtonAnimation animation);
+
+ public BrowserToolbarTabletBase(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ actionItemBar = (LinearLayout) findViewById(R.id.menu_items);
+
+ backButton = (BackButton) findViewById(R.id.back);
+ backButton.setEnabled(false);
+ forwardButton = (ForwardButton) findViewById(R.id.forward);
+ forwardButton.setEnabled(false);
+ initButtonListeners();
+
+ focusOrder.addAll(Arrays.asList(tabsButton, (View) backButton, (View) forwardButton, this));
+ focusOrder.addAll(urlDisplayLayout.getFocusOrder());
+ focusOrder.addAll(Arrays.asList(actionItemBar, menuButton));
+
+ urlDisplayLayout.updateSiteIdentityAnchor(backButton);
+
+ privateBrowsingTabletMenuItemColorFilter = new PorterDuffColorFilter(
+ ContextCompat.getColor(context, R.color.tabs_tray_icon_grey), PorterDuff.Mode.SRC_IN);
+
+ menuButtonMarginView = findViewById(R.id.menu_margin);
+ if (menuButtonMarginView != null) {
+ menuButtonMarginView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void initButtonListeners() {
+ backButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Tabs.getInstance().getSelectedTab().doBack();
+ }
+ });
+ backButton.setOnLongClickListener(new Button.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ return tabHistoryController.showTabHistory(Tabs.getInstance().getSelectedTab(),
+ TabHistoryController.HistoryAction.BACK);
+ }
+ });
+
+ forwardButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Tabs.getInstance().getSelectedTab().doForward();
+ }
+ });
+ forwardButton.setOnLongClickListener(new Button.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ return tabHistoryController.showTabHistory(Tabs.getInstance().getSelectedTab(),
+ TabHistoryController.HistoryAction.FORWARD);
+ }
+ });
+ }
+
+ @Override
+ protected boolean isTabsButtonOffscreen() {
+ return false;
+ }
+
+ @Override
+ public boolean addActionItem(final View actionItem) {
+ actionItemBar.addView(actionItem, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ return true;
+ }
+
+ @Override
+ public void removeActionItem(final View actionItem) {
+ actionItemBar.removeView(actionItem);
+ }
+
+ @Override
+ protected void updateNavigationButtons(final Tab tab) {
+ backButton.setEnabled(canDoBack(tab));
+ animateForwardButton(
+ canDoForward(tab) ? ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
+ }
+
+ @Override
+ public void refresh() {
+ super.refresh();
+ forwardButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_menu_forward));
+ backButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_menu_back));
+ }
+
+ @Override
+ public void setNextFocusDownId(int nextId) {
+ super.setNextFocusDownId(nextId);
+ backButton.setNextFocusDownId(nextId);
+ forwardButton.setNextFocusDownId(nextId);
+ }
+
+ @Override
+ public void setPrivateMode(final boolean isPrivate) {
+ super.setPrivateMode(isPrivate);
+
+ if (SkinConfig.isAustralis()) {
+ // If we had backgroundTintList, we could remove the colorFilter
+ // code in favor of setPrivateMode (bug 1197432).
+ final PorterDuffColorFilter colorFilter =
+ isPrivate ? privateBrowsingTabletMenuItemColorFilter : null;
+ setTabsCounterPrivateMode(isPrivate, colorFilter);
+ }
+
+ backButton.setPrivateMode(isPrivate);
+ forwardButton.setPrivateMode(isPrivate);
+
+ // bug 1375351: menuButton is a ThemedImageButton in Photon flavor
+ if (SkinConfig.isPhoton()) {
+ ((ThemedImageButton)menuButton).setPrivateMode(isPrivate);
+ } else {
+ ((ThemedFrameLayout)menuButton).setPrivateMode(isPrivate);
+ // menuIcon only exists in Australis flavor
+ menuIcon.setPrivateMode(isPrivate);
+ }
+
+ for (int i = 0; i < actionItemBar.getChildCount(); ++i) {
+ final MenuItemActionBar child = (MenuItemActionBar) actionItemBar.getChildAt(i);
+ child.setPrivateMode(isPrivate);
+ }
+ }
+
+ private void setTabsCounterPrivateMode(final boolean isPrivate, final PorterDuffColorFilter colorFilter) {
+ // The TabsCounter is a TextSwitcher which cycles two views
+ // to provide animations, hence looping over these two children.
+ for (int i = 0; i < 2; ++i) {
+ final ThemedTextView view = (ThemedTextView) tabsCounter.getChildAt(i);
+ view.setPrivateMode(isPrivate);
+ view.getBackground().mutate().setColorFilter(colorFilter);
+ }
+
+ // To prevent animation of the background,
+ // it is set to a different Drawable.
+ tabsCounter.getBackground().mutate().setColorFilter(colorFilter);
+ }
+
+ @Override
+ public View getDoorHangerAnchor() {
+ return backButton;
+ }
+
+ protected boolean canDoBack(final Tab tab) {
+ return (tab.canDoBack() && !isEditing());
+ }
+
+ protected boolean canDoForward(final Tab tab) {
+ return (tab.canDoForward() && !isEditing());
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ForwardButton.java
@@ -0,0 +1,31 @@
+/* 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.toolbar;
+
+import android.content.Context;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+
+public class ForwardButton extends NavButton {
+ public ForwardButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+ super.onSizeChanged(width, height, oldWidth, oldHeight);
+
+ boolean isLayoutRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ mBorderPath.reset();
+ final float startX;
+ if (isLayoutRtl) {
+ startX = 0 + mBorderWidth;
+ } else {
+ startX = width - mBorderWidth;
+ }
+ mBorderPath.moveTo(startX, 0);
+ mBorderPath.lineTo(startX, height);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -0,0 +1,524 @@
+/* -*- 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.toolbar;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+import android.support.v4.content.ContextCompat;
+import org.mozilla.gecko.AboutPages;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.reader.ReaderModeUtils;
+import org.mozilla.gecko.SiteIdentity;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.animation.PropertyAnimator;
+import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.skin.SkinConfig;
+import org.mozilla.gecko.toolbar.BrowserToolbarTabletBase.ForwardButtonAnimation;
+import org.mozilla.gecko.Experiments;
+import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.util.ViewUtil;
+import org.mozilla.gecko.widget.themed.ThemedImageButton;
+import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
+import org.mozilla.gecko.widget.themed.ThemedTextView;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+
+import org.mozilla.gecko.switchboard.SwitchBoard;
+
+/**
+* {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
+* display state. It's used to display the state of the currently selected
+* tab. It should always be updated through a single entry point
+* (updateFromTab) and should never track any tab events or gecko messages
+* on its own to keep it as dumb as possible.
+*
+* The UI has two possible modes: progress and display which are triggered
+* when UpdateFlags.PROGRESS is used depending on the current tab state.
+* The progress mode is triggered when the tab is loading a page. Display mode
+* is used otherwise.
+*
+* {@code ToolbarDisplayLayout} is meant to be owned by {@code BrowserToolbar}
+* which is the main event bus for the toolbar subsystem.
+*/
+public class ToolbarDisplayLayout extends ThemedLinearLayout {
+
+ private static final String LOGTAG = "GeckoToolbarDisplayLayout";
+ private boolean mTrackingProtectionEnabled;
+
+ // To be used with updateFromTab() to allow the caller
+ // to give enough context for the requested state change.
+ enum UpdateFlags {
+ TITLE,
+ FAVICON,
+ PROGRESS,
+ SITE_IDENTITY,
+ PRIVATE_MODE,
+
+ // Disable any animation that might be
+ // triggered from this state change. Mostly
+ // used on tab switches, see BrowserToolbar.
+ DISABLE_ANIMATIONS
+ }
+
+ private enum UIMode {
+ PROGRESS,
+ DISPLAY
+ }
+
+ interface OnStopListener {
+ Tab onStop();
+ }
+
+ interface OnTitleChangeListener {
+ void onTitleChange(CharSequence title);
+ }
+
+ private final BrowserApp mActivity;
+
+ private UIMode mUiMode;
+
+ private boolean mIsAttached;
+
+ private final ThemedTextView mTitle;
+ private final int mTitlePadding;
+ private ToolbarPrefs mPrefs;
+ private OnTitleChangeListener mTitleChangeListener;
+
+ private final ThemedImageButton mSiteSecurity;
+ private final ImageButton mStop;
+ private OnStopListener mStopListener;
+
+ private final PageActionLayout mPageActionLayout;
+
+ private final SiteIdentityPopup mSiteIdentityPopup;
+ private int mSecurityImageLevel;
+
+ // Security level constants, which map to the icons / levels defined in:
+ // http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/site_security_level.xml
+ // Default level (unverified pages) - globe icon:
+ private static final int LEVEL_DEFAULT_GLOBE = 0;
+ // Levels for displaying Mixed Content state icons.
+ private static final int LEVEL_WARNING_MINOR = 3;
+ private static final int LEVEL_LOCK_DISABLED = 4;
+ // Levels for displaying Tracking Protection state icons.
+ private static final int LEVEL_SHIELD_ENABLED = 5;
+ private static final int LEVEL_SHIELD_DISABLED = 6;
+ // Icon used for about:home
+ private static final int LEVEL_SEARCH_ICON = 999;
+
+ private final ForegroundColorSpan mUrlColorSpan;
+ private final ForegroundColorSpan mPrivateUrlColorSpan;
+ private final ForegroundColorSpan mBlockedColorSpan;
+ private final ForegroundColorSpan mPrivateBlockedColorSpan;
+ private final ForegroundColorSpan mDomainColorSpan;
+ private final ForegroundColorSpan mPrivateDomainColorSpan;
+ private final ForegroundColorSpan mCertificateOwnerColorSpan;
+ private final ForegroundColorSpan mPrivateCertificateOwnerColorSpan;
+
+ public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setOrientation(HORIZONTAL);
+
+ mActivity = (BrowserApp) context;
+
+ LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
+
+ mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
+ mTitlePadding = mTitle.getPaddingRight();
+
+ mUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext));
+ mPrivateUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext_private));
+ mBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext));
+ mPrivateBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext_private));
+ mDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext));
+ mPrivateDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext_private));
+ mCertificateOwnerColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_certificate_owner));
+ mPrivateCertificateOwnerColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_certificate_owner_private));
+
+ mSiteSecurity = (ThemedImageButton) findViewById(R.id.site_security);
+
+ mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
+ mSiteIdentityPopup.setAnchor(this);
+ mSiteIdentityPopup.setOnVisibilityChangeListener(mActivity);
+
+ mStop = (ImageButton) findViewById(R.id.stop);
+ mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
+ }
+
+ @Override
+ public void setPrivateMode(boolean isPrivate) {
+ super.setPrivateMode(isPrivate);
+ mSiteSecurity.setPrivateMode(isPrivate);
+
+ // Bug 1375351 should change class type to ThemedImageButton to avoid casting
+ if (SkinConfig.isPhoton()) {
+ ((ThemedImageButton)mStop).setPrivateMode(isPrivate);
+ }
+
+ mPageActionLayout.setPrivateMode(isPrivate);
+ }
+
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mIsAttached = true;
+
+ final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+ if (selectedTab != null) {
+ // A tab was selected before we became ready; update to that tab now.
+ updateFromTab(selectedTab, EnumSet.allOf(UpdateFlags.class));
+ }
+
+ mSiteIdentityPopup.registerListeners();
+
+ mSiteSecurity.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mSiteIdentityPopup.show();
+ }
+ });
+
+ mStop.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mStopListener != null) {
+ // Force toolbar to switch to Display mode
+ // immediately based on the stopped tab.
+ final Tab tab = mStopListener.onStop();
+ if (tab != null) {
+ updateUiMode(UIMode.DISPLAY);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mIsAttached = false;
+ mSiteIdentityPopup.unregisterListeners();
+ }
+
+ @Override
+ public void setNextFocusDownId(int nextId) {
+ mStop.setNextFocusDownId(nextId);
+ mSiteSecurity.setNextFocusDownId(nextId);
+ mPageActionLayout.setNextFocusDownId(nextId);
+ }
+
+ void setToolbarPrefs(final ToolbarPrefs prefs) {
+ mPrefs = prefs;
+ }
+
+ void updateFromTab(@NonNull Tab tab, EnumSet<UpdateFlags> flags) {
+ // Several parts of ToolbarDisplayLayout's state depends
+ // on the views being attached to the view tree.
+ if (!mIsAttached) {
+ return;
+ }
+
+ if (flags.contains(UpdateFlags.TITLE)) {
+ updateTitle(tab);
+ }
+
+ if (flags.contains(UpdateFlags.SITE_IDENTITY)) {
+ updateSiteIdentity(tab);
+ }
+
+ if (flags.contains(UpdateFlags.PROGRESS)) {
+ updateProgress(tab);
+ }
+
+ if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
+ mTitle.setPrivateMode(tab.isPrivate());
+ }
+ }
+
+ void setTitle(CharSequence title) {
+ mTitle.setText(title);
+
+ if (TextUtils.isEmpty(title)) {
+ // Reset TextDirection to Locale in order to reveal text hint in correct direction
+ ViewUtil.setTextDirection(mTitle, TEXT_DIRECTION_LOCALE);
+ } else {
+ // Otherwise, fall back to default first strong strategy
+ ViewUtil.setTextDirection(mTitle, TEXT_DIRECTION_FIRST_STRONG);
+ }
+
+ if (mTitleChangeListener != null) {
+ mTitleChangeListener.onTitleChange(title);
+ }
+ }
+
+ private void updateTitle(@NonNull Tab tab) {
+ // Keep the title unchanged if there's no selected tab,
+ // or if the tab is entering reader mode.
+ if (tab.isEnteringReaderMode()) {
+ return;
+ }
+
+ final String url = tab.getURL();
+
+ // Setting a null title will ensure we just see the
+ // "Enter Search or Address" placeholder text.
+ if (AboutPages.isTitlelessAboutPage(url)) {
+ setTitle(null);
+ setContentDescription(null);
+ return;
+ }
+
+ // Show the about:blocked page title in red, regardless of prefs
+ if (tab.getErrorType() == Tab.ErrorType.BLOCKED) {
+ final String title = tab.getDisplayTitle();
+
+ final SpannableStringBuilder builder = new SpannableStringBuilder(title);
+ final ForegroundColorSpan fgColorSpan = tab.isPrivate()
+ ? mPrivateBlockedColorSpan
+ : mBlockedColorSpan;
+ builder.setSpan(fgColorSpan, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+
+ setTitle(builder);
+ setContentDescription(null);
+ return;
+ }
+
+ final String baseDomain = tab.getBaseDomain();
+
+ String strippedURL = stripAboutReaderURL(url);
+
+ final boolean isHttpOrHttps = StringUtils.isHttpOrHttps(strippedURL);
+
+ if (mPrefs.shouldTrimUrls()) {
+ strippedURL = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(strippedURL));
+ }
+
+ // The URL bar does not support RTL currently (See bug 928688 and meta bug 702845).
+ // Displaying a URL using RTL (or mixed) characters can lead to an undesired reordering
+ // of elements of the URL. That's why we are forcing the URL to use LTR (bug 1284372).
+ strippedURL = StringUtils.forceLTR(strippedURL);
+
+ // This value is not visible to screen readers but we rely on it when running UI tests. Screen
+ // readers will instead focus BrowserToolbar and read the "base domain" from there. UI tests
+ // will read the content description to obtain the full URL for performing assertions.
+ setContentDescription(strippedURL);
+
+ final SiteIdentity siteIdentity = tab.getSiteIdentity();
+ if (siteIdentity.hasOwner() && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_EV_CERT_OWNER)) {
+ // Show Owner of EV certificate as title
+ updateTitleFromSiteIdentity(siteIdentity, tab.isPrivate());
+ } else if (isHttpOrHttps && !HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)
+ && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_ORIGIN_ONLY)) {
+ // Show just the base domain as title
+ setTitle(baseDomain);
+ } else {
+ // Display full URL with base domain highlighted as title
+ updateAndColorTitleFromFullURL(strippedURL, baseDomain, tab.isPrivate());
+ }
+ }
+
+ private void updateTitleFromSiteIdentity(SiteIdentity siteIdentity, boolean isPrivate) {
+ final String title;
+
+ if (siteIdentity.hasCountry()) {
+ title = String.format("%s (%s)", siteIdentity.getOwner(), siteIdentity.getCountry());
+ } else {
+ title = siteIdentity.getOwner();
+ }
+
+ final SpannableString spannable = new SpannableString(title);
+ final ForegroundColorSpan colorSpan = isPrivate
+ ? mPrivateCertificateOwnerColorSpan
+ : mCertificateOwnerColorSpan;
+ spannable.setSpan(colorSpan, 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ setTitle(spannable);
+ }
+
+ private void updateAndColorTitleFromFullURL(String url, String baseDomain, boolean isPrivate) {
+ if (TextUtils.isEmpty(baseDomain)) {
+ setTitle(url);
+ return;
+ }
+
+ int index = url.indexOf(baseDomain);
+ if (index == -1) {
+ setTitle(url);
+ return;
+ }
+
+ final SpannableStringBuilder builder = new SpannableStringBuilder(url);
+
+ builder.setSpan(isPrivate ? mPrivateUrlColorSpan : mUrlColorSpan, 0, url.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ builder.setSpan(isPrivate ? mPrivateDomainColorSpan : mDomainColorSpan,
+ index, index + baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+
+ setTitle(builder);
+ }
+
+ private String stripAboutReaderURL(final String url) {
+ if (!AboutPages.isAboutReader(url)) {
+ return url;
+ }
+
+ return ReaderModeUtils.stripAboutReaderUrl(url);
+ }
+
+ private void updateSiteIdentity(@NonNull Tab tab) {
+ final SiteIdentity siteIdentity = tab.getSiteIdentity();
+ final SecurityModeUtil.IconType type = SecurityModeUtil.resolve(siteIdentity, tab.getURL());
+ final int imageLevel = SecurityModeUtil.getImageLevel(type);
+
+ mSiteIdentityPopup.setSiteIdentity(siteIdentity);
+ mTrackingProtectionEnabled = SecurityModeUtil.isTrackingProtectionEnabled(siteIdentity);
+
+ if (mSecurityImageLevel != imageLevel) {
+ mSecurityImageLevel = imageLevel;
+ mSiteSecurity.setImageLevel(mSecurityImageLevel);
+ updatePageActions();
+ }
+ }
+
+ private void updateProgress(@NonNull Tab tab) {
+ final boolean shouldShowThrobber = tab.getState() == Tab.STATE_LOADING;
+
+ updateUiMode(shouldShowThrobber ? UIMode.PROGRESS : UIMode.DISPLAY);
+
+ if (Tab.STATE_SUCCESS == tab.getState() && mTrackingProtectionEnabled) {
+ mActivity.showTrackingProtectionPromptIfApplicable();
+ }
+ }
+
+ private void updateUiMode(UIMode uiMode) {
+ if (mUiMode == uiMode) {
+ return;
+ }
+
+ mUiMode = uiMode;
+ updatePageActions();
+ }
+
+ private void updatePageActions() {
+ final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS);
+
+ mStop.setVisibility(isShowingProgress ? View.VISIBLE : View.GONE);
+ mPageActionLayout.setVisibility(!isShowingProgress ? View.VISIBLE : View.GONE);
+
+ // We want title to fill the whole space available for it when there are icons
+ // being shown on the right side of the toolbar as the icons already have some
+ // padding in them. This is just to avoid wasting space when icons are shown.
+ mTitle.setPadding(0, 0, (!isShowingProgress ? mTitlePadding : 0), 0);
+ }
+
+ List<? extends View> getFocusOrder() {
+ return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop);
+ }
+
+ void setOnStopListener(OnStopListener listener) {
+ mStopListener = listener;
+ }
+
+ void setOnTitleChangeListener(OnTitleChangeListener listener) {
+ mTitleChangeListener = listener;
+ }
+
+ /**
+ * Update the Site Identity popup anchor.
+ *
+ * Tablet UI has a tablet-specific doorhanger anchor, so update it after all the views
+ * are inflated.
+ * @param view View to use as the anchor for the Site Identity popup.
+ */
+ void updateSiteIdentityAnchor(View view) {
+ mSiteIdentityPopup.setAnchor(view);
+ }
+
+ private boolean isLayoutRtl() {
+ return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ }
+
+ void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
+ if (animation == ForwardButtonAnimation.HIDE) {
+ // We animate these items individually, rather than this entire view,
+ // so that we don't animate certain views, e.g. the stop button.
+ anim.attach(mTitle,
+ PropertyAnimator.Property.TRANSLATION_X,
+ isLayoutRtl() ? width : 0);
+ anim.attach(mSiteSecurity,
+ PropertyAnimator.Property.TRANSLATION_X,
+ isLayoutRtl() ? width : 0);
+
+ // We're hiding the forward button. We're going to reset the margin before
+ // the animation starts, so we shift these items to the right so that they don't
+ // appear to move initially.
+ ViewHelper.setTranslationX(mTitle, width);
+ ViewHelper.setTranslationX(mSiteSecurity, width);
+ } else {
+ anim.attach(mTitle,
+ PropertyAnimator.Property.TRANSLATION_X,
+ isLayoutRtl() ? 0 : width);
+ anim.attach(mSiteSecurity,
+ PropertyAnimator.Property.TRANSLATION_X,
+ isLayoutRtl() ? 0 : width);
+ }
+ }
+
+ void finishForwardAnimation() {
+ ViewHelper.setTranslationX(mTitle, 0);
+ ViewHelper.setTranslationX(mSiteSecurity, 0);
+ }
+
+ void prepareStartEditingAnimation() {
+ // Hide page actions/stop buttons immediately
+ ViewHelper.setAlpha(mPageActionLayout, 0);
+ ViewHelper.setAlpha(mStop, 0);
+ }
+
+ void prepareStopEditingAnimation(PropertyAnimator anim) {
+ // Fade toolbar buttons (page actions, stop) after the entry
+ // is shrunk back to its original size.
+ anim.attach(mPageActionLayout,
+ PropertyAnimator.Property.ALPHA,
+ 1);
+
+ anim.attach(mStop,
+ PropertyAnimator.Property.ALPHA,
+ 1);
+ }
+
+ boolean dismissSiteIdentityPopup() {
+ if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
+ mSiteIdentityPopup.dismiss();
+ return true;
+ }
+
+ return false;
+ }
+
+ void destroy() {
+ mSiteIdentityPopup.destroy();
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/color/action_bar_menu_item_colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+ <item gecko:state_private="true"
+ android:state_enabled="false"
+ android:color="@color/photon_icon_private"/>
+
+ <item gecko:state_private="true"
+ android:state_enabled="true"
+ android:color="@color/photon_icon_private"/>
+
+ <item android:state_enabled="false"
+ android:color="@color/photon_icon"/>
+
+ <item android:color="@color/photon_icon"/>
+
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/color/tab_new_tab_strip_colors.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+ <item gecko:state_light="true"
+ android:color="@color/photon_icon"/>
+
+ <item android:color="@color/photon_icon_private"/>
+
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/photon/res/drawable-large-v11/url_bar_nav_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+ <!-- private pressed state -->
+ <item gecko:state_private="true"
+ android:state_pressed="true"
+ android:drawable="@color/text_and_tabs_tray_grey"/>
+
+ <!-- focused state -->
+ <item gecko:state_private="true"
+ android:state_focused="true"
+ android:state_pressed="false"
+ android:drawable="@color/placeholder_active_grey"/>
+
+ <!-- pressed state -->
+ <item android:state_pressed="true"
+ android:drawable="@color/toolbar_grey_pressed"/>
+
+ <!-- focused state -->
+ <item android:state_focused="true"
+ android:state_pressed="false"
+ android:drawable="@color/tablet_highlight_focused"/>
+
+ <!-- private browsing mode -->
+ <item gecko:state_private="true"
+ android:drawable="@color/tabs_tray_grey_pressed"/>
+
+ <!-- normal mode -->
+ <item android:drawable="@color/toolbar_grey"/>
+
+</selector>