/* -*- 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; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.MenuPopup; import org.mozilla.gecko.PageActionLayout; import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.StringUtils; import org.mozilla.gecko.util.HardwareUtils; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.util.UiAsyncTask; import org.mozilla.gecko.util.GeckoEventListener; import org.json.JSONObject; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.SystemClock; import android.text.style.ForegroundColorSpan; import android.text.Editable; import android.text.InputType; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.AlphaAnimation; import android.view.animation.Interpolator; import android.view.animation.TranslateAnimation; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.ViewSwitcher; import java.util.Arrays; import java.util.List; public class BrowserToolbar extends GeckoRelativeLayout implements TextWatcher, AutocompleteHandler, Tabs.OnTabsChangedListener, GeckoMenu.ActionItemBarPresenter, Animation.AnimationListener, GeckoEventListener { private static final String LOGTAG = "GeckoToolbar"; public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode"; public interface OnActivateListener { public void onActivate(); } public interface OnCommitListener { public void onCommit(); } public interface OnDismissListener { public void onDismiss(); } public interface OnFilterListener { public void onFilter(String searchText, AutocompleteHandler handler); } private LayoutParams mAwesomeBarParams; private View mUrlDisplayContainer; private View mUrlEditContainer; private CustomEditText mUrlEditText; private View mUrlBarEntry; private ImageView mUrlBarRightEdge; private BrowserToolbarBackground mUrlBarBackground; private GeckoTextView mTitle; private int mTitlePadding; private boolean mSiteSecurityVisible; private boolean mSwitchingTabs; private ShapedButton mTabs; private ImageButton mBack; private ImageButton mForward; public ImageButton mFavicon; public ImageButton mStop; public ImageButton mSiteSecurity; public ImageButton mGo; public PageActionLayout mPageActionLayout; private Animation mProgressSpinner; private TabCounter mTabsCounter; private ImageView mShadow; private GeckoImageButton mMenu; private GeckoImageView mMenuIcon; private LinearLayout mActionItemBar; private MenuPopup mMenuPopup; private List mFocusOrder; private OnActivateListener mActivateListener; private OnCommitListener mCommitListener; private OnDismissListener mDismissListener; private OnFilterListener mFilterListener; final private BrowserApp mActivity; private boolean mHasSoftMenuButton; private boolean mShowSiteSecurity; private boolean mShowReader; private boolean mSpinnerVisible; private boolean mDelayRestartInput; // The previous autocomplete result returned to us private String mAutoCompleteResult = ""; // The user typed part of the autocomplete result private String mAutoCompletePrefix = null; private boolean mIsEditing; private boolean mAnimatingEntry; private AlphaAnimation mLockFadeIn; private TranslateAnimation mTitleSlideLeft; private TranslateAnimation mTitleSlideRight; private int mUrlBarViewOffset; private int mDefaultForwardMargin; private PropertyAnimator mForwardAnim = null; private int mFaviconSize; private PropertyAnimator mVisibilityAnimator; private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator(); private static final int TABS_CONTRACTED = 1; private static final int TABS_EXPANDED = 2; private static final int FORWARD_ANIMATION_DURATION = 450; private final ForegroundColorSpan mUrlColor; private final ForegroundColorSpan mBlockedColor; private final ForegroundColorSpan mDomainColor; private final ForegroundColorSpan mPrivateDomainColor; private boolean mShowUrl; private Integer mPrefObserverId; public BrowserToolbar(Context context) { this(context, null); } public BrowserToolbar(Context context, AttributeSet attrs) { super(context, attrs); // BrowserToolbar is attached to BrowserApp only. mActivity = (BrowserApp) context; // Inflate the content. LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this); Tabs.registerOnTabsChangedListener(this); mSwitchingTabs = true; mIsEditing = false; mAnimatingEntry = false; mShowUrl = false; // listen to the title bar pref. mPrefObserverId = PrefsHelper.getPref(PREF_TITLEBAR_MODE, new PrefsHelper.PrefHandlerBase() { @Override public void prefValue(String pref, String str) { int value = Integer.parseInt(str); boolean shouldShowUrl = (value == 1); if (shouldShowUrl == mShowUrl) { return; } mShowUrl = shouldShowUrl; ThreadUtils.postToUiThread(new Runnable() { @Override public void run() { updateTitle(); } }); } @Override public boolean isObserver() { // We want to be notified of changes to be able to switch mode // without restarting. return true; } }); Resources res = getResources(); mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext)); mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext)); mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext)); mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private)); registerEventListener("Reader:Click"); registerEventListener("Reader:LongClick"); mShowSiteSecurity = false; mShowReader = false; mAnimatingEntry = false; mUrlBarBackground = (BrowserToolbarBackground) findViewById(R.id.url_bar_bg); mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left); mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset); mUrlDisplayContainer = findViewById(R.id.url_display_container); mUrlBarEntry = findViewById(R.id.url_bar_entry); mUrlEditContainer = findViewById(R.id.url_edit_container); mUrlEditText = (CustomEditText) findViewById(R.id.url_edit_text); // This will clip the right edge's image at half of its width mUrlBarRightEdge = (ImageView) findViewById(R.id.url_bar_right_edge); if (mUrlBarRightEdge != null) { mUrlBarRightEdge.getDrawable().setLevel(5000); } mTitle = (GeckoTextView) findViewById(R.id.url_bar_title); mTitlePadding = mTitle.getPaddingRight(); mTabs = (ShapedButton) findViewById(R.id.tabs); mTabsCounter = (TabCounter) findViewById(R.id.tabs_counter); mBack = (ImageButton) findViewById(R.id.back); mForward = (ImageButton) findViewById(R.id.forward); mForward.setEnabled(false); // initialize the forward button to not be enabled mFavicon = (ImageButton) findViewById(R.id.favicon); if (Build.VERSION.SDK_INT >= 16) mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size)); mSiteSecurity = (ImageButton) findViewById(R.id.site_security); mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE); mActivity.getSiteIdentityPopup().setAnchor(mSiteSecurity); mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner); mStop = (ImageButton) findViewById(R.id.stop); mShadow = (ImageView) findViewById(R.id.shadow); mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout); if (Build.VERSION.SDK_INT >= 16) { mShadow.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } mMenu = (GeckoImageButton) findViewById(R.id.menu); mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon); mActionItemBar = (LinearLayout) findViewById(R.id.menu_items); mHasSoftMenuButton = !HardwareUtils.hasMenuButton(); // We use different layouts on phones and tablets, so adjust the focus // order appropriately. if (HardwareUtils.isTablet()) { mFocusOrder = Arrays.asList(mTabs, mBack, mForward, this, mSiteSecurity, mPageActionLayout, mStop, mActionItemBar, mMenu); } else { mFocusOrder = Arrays.asList(this, mSiteSecurity, mPageActionLayout, mStop, mTabs, mMenu); } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { if (mActivateListener != null) { mActivateListener.onActivate(); } } }); setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { // We don't the context menu while editing if (isEditing()) { return; } MenuInflater inflater = mActivity.getMenuInflater(); inflater.inflate(R.menu.titlebar_contextmenu, menu); String clipboard = Clipboard.getText(); if (TextUtils.isEmpty(clipboard)) { menu.findItem(R.id.pasteandgo).setVisible(false); menu.findItem(R.id.paste).setVisible(false); } Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { String url = tab.getURL(); if (url == null) { menu.findItem(R.id.copyurl).setVisible(false); menu.findItem(R.id.share).setVisible(false); menu.findItem(R.id.add_to_launcher).setVisible(false); } if (!tab.getFeedsEnabled()) { menu.findItem(R.id.subscribe).setVisible(false); } } else { // if there is no tab, remove anything tab dependent menu.findItem(R.id.copyurl).setVisible(false); menu.findItem(R.id.share).setVisible(false); menu.findItem(R.id.add_to_launcher).setVisible(false); menu.findItem(R.id.subscribe).setVisible(false); } menu.findItem(R.id.share).setVisible(!GeckoProfile.get(getContext()).inGuestMode()); } }); mUrlEditText.addTextChangedListener(this); mUrlEditText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() { @Override public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) { // We only want to process one event per tap if (event.getAction() != KeyEvent.ACTION_DOWN) return false; if (keyCode == KeyEvent.KEYCODE_ENTER) { // If the edit text has a composition string, don't submit the text yet. // ENTER is needed to commit the composition string. Editable content = mUrlEditText.getText(); if (!hasCompositionString(content)) { if (mCommitListener != null) { mCommitListener.onCommit(); } return true; } } if (keyCode == KeyEvent.KEYCODE_BACK) { clearFocus(); return true; } return false; } }); mUrlEditText.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) { if (event.getAction() != KeyEvent.ACTION_DOWN) return true; if (mCommitListener != null) { mCommitListener.onCommit(); } return true; } else if (GamepadUtils.isBackKey(event)) { if (mDismissListener != null) { mDismissListener.onDismiss(); } return true; } return false; } }); mUrlEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (v == null) { return; } setSelected(hasFocus); if (hasFocus) { return; } InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); try { imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } catch (NullPointerException e) { Log.e(LOGTAG, "InputMethodManagerService, why are you throwing" + " a NullPointerException? See bug 782096", e); } } }); mUrlEditText.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (Build.VERSION.SDK_INT >= 11) { CustomEditText text = (CustomEditText) v; if (text.getSelectionStart() == text.getSelectionEnd()) return false; return false; } return false; } }); mTabs.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { toggleTabs(); } }); mTabs.setImageLevel(0); mBack.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View view) { Tabs.getInstance().getSelectedTab().doBack(); } }); mBack.setOnLongClickListener(new Button.OnLongClickListener() { @Override public boolean onLongClick(View view) { return Tabs.getInstance().getSelectedTab().showBackHistory(); } }); mForward.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View view) { Tabs.getInstance().getSelectedTab().doForward(); } }); mForward.setOnLongClickListener(new Button.OnLongClickListener() { @Override public boolean onLongClick(View view) { return Tabs.getInstance().getSelectedTab().showForwardHistory(); } }); Button.OnClickListener faviconListener = new Button.OnClickListener() { @Override public void onClick(View view) { if (mSiteSecurity.getVisibility() != View.VISIBLE) return; JSONObject identityData = Tabs.getInstance().getSelectedTab().getIdentityData(); if (identityData == null) { Log.e(LOGTAG, "Selected tab has no identity data"); return; } SiteIdentityPopup siteIdentityPopup = mActivity.getSiteIdentityPopup(); siteIdentityPopup.updateIdentity(identityData); siteIdentityPopup.show(); } }; mFavicon.setOnClickListener(faviconListener); mSiteSecurity.setOnClickListener(faviconListener); mStop.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) tab.doStop(); setProgressVisibility(false); } }); mGo = (ImageButton) findViewById(R.id.go); mGo.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { if (mCommitListener != null) { mCommitListener.onCommit(); } } }); float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width); LinearLayout.LayoutParams siteSecParams = (LinearLayout.LayoutParams) mSiteSecurity.getLayoutParams(); final float scale = getResources().getDisplayMetrics().density; slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f; mLockFadeIn = new AlphaAnimation(0.0f, 1.0f); mLockFadeIn.setAnimationListener(this); mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0); mTitleSlideLeft.setAnimationListener(this); mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0); mTitleSlideRight.setAnimationListener(this); final int lockAnimDuration = 300; mLockFadeIn.setDuration(lockAnimDuration); mTitleSlideLeft.setDuration(lockAnimDuration); mTitleSlideRight.setDuration(lockAnimDuration); if (mHasSoftMenuButton) { mMenu.setVisibility(View.VISIBLE); mMenuIcon.setVisibility(View.VISIBLE); mMenu.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View view) { mActivity.openOptionsMenu(); } }); } } public boolean onKey(int keyCode, KeyEvent event) { if (event.getAction() != KeyEvent.ACTION_DOWN) { return false; } // Galaxy Note sends key events for the stylus that are outside of the // valid keyCode range (see bug 758427) if (keyCode > KeyEvent.getMaxKeyCode()) { return true; } // This method is called only if the key event was not handled // by any of the views, which usually means the edit box lost focus if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { return false; } else if (isEditing()) { final int prevSelStart = mUrlEditText.getSelectionStart(); final int prevSelEnd = mUrlEditText.getSelectionEnd(); // Manually dispatch the key event to the edit text. If selection changed as // a result of the key event, then give focus back to mUrlEditText mUrlEditText.dispatchKeyEvent(event); final int curSelStart = mUrlEditText.getSelectionStart(); final int curSelEnd = mUrlEditText.getSelectionEnd(); if (prevSelStart != curSelStart || prevSelEnd != curSelEnd) { mUrlEditText.requestFocusFromTouch(); // Restore the selection, which gets lost due to the focus switch mUrlEditText.setSelection(curSelStart, curSelEnd); } return true; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { // If the motion event has occured below the toolbar (due to the scroll // offset), let it pass through to the page. if (event != null && event.getY() > getHeight() - getScrollY()) { return false; } return super.onTouchEvent(event); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (h != oldh) { // Post this to happen outside of onSizeChanged, as this may cause // a layout change and relayouts within a layout change don't work. post(new Runnable() { @Override public void run() { mActivity.refreshToolbarHeight(); } }); } } @Override public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { switch(msg) { case TITLE: if (Tabs.getInstance().isSelectedTab(tab)) { updateTitle(); } break; case START: if (Tabs.getInstance().isSelectedTab(tab)) { updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); Boolean showProgress = (Boolean)data; if (showProgress && tab.getState() == Tab.STATE_LOADING) setProgressVisibility(true); setSecurityMode(tab.getSecurityMode()); setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); } break; case STOP: if (Tabs.getInstance().isSelectedTab(tab)) { updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); setProgressVisibility(false); // Reset the title in case we haven't navigated to a new page yet. updateTitle(); } break; case LOADED: if (Tabs.getInstance().isSelectedTab(tab)) { updateTitle(); } break; case RESTORED: // TabCount fixup after OOM case SELECTED: updateTabCount(Tabs.getInstance().getDisplayCount()); mSwitchingTabs = true; // fall through case LOCATION_CHANGE: case LOAD_ERROR: if (Tabs.getInstance().isSelectedTab(tab)) { refresh(); } mSwitchingTabs = false; break; case CLOSED: case ADDED: updateTabCount(Tabs.getInstance().getDisplayCount()); if (Tabs.getInstance().isSelectedTab(tab)) { updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); } break; case FAVICON: if (Tabs.getInstance().isSelectedTab(tab)) { setFavicon(tab.getFavicon()); } break; case SECURITY_CHANGE: if (Tabs.getInstance().isSelectedTab(tab)) { setSecurityMode(tab.getSecurityMode()); } break; case READER_ENABLED: if (Tabs.getInstance().isSelectedTab(tab)) { setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); } break; } } // Return early if we're backspacing through the string, or // have no autocomplete results @Override public void onAutocomplete(final String result) { final String text = mUrlEditText.getText().toString(); if (result == null) { mAutoCompleteResult = ""; return; } if (!result.startsWith(text) || text.equals(result)) { return; } mAutoCompleteResult = result; mUrlEditText.getText().append(result.substring(text.length())); mUrlEditText.setSelection(text.length(), result.length()); } @Override public void afterTextChanged(final Editable s) { final String text = s.toString(); boolean useHandler = false; boolean reuseAutocomplete = false; if (!hasCompositionString(s) && !StringUtils.isSearchQuery(text, false)) { useHandler = true; // If you're hitting backspace (the string is getting smaller // or is unchanged), don't autocomplete. if (mAutoCompletePrefix != null && (mAutoCompletePrefix.length() >= text.length())) { useHandler = false; } else if (mAutoCompleteResult != null && mAutoCompleteResult.startsWith(text)) { // If this text already matches our autocomplete text, autocomplete likely // won't change. Just reuse the old autocomplete value. useHandler = false; reuseAutocomplete = true; } } // If this is the autocomplete text being set, don't run the filter. if (TextUtils.isEmpty(mAutoCompleteResult) || !mAutoCompleteResult.equals(text)) { if (isEditing() && mFilterListener != null) { mFilterListener.onFilter(text, useHandler ? this : null); } mAutoCompletePrefix = text; if (reuseAutocomplete) { onAutocomplete(mAutoCompleteResult); } } // If the edit text has a composition string, don't call updateGoButton(). // That method resets IME and composition state will be broken. if (!hasCompositionString(s) || InputMethods.isGestureKeyboard(mUrlEditText.getContext())) { updateGoButton(text); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // do nothing } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // do nothing } public boolean isVisible() { return getScrollY() == 0; } public void setNextFocusDownId(int nextId) { super.setNextFocusDownId(nextId); mTabs.setNextFocusDownId(nextId); mBack.setNextFocusDownId(nextId); mForward.setNextFocusDownId(nextId); mFavicon.setNextFocusDownId(nextId); mStop.setNextFocusDownId(nextId); mSiteSecurity.setNextFocusDownId(nextId); mPageActionLayout.setNextFocusDownId(nextId); mMenu.setNextFocusDownId(nextId); } @Override public void onAnimationStart(Animation animation) { if (animation.equals(mLockFadeIn)) { if (mSiteSecurityVisible) mSiteSecurity.setVisibility(View.VISIBLE); } else if (animation.equals(mTitleSlideLeft)) { // These two animations may be scheduled to start while the forward // animation is occurring. If we're showing the site security icon, make // sure it doesn't take any space during the forward transition. mSiteSecurity.setVisibility(View.GONE); } else if (animation.equals(mTitleSlideRight)) { // If we're hiding the icon, make sure that we keep its padding // in place during the forward transition mSiteSecurity.setVisibility(View.INVISIBLE); } } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (animation.equals(mTitleSlideRight)) { mSiteSecurity.startAnimation(mLockFadeIn); } } private int getUrlBarEntryTranslation() { return getWidth() - mUrlBarEntry.getRight(); } private int getUrlBarCurveTranslation() { return getWidth() - mTabs.getLeft(); } private static boolean hasCompositionString(Editable content) { Object[] spans = content.getSpans(0, content.length(), Object.class); if (spans != null) { for (Object span : spans) { if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { // Found composition string. return true; } } } return false; } private void addTab() { mActivity.addTab(); } private void toggleTabs() { if (mActivity.areTabsShown()) { if (mActivity.hasTabsSideBar()) mActivity.hideTabs(); } else { // hide the virtual keyboard InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTabs.getWindowToken(), 0); Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { if (!tab.isPrivate()) mActivity.showNormalTabs(); else mActivity.showPrivateTabs(); } } } public void updateTabCountAndAnimate(int count) { // Don't animate if the toolbar is hidden. if (!isVisible()) { updateTabCount(count); return; } // If toolbar is in edit mode, this means the entry is expanded and the // tabs button is translated offscreen. Don't trigger tabs counter // updates until the tabs button is back on screen. // See stopEditing() if (!isEditing()) { mTabsCounter.setCount(count); mTabs.setContentDescription((count > 1) ? mActivity.getString(R.string.num_tabs, count) : mActivity.getString(R.string.one_tab)); } } public void updateTabCount(int count) { // If toolbar is in edit mode, this means the entry is expanded and the // tabs button is translated offscreen. Don't trigger tabs counter // updates until the tabs button is back on screen. // See stopEditing() if (isEditing()) { return; } // Set TabCounter based on visibility if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0) { mTabsCounter.setCountWithAnimation(count); } else { mTabsCounter.setCount(count); } // Update A11y information mTabs.setContentDescription((count > 1) ? mActivity.getString(R.string.num_tabs, count) : mActivity.getString(R.string.one_tab)); } public void setProgressVisibility(boolean visible) { // The "Throbber start" and "Throbber stop" log messages in this method // are needed by S1/S2 tests (http://mrcote.info/phonedash/#). // See discussion in Bug 804457. Bug 805124 tracks paring these down. if (visible) { mFavicon.setImageResource(R.drawable.progress_spinner); //To stop the glitch caused by mutiple start() calls. if (!mSpinnerVisible) { setPageActionVisibility(true); mFavicon.setAnimation(mProgressSpinner); mProgressSpinner.start(); mSpinnerVisible = true; } Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start"); } else { Tab selectedTab = Tabs.getInstance().getSelectedTab(); if (selectedTab != null) setFavicon(selectedTab.getFavicon()); if (mSpinnerVisible) { setPageActionVisibility(false); mFavicon.setAnimation(null); mProgressSpinner.cancel(); mSpinnerVisible = false; } Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop"); } } public void setPageActionVisibility(boolean isLoading) { // Handle the loading mode page actions mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE); // Handle the viewing mode page actions setSiteSecurityVisibility(mShowSiteSecurity && !isLoading); boolean inReaderMode = false; Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) inReaderMode = ReaderModeUtils.isAboutReader(tab.getURL()); mPageActionLayout.setVisibility(!isLoading ? 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, (!isLoading && !(mShowReader || inReaderMode) ? mTitlePadding : 0), 0); updateFocusOrder(); } private void setSiteSecurityVisibility(final boolean visible) { if (visible == mSiteSecurityVisible) return; mSiteSecurityVisible = visible; if (mSwitchingTabs) { mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE); return; } mTitle.clearAnimation(); mSiteSecurity.clearAnimation(); // If any of these animations were cancelled as a result of the // clearAnimation() calls above, we need to reset them. mLockFadeIn.reset(); mTitleSlideLeft.reset(); mTitleSlideRight.reset(); if (mForwardAnim != null) { long delay = mForwardAnim.getRemainingTime(); mTitleSlideRight.setStartOffset(delay); mTitleSlideLeft.setStartOffset(delay); } else { mTitleSlideRight.setStartOffset(0); mTitleSlideLeft.setStartOffset(0); } mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft); } private void updateFocusOrder() { View prevView = null; // If the element that has focus becomes disabled or invisible, focus // is given to the URL bar. boolean needsNewFocus = false; for (View view : mFocusOrder) { if (view.getVisibility() != View.VISIBLE || !view.isEnabled()) { if (view.hasFocus()) { needsNewFocus = true; } continue; } if (view == mActionItemBar) { final int childCount = mActionItemBar.getChildCount(); for (int child = 0; child < childCount; child++) { View childView = mActionItemBar.getChildAt(child); if (prevView != null) { childView.setNextFocusLeftId(prevView.getId()); prevView.setNextFocusRightId(childView.getId()); } prevView = childView; } } else { if (prevView != null) { view.setNextFocusLeftId(prevView.getId()); prevView.setNextFocusRightId(view.getId()); } prevView = view; } } if (needsNewFocus) { requestFocus(); } } public void setShadowVisibility(boolean visible) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab == null) { return; } String url = tab.getURL(); if ((mShadow.getVisibility() == View.VISIBLE) != visible) { mShadow.setVisibility(visible ? View.VISIBLE : View.GONE); } } public void onEditSuggestion(String suggestion) { if (!isEditing()) { return; } mUrlEditText.setText(suggestion); mUrlEditText.setSelection(mUrlEditText.getText().length()); mUrlEditText.requestFocus(); showSoftInput(); } private void setTitle(CharSequence title) { mTitle.setText(title); setContentDescription(title != null ? title : mTitle.getHint()); } // Sets the toolbar title according to the selected tab, obeying the mShowUrl prference. private void updateTitle() { Tab tab = Tabs.getInstance().getSelectedTab(); // Keep the title unchanged if there's no selected tab, or if the tab is entering reader mode. if (tab == null || tab.isEnteringReaderMode()) { return; } String url = tab.getURL(); if (!isEditing()) { mUrlEditText.setText(url); } // Setting a null title will ensure we just see the "Enter Search or Address" placeholder text. if ("about:home".equals(url) || "about:privatebrowsing".equals(url)) { setTitle(null); return; } // Show the about:blocked page title in red, regardless of prefs if (tab.getErrorType() == Tab.ErrorType.BLOCKED) { String title = tab.getDisplayTitle(); SpannableStringBuilder builder = new SpannableStringBuilder(title); builder.setSpan(mBlockedColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); setTitle(builder); return; } // If the pref to show the URL isn't set, just use the tab's display title. if (!mShowUrl || url == null) { setTitle(tab.getDisplayTitle()); return; } url = StringUtils.stripScheme(url); CharSequence title = StringUtils.stripCommonSubdomains(url); String baseDomain = tab.getBaseDomain(); if (!TextUtils.isEmpty(baseDomain)) { SpannableStringBuilder builder = new SpannableStringBuilder(title); int index = title.toString().indexOf(baseDomain); if (index > -1) { builder.setSpan(mUrlColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); builder.setSpan(tab.isPrivate() ? mPrivateDomainColor : mDomainColor, index, index+baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); title = builder; } } setTitle(title); } private void setFavicon(Bitmap image) { if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING) return; if (image != null) { image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false); mFavicon.setImageBitmap(image); } else { mFavicon.setImageResource(R.drawable.favicon); } } private void setSecurityMode(String mode) { int imageLevel = SiteIdentityPopup.getSecurityImageLevel(mode); mSiteSecurity.setImageLevel(imageLevel); mShowSiteSecurity = (imageLevel != SiteIdentityPopup.LEVEL_UKNOWN); setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); } public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) { if (!tabsAreShown) { PropertyAnimator buttonsAnimator = new PropertyAnimator(animator.getDuration(), sButtonsInterpolator); buttonsAnimator.attach(mTabsCounter, PropertyAnimator.Property.ALPHA, 1.0f); if (mHasSoftMenuButton && !HardwareUtils.isTablet()) { buttonsAnimator.attach(mMenuIcon, PropertyAnimator.Property.ALPHA, 1.0f); } buttonsAnimator.start(); return; } ViewHelper.setAlpha(mTabsCounter, 0.0f); if (mHasSoftMenuButton && !HardwareUtils.isTablet()) { ViewHelper.setAlpha(mMenuIcon, 0.0f); } } public void finishTabsAnimation(boolean tabsAreShown) { if (tabsAreShown) { return; } PropertyAnimator animator = new PropertyAnimator(150); animator.attach(mTabsCounter, PropertyAnimator.Property.ALPHA, 1.0f); if (mHasSoftMenuButton && !HardwareUtils.isTablet()) { animator.attach(mMenuIcon, PropertyAnimator.Property.ALPHA, 1.0f); } animator.start(); } public void setOnActivateListener(OnActivateListener listener) { mActivateListener = listener; } public void setOnCommitListener(OnCommitListener listener) { mCommitListener = listener; } public void setOnDismissListener(OnDismissListener listener) { mDismissListener = listener; } public void setOnFilterListener(OnFilterListener listener) { mFilterListener = listener; } private void showSoftInput() { InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mUrlEditText, InputMethodManager.SHOW_IMPLICIT); } private void showUrlEditContainer() { setUrlEditContainerVisibility(true, null); } private void showUrlEditContainer(PropertyAnimator animator) { setUrlEditContainerVisibility(true, animator); } private void hideUrlEditContainer() { setUrlEditContainerVisibility(false, null); } private void hideUrlEditContainer(PropertyAnimator animator) { setUrlEditContainerVisibility(false, animator); } private void setUrlEditContainerVisibility(final boolean showEditContainer, PropertyAnimator animator) { final View viewToShow = (showEditContainer ? mUrlEditContainer : mUrlDisplayContainer); final View viewToHide = (showEditContainer ? mUrlDisplayContainer : mUrlEditContainer); if (animator == null) { viewToHide.setVisibility(View.GONE); viewToShow.setVisibility(View.VISIBLE); if (showEditContainer) { mUrlEditText.requestFocus(); showSoftInput(); } return; } ViewHelper.setAlpha(viewToShow, 0.0f); animator.attach(viewToShow, PropertyAnimator.Property.ALPHA, 1.0f); animator.attach(viewToHide, PropertyAnimator.Property.ALPHA, 0.0f); animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { viewToShow.setVisibility(View.VISIBLE); if (showEditContainer) { ViewHelper.setAlpha(mGo, 0.0f); mUrlEditText.requestFocus(); } } @Override public void onPropertyAnimationEnd() { viewToHide.setVisibility(View.GONE); ViewHelper.setAlpha(viewToHide, 1.0f); if (showEditContainer) { ViewHelper.setAlpha(mGo, 1.0f); showSoftInput(); } } }); } /** * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new * tab button). Note that selection state is independent of editing mode. */ public boolean isEditing() { return mIsEditing; } public void startEditing(String url, PropertyAnimator animator) { if (isEditing()) { return; } mUrlEditText.setText(url != null ? url : ""); mIsEditing = true; final int entryTranslation = getUrlBarEntryTranslation(); final int curveTranslation = getUrlBarCurveTranslation(); // This animation doesn't make much sense in a sidebar UI if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { showUrlEditContainer(); if (!HardwareUtils.isTablet()) { if (mUrlBarRightEdge != null) { ViewHelper.setTranslationX(mUrlBarRightEdge, entryTranslation); } ViewHelper.setTranslationX(mTabs, curveTranslation); ViewHelper.setTranslationX(mTabsCounter, curveTranslation); ViewHelper.setTranslationX(mActionItemBar, curveTranslation); if (mHasSoftMenuButton) { ViewHelper.setTranslationX(mMenu, curveTranslation); ViewHelper.setTranslationX(mMenuIcon, curveTranslation); } } return; } if (mAnimatingEntry) return; // Highlight the toolbar from the start of the animation. setSelected(true); // Hide page actions/stop buttons immediately ViewHelper.setAlpha(mPageActionLayout, 0); ViewHelper.setAlpha(mStop, 0); // Slide the right side elements of the toolbar if (mUrlBarRightEdge != null) { animator.attach(mUrlBarRightEdge, PropertyAnimator.Property.TRANSLATION_X, entryTranslation); } animator.attach(mTabs, PropertyAnimator.Property.TRANSLATION_X, curveTranslation); animator.attach(mTabsCounter, PropertyAnimator.Property.TRANSLATION_X, curveTranslation); animator.attach(mActionItemBar, PropertyAnimator.Property.TRANSLATION_X, curveTranslation); if (mHasSoftMenuButton) { animator.attach(mMenu, PropertyAnimator.Property.TRANSLATION_X, curveTranslation); animator.attach(mMenuIcon, PropertyAnimator.Property.TRANSLATION_X, curveTranslation); } showUrlEditContainer(animator); animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { } @Override public void onPropertyAnimationEnd() { mAnimatingEntry = false; } }); mAnimatingEntry = true; } /** * Exits edit mode without updating the toolbar title. * * @return the url that was entered */ public String cancelEdit() { return stopEditing(); } /** * Exits edit mode, updating the toolbar title with the url that was just entered. * * @return the url that was entered */ public String commitEdit() { final String url = stopEditing(); if (!TextUtils.isEmpty(url)) { setTitle(url); } return url; } private String stopEditing() { final String url = mUrlEditText.getText().toString(); if (!isEditing()) { return url; } mIsEditing = false; if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) { hideUrlEditContainer(); updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); if (!HardwareUtils.isTablet()) { if (mUrlBarRightEdge != null) { ViewHelper.setTranslationX(mUrlBarRightEdge, 0); } ViewHelper.setTranslationX(mTabs, 0); ViewHelper.setTranslationX(mTabsCounter, 0); ViewHelper.setTranslationX(mActionItemBar, 0); if (mHasSoftMenuButton) { ViewHelper.setTranslationX(mMenu, 0); ViewHelper.setTranslationX(mMenuIcon, 0); } } return url; } final PropertyAnimator contentAnimator = new PropertyAnimator(250); contentAnimator.setUseHardwareLayer(false); // Shrink the urlbar entry back to its original size if (mUrlBarRightEdge != null) { contentAnimator.attach(mUrlBarRightEdge, PropertyAnimator.Property.TRANSLATION_X, 0); } contentAnimator.attach(mTabs, PropertyAnimator.Property.TRANSLATION_X, 0); contentAnimator.attach(mTabsCounter, PropertyAnimator.Property.TRANSLATION_X, 0); contentAnimator.attach(mActionItemBar, PropertyAnimator.Property.TRANSLATION_X, 0); if (mHasSoftMenuButton) { contentAnimator.attach(mMenu, PropertyAnimator.Property.TRANSLATION_X, 0); contentAnimator.attach(mMenuIcon, PropertyAnimator.Property.TRANSLATION_X, 0); } hideUrlEditContainer(contentAnimator); contentAnimator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { } @Override public void onPropertyAnimationEnd() { PropertyAnimator buttonsAnimator = new PropertyAnimator(300); // Fade toolbar buttons (page actions, stop) after the entry // is schrunk back to its original size. buttonsAnimator.attach(mPageActionLayout, PropertyAnimator.Property.ALPHA, 1); buttonsAnimator.attach(mStop, PropertyAnimator.Property.ALPHA, 1); buttonsAnimator.start(); mAnimatingEntry = false; // Trigger animation to update the tabs counter once the // tabs button is back on screen. updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount()); } }); mAnimatingEntry = true; contentAnimator.start(); return url; } private void updateGoButton(String text) { if (text.length() == 0) { mGo.setVisibility(View.GONE); return; } mGo.setVisibility(View.VISIBLE); int imageResource = R.drawable.ic_url_bar_go; String contentDescription = mActivity.getString(R.string.go); int imeAction = EditorInfo.IME_ACTION_GO; int actionBits = mUrlEditText.getImeOptions() & EditorInfo.IME_MASK_ACTION; if (StringUtils.isSearchQuery(text, actionBits == EditorInfo.IME_ACTION_SEARCH)) { imageResource = R.drawable.ic_url_bar_search; contentDescription = mActivity.getString(R.string.search); imeAction = EditorInfo.IME_ACTION_SEARCH; } InputMethodManager imm = InputMethods.getInputMethodManager(mUrlEditText.getContext()); if (imm == null) { return; } boolean restartInput = false; if (actionBits != imeAction) { int optionBits = mUrlEditText.getImeOptions() & ~EditorInfo.IME_MASK_ACTION; mUrlEditText.setImeOptions(optionBits | imeAction); mDelayRestartInput = (imeAction == EditorInfo.IME_ACTION_GO) && (InputMethods.shouldDelayUrlBarUpdate(mUrlEditText.getContext())); if (!mDelayRestartInput) { restartInput = true; } } else if (mDelayRestartInput) { // Only call delayed restartInput when actionBits == imeAction // so if there are two restarts in a row, the first restarts will // be discarded and the second restart will be properly delayed mDelayRestartInput = false; restartInput = true; } if (restartInput) { updateKeyboardInputType(); imm.restartInput(mUrlEditText); mGo.setImageResource(imageResource); mGo.setContentDescription(contentDescription); } } private void updateKeyboardInputType() { // If the user enters a space, then we know they are entering search terms, not a URL. // We can then switch to text mode so, // 1) the IME auto-inserts spaces between words // 2) the IME doesn't reset input keyboard to Latin keyboard. String text = mUrlEditText.getText().toString(); int currentInputType = mUrlEditText.getInputType(); int newInputType = StringUtils.isSearchQuery(text, false) ? (currentInputType & ~InputType.TYPE_TEXT_VARIATION_URI) // Text mode : (currentInputType | InputType.TYPE_TEXT_VARIATION_URI); // URL mode if (newInputType != currentInputType) { mUrlEditText.setRawInputType(newInputType); } } public void updateBackButton(boolean enabled) { Drawable drawable = mBack.getDrawable(); if (drawable != null) drawable.setAlpha(enabled ? 255 : 77); mBack.setEnabled(enabled); } public void updateForwardButton(final boolean enabled) { if (mForward.isEnabled() == enabled) return; // Save the state on the forward button so that we can skip animations // when there's nothing to change mForward.setEnabled(enabled); if (mForward.getVisibility() != View.VISIBLE) return; // We want the forward button to show immediately when switching tabs mForwardAnim = new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION); final int width = mForward.getWidth() / 2; mForwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { @Override public void onPropertyAnimationStart() { if (!enabled) { // 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 ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mUrlDisplayContainer.getLayoutParams(); layoutParams.leftMargin = 0; // Do the same on the URL edit container layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams(); layoutParams.leftMargin = 0; 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() { if (enabled) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mUrlDisplayContainer.getLayoutParams(); layoutParams.leftMargin = mUrlBarViewOffset; layoutParams = (ViewGroup.MarginLayoutParams)mUrlEditContainer.getLayoutParams(); layoutParams.leftMargin = mUrlBarViewOffset; ViewHelper.setTranslationX(mTitle, 0); ViewHelper.setTranslationX(mFavicon, 0); ViewHelper.setTranslationX(mSiteSecurity, 0); } ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mForward.getLayoutParams(); layoutParams.leftMargin = mDefaultForwardMargin + (mForward.isEnabled() ? width : 0); ViewHelper.setTranslationX(mForward, 0); requestLayout(); mForwardAnim = null; } }); prepareForwardAnimation(mForwardAnim, enabled, width); mForwardAnim.start(); } private void prepareForwardAnimation(PropertyAnimator anim, boolean enabled, int width) { if (!enabled) { anim.attach(mForward, PropertyAnimator.Property.TRANSLATION_X, -width); anim.attach(mForward, PropertyAnimator.Property.ALPHA, 0); anim.attach(mTitle, PropertyAnimator.Property.TRANSLATION_X, 0); anim.attach(mFavicon, PropertyAnimator.Property.TRANSLATION_X, 0); anim.attach(mSiteSecurity, PropertyAnimator.Property.TRANSLATION_X, 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, mUrlBarViewOffset); ViewHelper.setTranslationX(mFavicon, mUrlBarViewOffset); ViewHelper.setTranslationX(mSiteSecurity, mUrlBarViewOffset); } else { anim.attach(mForward, PropertyAnimator.Property.TRANSLATION_X, width); anim.attach(mForward, PropertyAnimator.Property.ALPHA, 1); anim.attach(mTitle, PropertyAnimator.Property.TRANSLATION_X, mUrlBarViewOffset); anim.attach(mFavicon, PropertyAnimator.Property.TRANSLATION_X, mUrlBarViewOffset); anim.attach(mSiteSecurity, PropertyAnimator.Property.TRANSLATION_X, mUrlBarViewOffset); } } @Override public void addActionItem(View actionItem) { mActionItemBar.addView(actionItem); } @Override public void removeActionItem(View actionItem) { mActionItemBar.removeView(actionItem); } public void show() { setVisibility(View.VISIBLE); } public void hide() { setVisibility(View.GONE); } public void refresh() { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { updateTitle(); setFavicon(tab.getFavicon()); setProgressVisibility(tab.getState() == Tab.STATE_LOADING); setSecurityMode(tab.getSecurityMode()); setPageActionVisibility(mStop.getVisibility() == View.VISIBLE); updateBackButton(tab.canDoBack()); updateForwardButton(tab.canDoForward()); final boolean isPrivate = tab.isPrivate(); mUrlBarBackground.setPrivateMode(isPrivate); setPrivateMode(isPrivate); mTabs.setPrivateMode(isPrivate); mTitle.setPrivateMode(isPrivate); mMenu.setPrivateMode(isPrivate); mMenuIcon.setPrivateMode(isPrivate); mUrlEditText.setPrivateMode(isPrivate); if (mBack instanceof BackButton) ((BackButton) mBack).setPrivateMode(isPrivate); if (mForward instanceof ForwardButton) ((ForwardButton) mForward).setPrivateMode(isPrivate); } } public void onDestroy() { if (mPrefObserverId != null) { PrefsHelper.removeObserver(mPrefObserverId); mPrefObserverId = null; } Tabs.unregisterOnTabsChangedListener(this); unregisterEventListener("Reader:Click"); unregisterEventListener("Reader:LongClick"); } public boolean openOptionsMenu() { if (!mHasSoftMenuButton) return false; // Initialize the popup. if (mMenuPopup == null) { View panel = mActivity.getMenuPanel(); mMenuPopup = new MenuPopup(mActivity); mMenuPopup.setPanelView(panel); mMenuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { mActivity.onOptionsMenuClosed(null); } }); } GeckoAppShell.getGeckoInterface().invalidateOptionsMenu(); if (!mMenuPopup.isShowing()) mMenuPopup.showAsDropDown(mMenu); return true; } public boolean closeOptionsMenu() { if (!mHasSoftMenuButton) return false; if (mMenuPopup != null && mMenuPopup.isShowing()) mMenuPopup.dismiss(); return true; } protected void registerEventListener(String event) { GeckoAppShell.getEventDispatcher().registerEventListener(event, this); } protected void unregisterEventListener(String event) { GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); } @Override public void handleMessage(String event, JSONObject message) { if (event.equals("Reader:Click")) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { tab.toggleReaderMode(); } } else if (event.equals("Reader:LongClick")) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { tab.addToReadingList(); } } } }