mobile/android/base/toolbar/ToolbarDisplayLayout.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     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.toolbar;
     8 import org.mozilla.gecko.AboutPages;
     9 import org.mozilla.gecko.animation.PropertyAnimator;
    10 import org.mozilla.gecko.animation.ViewHelper;
    11 import org.mozilla.gecko.BrowserApp;
    12 import org.mozilla.gecko.R;
    13 import org.mozilla.gecko.SiteIdentity;
    14 import org.mozilla.gecko.SiteIdentity.SecurityMode;
    15 import org.mozilla.gecko.Tab;
    16 import org.mozilla.gecko.Tabs;
    17 import org.mozilla.gecko.toolbar.BrowserToolbar.ForwardButtonAnimation;
    18 import org.mozilla.gecko.util.StringUtils;
    19 import org.mozilla.gecko.widget.ThemedLinearLayout;
    20 import org.mozilla.gecko.widget.ThemedTextView;
    22 import org.json.JSONObject;
    24 import android.content.Context;
    25 import android.content.res.Resources;
    26 import android.graphics.Bitmap;
    27 import android.os.Build;
    28 import android.os.SystemClock;
    29 import android.text.style.ForegroundColorSpan;
    30 import android.text.Spannable;
    31 import android.text.SpannableStringBuilder;
    32 import android.text.Spanned;
    33 import android.text.TextUtils;
    34 import android.util.AttributeSet;
    35 import android.util.Log;
    36 import android.view.LayoutInflater;
    37 import android.view.View;
    38 import android.view.animation.Animation;
    39 import android.view.animation.AnimationUtils;
    40 import android.view.animation.AlphaAnimation;
    41 import android.view.animation.TranslateAnimation;
    42 import android.widget.Button;
    43 import android.widget.ImageButton;
    44 import android.widget.LinearLayout.LayoutParams;
    46 import java.util.Arrays;
    47 import java.util.EnumSet;
    48 import java.util.List;
    50 /**
    51 * {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
    52 * display state. It's used to display the state of the currently selected
    53 * tab. It should always be updated through a single entry point
    54 * (updateFromTab) and should never track any tab events or gecko messages
    55 * on its own to keep it as dumb as possible.
    56 *
    57 * The UI has two possible modes: progress and display which are triggered
    58 * when UpdateFlags.PROGRESS is used depending on the current tab state.
    59 * The progress mode is triggered when the tab is loading a page. Display mode
    60 * is used otherwise.
    61 *
    62 * {@code ToolbarDisplayLayout} is meant to be owned by {@code BrowserToolbar}
    63 * which is the main event bus for the toolbar subsystem.
    64 */
    65 public class ToolbarDisplayLayout extends ThemedLinearLayout
    66                                   implements Animation.AnimationListener {
    68     private static final String LOGTAG = "GeckoToolbarDisplayLayout";
    70     // To be used with updateFromTab() to allow the caller
    71     // to give enough context for the requested state change.
    72     enum UpdateFlags {
    73         TITLE,
    74         FAVICON,
    75         PROGRESS,
    76         SITE_IDENTITY,
    77         PRIVATE_MODE,
    79         // Disable any animation that might be
    80         // triggered from this state change. Mostly
    81         // used on tab switches, see BrowserToolbar.
    82         DISABLE_ANIMATIONS
    83     }
    85     private enum UIMode {
    86         PROGRESS,
    87         DISPLAY
    88     }
    90     interface OnStopListener {
    91         public Tab onStop();
    92     }
    94     interface OnTitleChangeListener {
    95         public void onTitleChange(CharSequence title);
    96     }
    98     private final BrowserApp mActivity;
   100     private UIMode mUiMode;
   102     private ThemedTextView mTitle;
   103     private int mTitlePadding;
   104     private ToolbarTitlePrefs mTitlePrefs;
   105     private OnTitleChangeListener mTitleChangeListener;
   107     private ImageButton mSiteSecurity;
   108     private boolean mSiteSecurityVisible;
   110     // To de-bounce sets.
   111     private Bitmap mLastFavicon;
   112     private ImageButton mFavicon;
   113     private int mFaviconSize;
   115     private ImageButton mStop;
   116     private OnStopListener mStopListener;
   118     private PageActionLayout mPageActionLayout;
   120     private AlphaAnimation mLockFadeIn;
   121     private TranslateAnimation mTitleSlideLeft;
   122     private TranslateAnimation mTitleSlideRight;
   124     private SiteIdentityPopup mSiteIdentityPopup;
   125     private SecurityMode mSecurityMode;
   127     private PropertyAnimator mForwardAnim;
   129     private final ForegroundColorSpan mUrlColor;
   130     private final ForegroundColorSpan mBlockedColor;
   131     private final ForegroundColorSpan mDomainColor;
   132     private final ForegroundColorSpan mPrivateDomainColor;
   134     public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
   135         super(context, attrs);
   136         setOrientation(HORIZONTAL);
   138         mActivity = (BrowserApp) context;
   140         LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
   142         mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
   143         mTitlePadding = mTitle.getPaddingRight();
   145         final Resources res = getResources();
   147         mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext));
   148         mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext));
   149         mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
   150         mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
   152         mFavicon = (ImageButton) findViewById(R.id.favicon);
   153         if (Build.VERSION.SDK_INT >= 16) {
   154             mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
   155         }
   156         mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
   158         mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
   159         mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
   161         mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
   162         mSiteIdentityPopup.setAnchor(mSiteSecurity);
   164         mStop = (ImageButton) findViewById(R.id.stop);
   165         mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
   166     }
   168     @Override
   169     public void onAttachedToWindow() {
   170         mTitlePrefs = new ToolbarTitlePrefs();
   172         Button.OnClickListener faviconListener = new Button.OnClickListener() {
   173             @Override
   174             public void onClick(View view) {
   175                 if (mSiteSecurity.getVisibility() != View.VISIBLE) {
   176                     return;
   177                 }
   179                 mSiteIdentityPopup.show();
   180             }
   181         };
   183         mFavicon.setOnClickListener(faviconListener);
   184         mSiteSecurity.setOnClickListener(faviconListener);
   186         mStop.setOnClickListener(new Button.OnClickListener() {
   187             @Override
   188             public void onClick(View v) {
   189                 if (mStopListener != null) {
   190                     // Force toolbar to switch to Display mode
   191                     // immediately based on the stopped tab.
   192                     final Tab tab = mStopListener.onStop();
   193                     if (tab != null) {
   194                         updateUiMode(tab, UIMode.DISPLAY, EnumSet.noneOf(UpdateFlags.class));
   195                     }
   196                 }
   197             }
   198         });
   200         float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
   202         LayoutParams siteSecParams = (LayoutParams) mSiteSecurity.getLayoutParams();
   203         final float scale = getResources().getDisplayMetrics().density;
   204         slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f;
   206         mLockFadeIn = new AlphaAnimation(0.0f, 1.0f);
   207         mLockFadeIn.setAnimationListener(this);
   209         mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0);
   210         mTitleSlideLeft.setAnimationListener(this);
   212         mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0);
   213         mTitleSlideRight.setAnimationListener(this);
   215         final int lockAnimDuration = 300;
   216         mLockFadeIn.setDuration(lockAnimDuration);
   217         mTitleSlideLeft.setDuration(lockAnimDuration);
   218         mTitleSlideRight.setDuration(lockAnimDuration);
   219     }
   221     @Override
   222     public void onDetachedFromWindow() {
   223         mTitlePrefs.close();
   224     }
   226     @Override
   227     public void onAnimationStart(Animation animation) {
   228         if (animation.equals(mLockFadeIn)) {
   229             if (mSiteSecurityVisible)
   230                 mSiteSecurity.setVisibility(View.VISIBLE);
   231         } else if (animation.equals(mTitleSlideLeft)) {
   232             // These two animations may be scheduled to start while the forward
   233             // animation is occurring. If we're showing the site security icon, make
   234             // sure it doesn't take any space during the forward transition.
   235             mSiteSecurity.setVisibility(View.GONE);
   236         } else if (animation.equals(mTitleSlideRight)) {
   237             // If we're hiding the icon, make sure that we keep its padding
   238             // in place during the forward transition
   239             mSiteSecurity.setVisibility(View.INVISIBLE);
   240         }
   241     }
   243     @Override
   244     public void onAnimationRepeat(Animation animation) {
   245     }
   247     @Override
   248     public void onAnimationEnd(Animation animation) {
   249         if (animation.equals(mTitleSlideRight)) {
   250             mSiteSecurity.startAnimation(mLockFadeIn);
   251         }
   252     }
   254     @Override
   255     public void setNextFocusDownId(int nextId) {
   256         mFavicon.setNextFocusDownId(nextId);
   257         mStop.setNextFocusDownId(nextId);
   258         mSiteSecurity.setNextFocusDownId(nextId);
   259         mPageActionLayout.setNextFocusDownId(nextId);
   260     }
   262     void updateFromTab(Tab tab, EnumSet<UpdateFlags> flags) {
   263         if (flags.contains(UpdateFlags.TITLE)) {
   264             updateTitle(tab);
   265         }
   267         if (flags.contains(UpdateFlags.FAVICON)) {
   268             updateFavicon(tab);
   269         }
   271         if (flags.contains(UpdateFlags.SITE_IDENTITY)) {
   272             updateSiteIdentity(tab, flags);
   273         }
   275         if (flags.contains(UpdateFlags.PROGRESS)) {
   276             updateProgress(tab, flags);
   277         }
   279         if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
   280             mTitle.setPrivateMode(tab != null && tab.isPrivate());
   281         }
   282     }
   284     void setTitle(CharSequence title) {
   285         mTitle.setText(title);
   287         if (mTitleChangeListener != null) {
   288             mTitleChangeListener.onTitleChange(title);
   289         }
   290     }
   292     private void updateTitle(Tab tab) {
   293         // Keep the title unchanged if there's no selected tab,
   294         // or if the tab is entering reader mode.
   295         if (tab == null || tab.isEnteringReaderMode()) {
   296             return;
   297         }
   299         final String url = tab.getURL();
   301         // Setting a null title will ensure we just see the
   302         // "Enter Search or Address" placeholder text.
   303         if (AboutPages.isTitlelessAboutPage(url)) {
   304             setTitle(null);
   305             return;
   306         }
   308         // Show the about:blocked page title in red, regardless of prefs
   309         if (tab.getErrorType() == Tab.ErrorType.BLOCKED) {
   310             final String title = tab.getDisplayTitle();
   312             final SpannableStringBuilder builder = new SpannableStringBuilder(title);
   313             builder.setSpan(mBlockedColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
   315             setTitle(builder);
   316             return;
   317         }
   319         // If the pref to show the URL isn't set, just use the tab's display title.
   320         if (!mTitlePrefs.shouldShowUrl() || url == null) {
   321             setTitle(tab.getDisplayTitle());
   322             return;
   323         }
   325         CharSequence title = url;
   326         if (mTitlePrefs.shouldTrimUrls()) {
   327             title = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url));
   328         }
   330         final String baseDomain = tab.getBaseDomain();
   331         if (!TextUtils.isEmpty(baseDomain)) {
   332             final SpannableStringBuilder builder = new SpannableStringBuilder(title);
   334             int index = title.toString().indexOf(baseDomain);
   335             if (index > -1) {
   336                 builder.setSpan(mUrlColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
   337                 builder.setSpan(tab.isPrivate() ? mPrivateDomainColor : mDomainColor,
   338                                 index, index + baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
   340                 title = builder;
   341             }
   342         }
   344         setTitle(title);
   345     }
   347     private void updateFavicon(Tab tab) {
   348         if (tab == null) {
   349             mFavicon.setImageDrawable(null);
   350             return;
   351         }
   353         Bitmap image = tab.getFavicon();
   355         if (image != null && image == mLastFavicon) {
   356             Log.d(LOGTAG, "Ignoring favicon: new image is identical to previous one.");
   357             return;
   358         }
   360         // Cache the original so we can debounce without scaling
   361         mLastFavicon = image;
   363         Log.d(LOGTAG, "updateFavicon(" + image + ")");
   365         if (image != null) {
   366             image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
   367             mFavicon.setImageBitmap(image);
   368         } else {
   369             mFavicon.setImageResource(R.drawable.favicon);
   370         }
   371     }
   373     private void updateSiteIdentity(Tab tab, EnumSet<UpdateFlags> flags) {
   374         final SiteIdentity siteIdentity;
   375         if (tab == null) {
   376             siteIdentity = null;
   377         } else {
   378             siteIdentity = tab.getSiteIdentity();
   379         }
   381         mSiteIdentityPopup.setSiteIdentity(siteIdentity);
   383         final SecurityMode securityMode;
   384         if (siteIdentity == null) {
   385             securityMode = SecurityMode.UNKNOWN;
   386         } else {
   387             securityMode = siteIdentity.getSecurityMode();
   388         }
   390         if (mSecurityMode != securityMode) {
   391             mSecurityMode = securityMode;
   392             mSiteSecurity.setImageLevel(mSecurityMode.ordinal());
   393             updatePageActions(flags);
   394         }
   395     }
   397     private void updateProgress(Tab tab, EnumSet<UpdateFlags> flags) {
   398         final boolean shouldShowThrobber = (tab != null &&
   399                                             tab.getState() == Tab.STATE_LOADING);
   401         updateUiMode(tab, shouldShowThrobber ? UIMode.PROGRESS : UIMode.DISPLAY, flags);
   402     }
   404     private void updateUiMode(Tab tab, UIMode uiMode, EnumSet<UpdateFlags> flags) {
   405         if (mUiMode == uiMode) {
   406             return;
   407         }
   409         mUiMode = uiMode;
   411         // The "Throbber start" and "Throbber stop" log messages in this method
   412         // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
   413         // See discussion in Bug 804457. Bug 805124 tracks paring these down.
   414         if (mUiMode == UIMode.PROGRESS) {
   415             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
   416         } else {
   417             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
   418         }
   420         updatePageActions(flags);
   421     }
   423     private void updatePageActions(EnumSet<UpdateFlags> flags) {
   424         final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS);
   426         mStop.setVisibility(isShowingProgress ? View.VISIBLE : View.GONE);
   427         mPageActionLayout.setVisibility(!isShowingProgress ? View.VISIBLE : View.GONE);
   429         boolean shouldShowSiteSecurity = (!isShowingProgress &&
   430                                           mSecurityMode != SecurityMode.UNKNOWN);
   432         setSiteSecurityVisibility(shouldShowSiteSecurity, flags);
   434         // We want title to fill the whole space available for it when there are icons
   435         // being shown on the right side of the toolbar as the icons already have some
   436         // padding in them. This is just to avoid wasting space when icons are shown.
   437         mTitle.setPadding(0, 0, (!isShowingProgress ? mTitlePadding : 0), 0);
   438     }
   440     private void setSiteSecurityVisibility(boolean visible, EnumSet<UpdateFlags> flags) {
   441         if (visible == mSiteSecurityVisible) {
   442             return;
   443         }
   445         mSiteSecurityVisible = visible;
   447         mTitle.clearAnimation();
   448         mSiteSecurity.clearAnimation();
   450         if (flags.contains(UpdateFlags.DISABLE_ANIMATIONS)) {
   451             mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE);
   452             return;
   453         }
   455         // If any of these animations were cancelled as a result of the
   456         // clearAnimation() calls above, we need to reset them.
   457         mLockFadeIn.reset();
   458         mTitleSlideLeft.reset();
   459         mTitleSlideRight.reset();
   461         if (mForwardAnim != null) {
   462             long delay = mForwardAnim.getRemainingTime();
   463             mTitleSlideRight.setStartOffset(delay);
   464             mTitleSlideLeft.setStartOffset(delay);
   465         } else {
   466             mTitleSlideRight.setStartOffset(0);
   467             mTitleSlideLeft.setStartOffset(0);
   468         }
   470         mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft);
   471     }
   473     List<View> getFocusOrder() {
   474         return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop);
   475     }
   477     void setOnStopListener(OnStopListener listener) {
   478         mStopListener = listener;
   479     }
   481     void setOnTitleChangeListener(OnTitleChangeListener listener) {
   482         mTitleChangeListener = listener;
   483     }
   485     View getDoorHangerAnchor() {
   486         return mFavicon;
   487     }
   489     void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
   490         mForwardAnim = anim;
   492         if (animation == ForwardButtonAnimation.HIDE) {
   493             anim.attach(mTitle,
   494                         PropertyAnimator.Property.TRANSLATION_X,
   495                         0);
   496             anim.attach(mFavicon,
   497                         PropertyAnimator.Property.TRANSLATION_X,
   498                         0);
   499             anim.attach(mSiteSecurity,
   500                         PropertyAnimator.Property.TRANSLATION_X,
   501                         0);
   503             // We're hiding the forward button. We're going to reset the margin before
   504             // the animation starts, so we shift these items to the right so that they don't
   505             // appear to move initially.
   506             ViewHelper.setTranslationX(mTitle, width);
   507             ViewHelper.setTranslationX(mFavicon, width);
   508             ViewHelper.setTranslationX(mSiteSecurity, width);
   509         } else {
   510             anim.attach(mTitle,
   511                         PropertyAnimator.Property.TRANSLATION_X,
   512                         width);
   513             anim.attach(mFavicon,
   514                         PropertyAnimator.Property.TRANSLATION_X,
   515                         width);
   516             anim.attach(mSiteSecurity,
   517                         PropertyAnimator.Property.TRANSLATION_X,
   518                         width);
   519         }
   520     }
   522     void finishForwardAnimation() {
   523         ViewHelper.setTranslationX(mTitle, 0);
   524         ViewHelper.setTranslationX(mFavicon, 0);
   525         ViewHelper.setTranslationX(mSiteSecurity, 0);
   527         mForwardAnim = null;
   528     }
   530     void prepareStartEditingAnimation() {
   531         // Hide page actions/stop buttons immediately
   532         ViewHelper.setAlpha(mPageActionLayout, 0);
   533         ViewHelper.setAlpha(mStop, 0);
   534     }
   536     void prepareStopEditingAnimation(PropertyAnimator anim) {
   537         // Fade toolbar buttons (page actions, stop) after the entry
   538         // is schrunk back to its original size.
   539         anim.attach(mPageActionLayout,
   540                     PropertyAnimator.Property.ALPHA,
   541                     1);
   543         anim.attach(mStop,
   544                     PropertyAnimator.Property.ALPHA,
   545                     1);
   546     }
   548     boolean dismissSiteIdentityPopup() {
   549         if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
   550             mSiteIdentityPopup.dismiss();
   551             return true;
   552         }
   554         return false;
   555     }
   556 }

mercurial