|
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/. */ |
|
5 |
|
6 package org.mozilla.gecko.toolbar; |
|
7 |
|
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; |
|
21 |
|
22 import org.json.JSONObject; |
|
23 |
|
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; |
|
45 |
|
46 import java.util.Arrays; |
|
47 import java.util.EnumSet; |
|
48 import java.util.List; |
|
49 |
|
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 { |
|
67 |
|
68 private static final String LOGTAG = "GeckoToolbarDisplayLayout"; |
|
69 |
|
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, |
|
78 |
|
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 } |
|
84 |
|
85 private enum UIMode { |
|
86 PROGRESS, |
|
87 DISPLAY |
|
88 } |
|
89 |
|
90 interface OnStopListener { |
|
91 public Tab onStop(); |
|
92 } |
|
93 |
|
94 interface OnTitleChangeListener { |
|
95 public void onTitleChange(CharSequence title); |
|
96 } |
|
97 |
|
98 private final BrowserApp mActivity; |
|
99 |
|
100 private UIMode mUiMode; |
|
101 |
|
102 private ThemedTextView mTitle; |
|
103 private int mTitlePadding; |
|
104 private ToolbarTitlePrefs mTitlePrefs; |
|
105 private OnTitleChangeListener mTitleChangeListener; |
|
106 |
|
107 private ImageButton mSiteSecurity; |
|
108 private boolean mSiteSecurityVisible; |
|
109 |
|
110 // To de-bounce sets. |
|
111 private Bitmap mLastFavicon; |
|
112 private ImageButton mFavicon; |
|
113 private int mFaviconSize; |
|
114 |
|
115 private ImageButton mStop; |
|
116 private OnStopListener mStopListener; |
|
117 |
|
118 private PageActionLayout mPageActionLayout; |
|
119 |
|
120 private AlphaAnimation mLockFadeIn; |
|
121 private TranslateAnimation mTitleSlideLeft; |
|
122 private TranslateAnimation mTitleSlideRight; |
|
123 |
|
124 private SiteIdentityPopup mSiteIdentityPopup; |
|
125 private SecurityMode mSecurityMode; |
|
126 |
|
127 private PropertyAnimator mForwardAnim; |
|
128 |
|
129 private final ForegroundColorSpan mUrlColor; |
|
130 private final ForegroundColorSpan mBlockedColor; |
|
131 private final ForegroundColorSpan mDomainColor; |
|
132 private final ForegroundColorSpan mPrivateDomainColor; |
|
133 |
|
134 public ToolbarDisplayLayout(Context context, AttributeSet attrs) { |
|
135 super(context, attrs); |
|
136 setOrientation(HORIZONTAL); |
|
137 |
|
138 mActivity = (BrowserApp) context; |
|
139 |
|
140 LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this); |
|
141 |
|
142 mTitle = (ThemedTextView) findViewById(R.id.url_bar_title); |
|
143 mTitlePadding = mTitle.getPaddingRight(); |
|
144 |
|
145 final Resources res = getResources(); |
|
146 |
|
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)); |
|
151 |
|
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)); |
|
157 |
|
158 mSiteSecurity = (ImageButton) findViewById(R.id.site_security); |
|
159 mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE); |
|
160 |
|
161 mSiteIdentityPopup = new SiteIdentityPopup(mActivity); |
|
162 mSiteIdentityPopup.setAnchor(mSiteSecurity); |
|
163 |
|
164 mStop = (ImageButton) findViewById(R.id.stop); |
|
165 mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout); |
|
166 } |
|
167 |
|
168 @Override |
|
169 public void onAttachedToWindow() { |
|
170 mTitlePrefs = new ToolbarTitlePrefs(); |
|
171 |
|
172 Button.OnClickListener faviconListener = new Button.OnClickListener() { |
|
173 @Override |
|
174 public void onClick(View view) { |
|
175 if (mSiteSecurity.getVisibility() != View.VISIBLE) { |
|
176 return; |
|
177 } |
|
178 |
|
179 mSiteIdentityPopup.show(); |
|
180 } |
|
181 }; |
|
182 |
|
183 mFavicon.setOnClickListener(faviconListener); |
|
184 mSiteSecurity.setOnClickListener(faviconListener); |
|
185 |
|
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 }); |
|
199 |
|
200 float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width); |
|
201 |
|
202 LayoutParams siteSecParams = (LayoutParams) mSiteSecurity.getLayoutParams(); |
|
203 final float scale = getResources().getDisplayMetrics().density; |
|
204 slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f; |
|
205 |
|
206 mLockFadeIn = new AlphaAnimation(0.0f, 1.0f); |
|
207 mLockFadeIn.setAnimationListener(this); |
|
208 |
|
209 mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0); |
|
210 mTitleSlideLeft.setAnimationListener(this); |
|
211 |
|
212 mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0); |
|
213 mTitleSlideRight.setAnimationListener(this); |
|
214 |
|
215 final int lockAnimDuration = 300; |
|
216 mLockFadeIn.setDuration(lockAnimDuration); |
|
217 mTitleSlideLeft.setDuration(lockAnimDuration); |
|
218 mTitleSlideRight.setDuration(lockAnimDuration); |
|
219 } |
|
220 |
|
221 @Override |
|
222 public void onDetachedFromWindow() { |
|
223 mTitlePrefs.close(); |
|
224 } |
|
225 |
|
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 } |
|
242 |
|
243 @Override |
|
244 public void onAnimationRepeat(Animation animation) { |
|
245 } |
|
246 |
|
247 @Override |
|
248 public void onAnimationEnd(Animation animation) { |
|
249 if (animation.equals(mTitleSlideRight)) { |
|
250 mSiteSecurity.startAnimation(mLockFadeIn); |
|
251 } |
|
252 } |
|
253 |
|
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 } |
|
261 |
|
262 void updateFromTab(Tab tab, EnumSet<UpdateFlags> flags) { |
|
263 if (flags.contains(UpdateFlags.TITLE)) { |
|
264 updateTitle(tab); |
|
265 } |
|
266 |
|
267 if (flags.contains(UpdateFlags.FAVICON)) { |
|
268 updateFavicon(tab); |
|
269 } |
|
270 |
|
271 if (flags.contains(UpdateFlags.SITE_IDENTITY)) { |
|
272 updateSiteIdentity(tab, flags); |
|
273 } |
|
274 |
|
275 if (flags.contains(UpdateFlags.PROGRESS)) { |
|
276 updateProgress(tab, flags); |
|
277 } |
|
278 |
|
279 if (flags.contains(UpdateFlags.PRIVATE_MODE)) { |
|
280 mTitle.setPrivateMode(tab != null && tab.isPrivate()); |
|
281 } |
|
282 } |
|
283 |
|
284 void setTitle(CharSequence title) { |
|
285 mTitle.setText(title); |
|
286 |
|
287 if (mTitleChangeListener != null) { |
|
288 mTitleChangeListener.onTitleChange(title); |
|
289 } |
|
290 } |
|
291 |
|
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 } |
|
298 |
|
299 final String url = tab.getURL(); |
|
300 |
|
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 } |
|
307 |
|
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(); |
|
311 |
|
312 final SpannableStringBuilder builder = new SpannableStringBuilder(title); |
|
313 builder.setSpan(mBlockedColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); |
|
314 |
|
315 setTitle(builder); |
|
316 return; |
|
317 } |
|
318 |
|
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 } |
|
324 |
|
325 CharSequence title = url; |
|
326 if (mTitlePrefs.shouldTrimUrls()) { |
|
327 title = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url)); |
|
328 } |
|
329 |
|
330 final String baseDomain = tab.getBaseDomain(); |
|
331 if (!TextUtils.isEmpty(baseDomain)) { |
|
332 final SpannableStringBuilder builder = new SpannableStringBuilder(title); |
|
333 |
|
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); |
|
339 |
|
340 title = builder; |
|
341 } |
|
342 } |
|
343 |
|
344 setTitle(title); |
|
345 } |
|
346 |
|
347 private void updateFavicon(Tab tab) { |
|
348 if (tab == null) { |
|
349 mFavicon.setImageDrawable(null); |
|
350 return; |
|
351 } |
|
352 |
|
353 Bitmap image = tab.getFavicon(); |
|
354 |
|
355 if (image != null && image == mLastFavicon) { |
|
356 Log.d(LOGTAG, "Ignoring favicon: new image is identical to previous one."); |
|
357 return; |
|
358 } |
|
359 |
|
360 // Cache the original so we can debounce without scaling |
|
361 mLastFavicon = image; |
|
362 |
|
363 Log.d(LOGTAG, "updateFavicon(" + image + ")"); |
|
364 |
|
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 } |
|
372 |
|
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 } |
|
380 |
|
381 mSiteIdentityPopup.setSiteIdentity(siteIdentity); |
|
382 |
|
383 final SecurityMode securityMode; |
|
384 if (siteIdentity == null) { |
|
385 securityMode = SecurityMode.UNKNOWN; |
|
386 } else { |
|
387 securityMode = siteIdentity.getSecurityMode(); |
|
388 } |
|
389 |
|
390 if (mSecurityMode != securityMode) { |
|
391 mSecurityMode = securityMode; |
|
392 mSiteSecurity.setImageLevel(mSecurityMode.ordinal()); |
|
393 updatePageActions(flags); |
|
394 } |
|
395 } |
|
396 |
|
397 private void updateProgress(Tab tab, EnumSet<UpdateFlags> flags) { |
|
398 final boolean shouldShowThrobber = (tab != null && |
|
399 tab.getState() == Tab.STATE_LOADING); |
|
400 |
|
401 updateUiMode(tab, shouldShowThrobber ? UIMode.PROGRESS : UIMode.DISPLAY, flags); |
|
402 } |
|
403 |
|
404 private void updateUiMode(Tab tab, UIMode uiMode, EnumSet<UpdateFlags> flags) { |
|
405 if (mUiMode == uiMode) { |
|
406 return; |
|
407 } |
|
408 |
|
409 mUiMode = uiMode; |
|
410 |
|
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 } |
|
419 |
|
420 updatePageActions(flags); |
|
421 } |
|
422 |
|
423 private void updatePageActions(EnumSet<UpdateFlags> flags) { |
|
424 final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS); |
|
425 |
|
426 mStop.setVisibility(isShowingProgress ? View.VISIBLE : View.GONE); |
|
427 mPageActionLayout.setVisibility(!isShowingProgress ? View.VISIBLE : View.GONE); |
|
428 |
|
429 boolean shouldShowSiteSecurity = (!isShowingProgress && |
|
430 mSecurityMode != SecurityMode.UNKNOWN); |
|
431 |
|
432 setSiteSecurityVisibility(shouldShowSiteSecurity, flags); |
|
433 |
|
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 } |
|
439 |
|
440 private void setSiteSecurityVisibility(boolean visible, EnumSet<UpdateFlags> flags) { |
|
441 if (visible == mSiteSecurityVisible) { |
|
442 return; |
|
443 } |
|
444 |
|
445 mSiteSecurityVisible = visible; |
|
446 |
|
447 mTitle.clearAnimation(); |
|
448 mSiteSecurity.clearAnimation(); |
|
449 |
|
450 if (flags.contains(UpdateFlags.DISABLE_ANIMATIONS)) { |
|
451 mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE); |
|
452 return; |
|
453 } |
|
454 |
|
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(); |
|
460 |
|
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 } |
|
469 |
|
470 mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft); |
|
471 } |
|
472 |
|
473 List<View> getFocusOrder() { |
|
474 return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop); |
|
475 } |
|
476 |
|
477 void setOnStopListener(OnStopListener listener) { |
|
478 mStopListener = listener; |
|
479 } |
|
480 |
|
481 void setOnTitleChangeListener(OnTitleChangeListener listener) { |
|
482 mTitleChangeListener = listener; |
|
483 } |
|
484 |
|
485 View getDoorHangerAnchor() { |
|
486 return mFavicon; |
|
487 } |
|
488 |
|
489 void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) { |
|
490 mForwardAnim = anim; |
|
491 |
|
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); |
|
502 |
|
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 } |
|
521 |
|
522 void finishForwardAnimation() { |
|
523 ViewHelper.setTranslationX(mTitle, 0); |
|
524 ViewHelper.setTranslationX(mFavicon, 0); |
|
525 ViewHelper.setTranslationX(mSiteSecurity, 0); |
|
526 |
|
527 mForwardAnim = null; |
|
528 } |
|
529 |
|
530 void prepareStartEditingAnimation() { |
|
531 // Hide page actions/stop buttons immediately |
|
532 ViewHelper.setAlpha(mPageActionLayout, 0); |
|
533 ViewHelper.setAlpha(mStop, 0); |
|
534 } |
|
535 |
|
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); |
|
542 |
|
543 anim.attach(mStop, |
|
544 PropertyAnimator.Property.ALPHA, |
|
545 1); |
|
546 } |
|
547 |
|
548 boolean dismissSiteIdentityPopup() { |
|
549 if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) { |
|
550 mSiteIdentityPopup.dismiss(); |
|
551 return true; |
|
552 } |
|
553 |
|
554 return false; |
|
555 } |
|
556 } |