OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.omnibox; |
| 6 |
| 7 import static org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type.HISTORY
_URL; |
| 8 |
| 9 import android.annotation.SuppressLint; |
| 10 import android.content.Context; |
| 11 import android.content.DialogInterface; |
| 12 import android.content.res.TypedArray; |
| 13 import android.graphics.Bitmap; |
| 14 import android.graphics.Canvas; |
| 15 import android.graphics.Color; |
| 16 import android.graphics.PorterDuff; |
| 17 import android.graphics.drawable.Drawable; |
| 18 import android.support.v7.app.AlertDialog; |
| 19 import android.text.Spannable; |
| 20 import android.text.SpannableString; |
| 21 import android.text.TextPaint; |
| 22 import android.text.TextUtils; |
| 23 import android.text.style.StyleSpan; |
| 24 import android.view.MotionEvent; |
| 25 import android.view.View; |
| 26 import android.view.ViewGroup; |
| 27 import android.widget.ImageView; |
| 28 import android.widget.TextView; |
| 29 import android.widget.TextView.BufferType; |
| 30 |
| 31 import com.google.android.apps.chrome.R; |
| 32 |
| 33 import org.chromium.base.ApiCompatibilityUtils; |
| 34 import org.chromium.base.metrics.RecordUserAction; |
| 35 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultIt
em; |
| 36 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggesti
onDelegate; |
| 37 import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type; |
| 38 import org.chromium.chrome.browser.widget.TintedDrawable; |
| 39 import org.chromium.ui.base.DeviceFormFactor; |
| 40 |
| 41 import java.util.Locale; |
| 42 |
| 43 /** |
| 44 * Container view for omnibox suggestions made very specific for omnibox suggest
ions to minimize |
| 45 * any unnecessary measures and layouts. |
| 46 */ |
| 47 class SuggestionView extends ViewGroup { |
| 48 private enum SuggestionIconType { |
| 49 BOOKMARK, |
| 50 HISTORY, |
| 51 GLOBE, |
| 52 MAGNIFIER, |
| 53 VOICE |
| 54 } |
| 55 |
| 56 private static final int FIRST_LINE_TEXT_SIZE_SP = 17; |
| 57 private static final int SECOND_LINE_TEXT_SIZE_SP = 14; |
| 58 |
| 59 private static final long RELAYOUT_DELAY_MS = 20; |
| 60 |
| 61 private static final int TITLE_COLOR_STANDARD_FONT_DARK = Color.rgb(51, 51,
51); |
| 62 private static final int TITLE_COLOR_STANDARD_FONT_LIGHT = Color.rgb(255, 25
5, 255); |
| 63 private static final int URL_COLOR = Color.rgb(85, 149, 254); |
| 64 |
| 65 private static final int ANSWER_IMAGE_HORIZONTAL_SPACING_DP = 4; |
| 66 private static final int ANSWER_IMAGE_VERTICAL_SPACING_DP = 5; |
| 67 private static final float ANSWER_IMAGE_SCALING_FACTOR = 1.15f; |
| 68 |
| 69 private LocationBar mLocationBar; |
| 70 private UrlBar mUrlBar; |
| 71 private ImageView mNavigationButton; |
| 72 |
| 73 private int mSuggestionHeight; |
| 74 private int mSuggestionAnswerHeight; |
| 75 |
| 76 private OmniboxResultItem mSuggestionItem; |
| 77 private OmniboxSuggestion mSuggestion; |
| 78 private OmniboxSuggestionDelegate mSuggestionDelegate; |
| 79 private Boolean mUseDarkColors; |
| 80 private int mPosition; |
| 81 |
| 82 private SuggestionContentsContainer mContentsView; |
| 83 |
| 84 private int mRefineWidth; |
| 85 private View mRefineView; |
| 86 private TintedDrawable mRefineIcon; |
| 87 |
| 88 private final int[] mViewPositionHolder = new int[2]; |
| 89 |
| 90 /** |
| 91 * Constructs a new omnibox suggestion view. |
| 92 * |
| 93 * @param context The context used to construct the suggestion view. |
| 94 * @param locationBar The location bar showing these suggestions. |
| 95 */ |
| 96 public SuggestionView(Context context, LocationBar locationBar) { |
| 97 super(context); |
| 98 mLocationBar = locationBar; |
| 99 |
| 100 mSuggestionHeight = |
| 101 context.getResources().getDimensionPixelOffset(R.dimen.omnibox_s
uggestion_height); |
| 102 mSuggestionAnswerHeight = |
| 103 context.getResources().getDimensionPixelOffset( |
| 104 R.dimen.omnibox_suggestion_answer_height); |
| 105 |
| 106 TypedArray a = getContext().obtainStyledAttributes( |
| 107 new int [] {R.attr.selectableItemBackground}); |
| 108 Drawable itemBackground = a.getDrawable(0); |
| 109 a.recycle(); |
| 110 |
| 111 mContentsView = new SuggestionContentsContainer(context, itemBackground)
; |
| 112 addView(mContentsView); |
| 113 |
| 114 mRefineView = new View(context) { |
| 115 @Override |
| 116 protected void onDraw(Canvas canvas) { |
| 117 super.onDraw(canvas); |
| 118 |
| 119 if (mRefineIcon == null) return; |
| 120 canvas.save(); |
| 121 canvas.translate( |
| 122 (getMeasuredWidth() - mRefineIcon.getIntrinsicWidth()) /
2f, |
| 123 (getMeasuredHeight() - mRefineIcon.getIntrinsicHeight())
/ 2f); |
| 124 mRefineIcon.draw(canvas); |
| 125 canvas.restore(); |
| 126 } |
| 127 |
| 128 @Override |
| 129 public void setVisibility(int visibility) { |
| 130 super.setVisibility(visibility); |
| 131 |
| 132 if (visibility == VISIBLE) { |
| 133 setClickable(true); |
| 134 setFocusable(true); |
| 135 } else { |
| 136 setClickable(false); |
| 137 setFocusable(false); |
| 138 } |
| 139 } |
| 140 |
| 141 @Override |
| 142 protected void drawableStateChanged() { |
| 143 super.drawableStateChanged(); |
| 144 |
| 145 if (mRefineIcon != null && mRefineIcon.isStateful()) { |
| 146 mRefineIcon.setState(getDrawableState()); |
| 147 } |
| 148 } |
| 149 }; |
| 150 mRefineView.setContentDescription(getContext().getString( |
| 151 R.string.accessibility_omnibox_btn_refine)); |
| 152 |
| 153 // Although this has the same background as the suggestion view, it can
not be shared as |
| 154 // it will result in the state of the drawable being shared and always s
howing up in the |
| 155 // refine view. |
| 156 mRefineView.setBackground(itemBackground.getConstantState().newDrawable(
)); |
| 157 mRefineView.setId(R.id.refine_view_id); |
| 158 mRefineView.setClickable(true); |
| 159 mRefineView.setFocusable(true); |
| 160 mRefineView.setLayoutParams(new LayoutParams(0, 0)); |
| 161 addView(mRefineView); |
| 162 |
| 163 mRefineWidth = (int) (getResources().getDisplayMetrics().density * 48); |
| 164 |
| 165 mUrlBar = (UrlBar) locationBar.getContainerView().findViewById(R.id.url_
bar); |
| 166 } |
| 167 |
| 168 @Override |
| 169 protected void onLayout(boolean changed, int left, int top, int right, int b
ottom) { |
| 170 if (getMeasuredWidth() == 0) return; |
| 171 |
| 172 if (mSuggestion.getType() != Type.SEARCH_SUGGEST_TAIL) { |
| 173 mContentsView.resetTextWidths(); |
| 174 } |
| 175 |
| 176 boolean refineVisible = mRefineView.getVisibility() == VISIBLE; |
| 177 boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this); |
| 178 int contentsViewOffsetX = isRtl ? mRefineWidth : 0; |
| 179 if (!refineVisible) contentsViewOffsetX = 0; |
| 180 mContentsView.layout( |
| 181 contentsViewOffsetX, |
| 182 0, |
| 183 contentsViewOffsetX + mContentsView.getMeasuredWidth(), |
| 184 mContentsView.getMeasuredHeight()); |
| 185 int refineViewOffsetX = isRtl ? 0 : getMeasuredWidth() - mRefineWidth; |
| 186 mRefineView.layout( |
| 187 refineViewOffsetX, |
| 188 0, |
| 189 refineViewOffsetX + mRefineWidth, |
| 190 mContentsView.getMeasuredHeight()); |
| 191 } |
| 192 |
| 193 @Override |
| 194 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 195 int width = MeasureSpec.getSize(widthMeasureSpec); |
| 196 int height = mSuggestionHeight; |
| 197 if (!TextUtils.isEmpty(mSuggestion.getAnswerContents())) { |
| 198 height = mSuggestionAnswerHeight; |
| 199 } |
| 200 setMeasuredDimension(width, height); |
| 201 |
| 202 // The width will be specified as 0 when determining the height of the p
opup, so exit early |
| 203 // after setting the height. |
| 204 if (width == 0) return; |
| 205 |
| 206 boolean refineVisible = mRefineView.getVisibility() == VISIBLE; |
| 207 int refineWidth = refineVisible ? mRefineWidth : 0; |
| 208 mContentsView.measure( |
| 209 MeasureSpec.makeMeasureSpec(width - refineWidth, MeasureSpec.EXA
CTLY), |
| 210 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); |
| 211 mContentsView.getLayoutParams().width = mContentsView.getMeasuredWidth()
; |
| 212 mContentsView.getLayoutParams().height = mContentsView.getMeasuredHeight
(); |
| 213 |
| 214 mRefineView.measure( |
| 215 MeasureSpec.makeMeasureSpec(mRefineWidth, MeasureSpec.EXACTLY), |
| 216 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); |
| 217 mRefineView.getLayoutParams().width = mRefineView.getMeasuredWidth(); |
| 218 mRefineView.getLayoutParams().height = mRefineView.getMeasuredHeight(); |
| 219 } |
| 220 |
| 221 @Override |
| 222 public void invalidate() { |
| 223 super.invalidate(); |
| 224 mContentsView.invalidate(); |
| 225 } |
| 226 |
| 227 @Override |
| 228 public boolean dispatchTouchEvent(MotionEvent ev) { |
| 229 // Whenever the suggestion dropdown is touched, we dispatch onGestureDow
n which is |
| 230 // used to let autocomplete controller know that it should stop updating
suggestions. |
| 231 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) mSuggestionDelegate
.onGestureDown(); |
| 232 return super.dispatchTouchEvent(ev); |
| 233 } |
| 234 |
| 235 /** |
| 236 * Sets the contents and state of the view for the given suggestion. |
| 237 * |
| 238 * @param suggestionItem The omnibox suggestion item this view represents. |
| 239 * @param suggestionDelegate The suggestion delegate. |
| 240 * @param position Position of the suggestion in the dropdown list. |
| 241 * @param useDarkColors Whether dark colors should be used for fonts and ico
ns. |
| 242 */ |
| 243 public void init(OmniboxResultItem suggestionItem, |
| 244 OmniboxSuggestionDelegate suggestionDelegate, |
| 245 int position, boolean useDarkColors) { |
| 246 // Update the position unconditionally. |
| 247 mPosition = position; |
| 248 jumpDrawablesToCurrentState(); |
| 249 boolean colorsChanged = mUseDarkColors == null || mUseDarkColors != useD
arkColors; |
| 250 if (suggestionItem.equals(mSuggestionItem) && !colorsChanged) return; |
| 251 mUseDarkColors = useDarkColors; |
| 252 if (colorsChanged) { |
| 253 mContentsView.mTextLine1.setTextColor(getStandardFontColor()); |
| 254 setRefineIcon(true); |
| 255 } |
| 256 |
| 257 mSuggestionItem = suggestionItem; |
| 258 mSuggestion = suggestionItem.getSuggestion(); |
| 259 mSuggestionDelegate = suggestionDelegate; |
| 260 // Reset old computations. |
| 261 mContentsView.resetTextWidths(); |
| 262 mContentsView.mAnswerImage.setVisibility(GONE); |
| 263 mContentsView.mAnswerImage.getLayoutParams().height = 0; |
| 264 mContentsView.mAnswerImage.getLayoutParams().width = 0; |
| 265 mContentsView.mAnswerImage.setImageDrawable(null); |
| 266 mContentsView.mAnswerImageMaxSize = 0; |
| 267 mContentsView.mTextLine1.setTextSize(FIRST_LINE_TEXT_SIZE_SP); |
| 268 mContentsView.mTextLine2.setTextSize(SECOND_LINE_TEXT_SIZE_SP); |
| 269 |
| 270 // Suggestions with attached answers are rendered with rich results rega
rdless of which |
| 271 // suggestion type they are. |
| 272 if (mSuggestion.hasAnswer()) { |
| 273 setAnswer(mSuggestion.getAnswer()); |
| 274 mContentsView.setSuggestionIcon(SuggestionIconType.MAGNIFIER, colors
Changed); |
| 275 mContentsView.mTextLine2.setVisibility(VISIBLE); |
| 276 setRefinable(true); |
| 277 return; |
| 278 } |
| 279 |
| 280 boolean sameAsTyped = |
| 281 suggestionItem.getMatchedQuery().equalsIgnoreCase(mSuggestion.ge
tDisplayText()); |
| 282 Type suggestionType = mSuggestion.getType(); |
| 283 switch (suggestionType) { |
| 284 case HISTORY_URL: |
| 285 case URL_WHAT_YOU_TYPED: |
| 286 case NAVSUGGEST: |
| 287 case HISTORY_TITLE: |
| 288 case HISTORY_BODY: |
| 289 case HISTORY_KEYWORD: |
| 290 case OPEN_HISTORY_PAGE: |
| 291 if (mSuggestion.isStarred()) { |
| 292 mContentsView.setSuggestionIcon(SuggestionIconType.BOOKMARK,
colorsChanged); |
| 293 } else if (suggestionType == HISTORY_URL) { |
| 294 mContentsView.setSuggestionIcon(SuggestionIconType.HISTORY,
colorsChanged); |
| 295 } else { |
| 296 mContentsView.setSuggestionIcon(SuggestionIconType.GLOBE, co
lorsChanged); |
| 297 } |
| 298 boolean urlShown = !TextUtils.isEmpty(mSuggestion.getUrl()); |
| 299 boolean urlHighlighted = false; |
| 300 if (urlShown) { |
| 301 urlHighlighted = setUrlText(suggestionItem); |
| 302 } else { |
| 303 mContentsView.mTextLine2.setVisibility(INVISIBLE); |
| 304 } |
| 305 setSuggestedQuery(suggestionItem, true, urlShown, urlHighlighted
); |
| 306 setRefinable(!sameAsTyped); |
| 307 break; |
| 308 case SEARCH_WHAT_YOU_TYPED: |
| 309 case SEARCH_HISTORY: |
| 310 case SEARCH_SUGGEST: |
| 311 case SEARCH_OTHER_ENGINE: |
| 312 case SEARCH_SUGGEST_ENTITY: |
| 313 case SEARCH_SUGGEST_TAIL: |
| 314 case SEARCH_SUGGEST_PERSONALIZED: |
| 315 case SEARCH_SUGGEST_PROFILE: |
| 316 case VOICE_SUGGEST: |
| 317 SuggestionIconType suggestionIcon = SuggestionIconType.MAGNIFIER
; |
| 318 if (suggestionType == Type.VOICE_SUGGEST) { |
| 319 suggestionIcon = SuggestionIconType.VOICE; |
| 320 } else if ((suggestionType == Type.SEARCH_SUGGEST_PERSONALIZED) |
| 321 || (suggestionType == Type.SEARCH_HISTORY)) { |
| 322 // Show history icon for suggestions based on user queries. |
| 323 suggestionIcon = SuggestionIconType.HISTORY; |
| 324 } |
| 325 mContentsView.setSuggestionIcon(suggestionIcon, colorsChanged); |
| 326 setRefinable(!sameAsTyped); |
| 327 setSuggestedQuery(suggestionItem, false, false, false); |
| 328 if ((suggestionType == Type.SEARCH_SUGGEST_ENTITY) |
| 329 || (suggestionType == Type.SEARCH_SUGGEST_PROFILE)) { |
| 330 showDescriptionLine( |
| 331 SpannableString.valueOf(mSuggestion.getDescription()
), |
| 332 getStandardFontColor()); |
| 333 } else { |
| 334 mContentsView.mTextLine2.setVisibility(INVISIBLE); |
| 335 } |
| 336 break; |
| 337 default: |
| 338 assert false : "Suggestion type (" + mSuggestion.getType() + ")
is not handled"; |
| 339 break; |
| 340 } |
| 341 } |
| 342 |
| 343 private void setRefinable(boolean refinable) { |
| 344 if (refinable) { |
| 345 mRefineView.setVisibility(VISIBLE); |
| 346 mRefineView.setOnClickListener(new OnClickListener() { |
| 347 @Override |
| 348 public void onClick(View v) { |
| 349 // Post the refine action to the end of the UI thread to all
ow the refine view |
| 350 // a chance to update its background selection state. |
| 351 PerformRefineSuggestion performRefine = new PerformRefineSug
gestion(); |
| 352 if (!post(performRefine)) performRefine.run(); |
| 353 } |
| 354 }); |
| 355 } else { |
| 356 mRefineView.setOnClickListener(null); |
| 357 mRefineView.setVisibility(GONE); |
| 358 } |
| 359 } |
| 360 |
| 361 private int getStandardFontColor() { |
| 362 return (mUseDarkColors == null || mUseDarkColors) |
| 363 ? TITLE_COLOR_STANDARD_FONT_DARK : TITLE_COLOR_STANDARD_FONT_LIG
HT; |
| 364 } |
| 365 |
| 366 @Override |
| 367 public void setSelected(boolean selected) { |
| 368 super.setSelected(selected); |
| 369 if (selected && !isInTouchMode()) { |
| 370 mSuggestionDelegate.onSetUrlToSuggestion(mSuggestion); |
| 371 } |
| 372 } |
| 373 |
| 374 private void setRefineIcon(boolean invalidateIcon) { |
| 375 if (!invalidateIcon && mRefineIcon != null) return; |
| 376 |
| 377 mRefineIcon = TintedDrawable.constructTintedDrawable( |
| 378 getResources(), R.drawable.btn_suggestion_refine); |
| 379 mRefineIcon.setTint(getResources().getColorStateList(mUseDarkColors |
| 380 ? R.color.dark_mode_tint |
| 381 : R.color.light_mode_tint)); |
| 382 mRefineIcon.setBounds( |
| 383 0, 0, |
| 384 mRefineIcon.getIntrinsicWidth(), |
| 385 mRefineIcon.getIntrinsicHeight()); |
| 386 mRefineIcon.setState(mRefineView.getDrawableState()); |
| 387 mRefineView.postInvalidateOnAnimation(); |
| 388 } |
| 389 |
| 390 /** |
| 391 * Sets (and highlights) the URL text of the second line of the omnibox sugg
estion. |
| 392 * |
| 393 * @param suggestion The suggestion containing the URL. |
| 394 * @return Whether the URL was highlighted based on the user query. |
| 395 */ |
| 396 private boolean setUrlText(OmniboxResultItem suggestion) { |
| 397 String query = suggestion.getMatchedQuery(); |
| 398 String url = suggestion.getSuggestion().getFormattedUrl(); |
| 399 int index = url.indexOf(query); |
| 400 Spannable str = SpannableString.valueOf(url); |
| 401 if (index >= 0) { |
| 402 // Bold the part of the URL that matches the user query. |
| 403 str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), |
| 404 index, index + query.length(), Spannable.SPAN_EXCLUSIVE_EXCL
USIVE); |
| 405 } |
| 406 showDescriptionLine(str, URL_COLOR); |
| 407 return index >= 0; |
| 408 } |
| 409 |
| 410 /** |
| 411 * Sets a description line for the omnibox suggestion. |
| 412 * |
| 413 * @param str The description text. |
| 414 */ |
| 415 private void showDescriptionLine(Spannable str, int textColor) { |
| 416 if (mContentsView.mTextLine2.getVisibility() != VISIBLE) { |
| 417 mContentsView.mTextLine2.setVisibility(VISIBLE); |
| 418 } |
| 419 mContentsView.mTextLine2.setTextColor(textColor); |
| 420 mContentsView.mTextLine2.setText(str, BufferType.SPANNABLE); |
| 421 } |
| 422 |
| 423 /** |
| 424 * Sets the text of the first line of the omnibox suggestion. |
| 425 * |
| 426 * @param suggestionItem The item containing the suggestion data. |
| 427 * @param showDescriptionIfPresent Whether to show the description text of t
he suggestion if |
| 428 * the item contains valid data. |
| 429 * @param isUrlQuery Whether this suggestion is showing an URL. |
| 430 * @param isUrlHighlighted Whether the URL contains any highlighted matching
sections. |
| 431 */ |
| 432 private void setSuggestedQuery( |
| 433 OmniboxResultItem suggestionItem, boolean showDescriptionIfPresent, |
| 434 boolean isUrlQuery, boolean isUrlHighlighted) { |
| 435 String userQuery = suggestionItem.getMatchedQuery(); |
| 436 String suggestedQuery = null; |
| 437 OmniboxSuggestion suggestion = suggestionItem.getSuggestion(); |
| 438 if (showDescriptionIfPresent && !TextUtils.isEmpty(suggestion.getUrl()) |
| 439 && !TextUtils.isEmpty(suggestion.getDescription())) { |
| 440 suggestedQuery = suggestion.getDescription(); |
| 441 } else { |
| 442 suggestedQuery = suggestion.getDisplayText(); |
| 443 } |
| 444 if (suggestedQuery == null) { |
| 445 assert false : "Invalid suggestion sent with no displayable text"; |
| 446 suggestedQuery = ""; |
| 447 } else if (suggestedQuery.equals(suggestion.getUrl())) { |
| 448 // This is a navigation match with the title defaulted to the URL, d
isplay formatted URL |
| 449 // so that they continue matching. |
| 450 suggestedQuery = suggestion.getFormattedUrl(); |
| 451 } |
| 452 |
| 453 if (mSuggestion.getType() == Type.SEARCH_SUGGEST_TAIL) { |
| 454 String fillIntoEdit = mSuggestion.getFillIntoEdit(); |
| 455 // Data sanity checks. |
| 456 if (fillIntoEdit.startsWith(userQuery) |
| 457 && fillIntoEdit.endsWith(suggestedQuery) |
| 458 && fillIntoEdit.length() < userQuery.length() + suggestedQue
ry.length()) { |
| 459 String ignoredPrefix = fillIntoEdit.substring( |
| 460 0, fillIntoEdit.length() - suggestedQuery.length()); |
| 461 final String ellipsisPrefix = "\u2026 "; |
| 462 suggestedQuery = ellipsisPrefix + suggestedQuery; |
| 463 if (userQuery.startsWith(ignoredPrefix)) { |
| 464 userQuery = ellipsisPrefix + userQuery.substring(ignoredPref
ix.length()); |
| 465 } |
| 466 if (DeviceFormFactor.isTablet(getContext())) { |
| 467 TextPaint tp = mContentsView.mTextLine1.getPaint(); |
| 468 mContentsView.mRequiredWidth = |
| 469 tp.measureText(fillIntoEdit, 0, fillIntoEdit.length(
)); |
| 470 mContentsView.mMatchContentsWidth = |
| 471 tp.measureText(suggestedQuery, 0, suggestedQuery.len
gth()); |
| 472 |
| 473 // Update the max text widths values in SuggestionList. Thes
e will be passed to |
| 474 // the contents view on layout. |
| 475 mSuggestionDelegate.onTextWidthsUpdated( |
| 476 mContentsView.mRequiredWidth, mContentsView.mMatchCo
ntentsWidth); |
| 477 } |
| 478 } |
| 479 } |
| 480 |
| 481 Spannable str = SpannableString.valueOf(suggestedQuery); |
| 482 int userQueryIndex = isUrlHighlighted ? -1 |
| 483 : suggestedQuery.toLowerCase(Locale.getDefault()).indexOf( |
| 484 userQuery.toLowerCase(Locale.getDefault())); |
| 485 if (userQueryIndex != -1) { |
| 486 int spanStart = 0; |
| 487 int spanEnd = 0; |
| 488 if (isUrlQuery) { |
| 489 spanStart = userQueryIndex; |
| 490 spanEnd = userQueryIndex + userQuery.length(); |
| 491 } else { |
| 492 spanStart = userQueryIndex + userQuery.length(); |
| 493 spanEnd = str.length(); |
| 494 } |
| 495 spanStart = Math.min(spanStart, str.length()); |
| 496 spanEnd = Math.min(spanEnd, str.length()); |
| 497 if (spanStart != spanEnd) { |
| 498 str.setSpan( |
| 499 new StyleSpan(android.graphics.Typeface.BOLD), |
| 500 spanStart, spanEnd, |
| 501 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 502 } |
| 503 } |
| 504 mContentsView.mTextLine1.setText(str, BufferType.SPANNABLE); |
| 505 } |
| 506 |
| 507 /** |
| 508 * Sets both lines of the Omnibox suggestion based on an Answers in Suggest
result. |
| 509 * |
| 510 * @param answer The answer to be displayed. |
| 511 */ |
| 512 private void setAnswer(SuggestionAnswer answer) { |
| 513 float density = getResources().getDisplayMetrics().density; |
| 514 |
| 515 SuggestionAnswer.ImageLine firstLine = answer.getFirstLine(); |
| 516 mContentsView.mTextLine1.setTextSize(AnswerTextBuilder.getMaxTextHeightS
p(firstLine)); |
| 517 Spannable firstLineText = AnswerTextBuilder.buildSpannable( |
| 518 firstLine, mContentsView.mTextLine1.getPaint().getFontMetrics(),
density); |
| 519 mContentsView.mTextLine1.setText(firstLineText, BufferType.SPANNABLE); |
| 520 |
| 521 SuggestionAnswer.ImageLine secondLine = answer.getSecondLine(); |
| 522 mContentsView.mTextLine2.setTextSize(AnswerTextBuilder.getMaxTextHeightS
p(secondLine)); |
| 523 Spannable secondLineText = AnswerTextBuilder.buildSpannable( |
| 524 secondLine, mContentsView.mTextLine2.getPaint().getFontMetrics()
, density); |
| 525 mContentsView.mTextLine2.setText(secondLineText, BufferType.SPANNABLE); |
| 526 |
| 527 if (secondLine.hasImage()) { |
| 528 mContentsView.mAnswerImage.setVisibility(VISIBLE); |
| 529 |
| 530 float textSize = mContentsView.mTextLine2.getTextSize(); |
| 531 int imageSize = (int) (textSize * ANSWER_IMAGE_SCALING_FACTOR); |
| 532 mContentsView.mAnswerImage.getLayoutParams().height = imageSize; |
| 533 mContentsView.mAnswerImage.getLayoutParams().width = imageSize; |
| 534 mContentsView.mAnswerImageMaxSize = imageSize; |
| 535 |
| 536 String url = "https:" + secondLine.getImage().replace("\\/", "/"); |
| 537 AnswersImage.requestAnswersImage( |
| 538 mLocationBar.getCurrentTab().getProfile(), |
| 539 url, |
| 540 new AnswersImage.AnswersImageObserver() { |
| 541 @Override |
| 542 public void onAnswersImageChanged(Bitmap bitmap) { |
| 543 mContentsView.mAnswerImage.setImageBitmap(bitmap); |
| 544 } |
| 545 }); |
| 546 } |
| 547 } |
| 548 |
| 549 /** |
| 550 * Handles triggering a selection request for the suggestion rendered by thi
s view. |
| 551 */ |
| 552 private class PerformSelectSuggestion implements Runnable { |
| 553 @Override |
| 554 public void run() { |
| 555 mSuggestionDelegate.onSelection(mSuggestion, mPosition); |
| 556 } |
| 557 } |
| 558 |
| 559 /** |
| 560 * Handles triggering a refine request for the suggestion rendered by this v
iew. |
| 561 */ |
| 562 private class PerformRefineSuggestion implements Runnable { |
| 563 @Override |
| 564 public void run() { |
| 565 mSuggestionDelegate.onRefineSuggestion(mSuggestion); |
| 566 } |
| 567 } |
| 568 |
| 569 /** |
| 570 * @return The left offset for the suggestion text that will align it with t
he url text. |
| 571 */ |
| 572 private int getUrlBarTextLeftPosition() { |
| 573 mUrlBar.getLocationOnScreen(mViewPositionHolder); |
| 574 return mViewPositionHolder[0] + mUrlBar.getPaddingLeft(); |
| 575 } |
| 576 |
| 577 /** |
| 578 * @return The right offset for the suggestion text that will align it with
the url text. |
| 579 */ |
| 580 private int getUrlBarTextRightPosition() { |
| 581 mUrlBar.getLocationOnScreen(mViewPositionHolder); |
| 582 return mViewPositionHolder[0] + mUrlBar.getWidth() - mUrlBar.getPaddingR
ight(); |
| 583 } |
| 584 |
| 585 /** |
| 586 * Container view for the contents of the suggestion (the search query, URL,
and suggestion type |
| 587 * icon). |
| 588 */ |
| 589 private class SuggestionContentsContainer extends ViewGroup implements OnLay
outChangeListener { |
| 590 private int mSuggestionIconLeft = Integer.MIN_VALUE; |
| 591 private int mTextLeft = Integer.MIN_VALUE; |
| 592 private int mTextRight = Integer.MIN_VALUE; |
| 593 private Drawable mSuggestionIcon; |
| 594 private SuggestionIconType mSuggestionIconType; |
| 595 |
| 596 private final TextView mTextLine1; |
| 597 private final TextView mTextLine2; |
| 598 private final ImageView mAnswerImage; |
| 599 |
| 600 private int mAnswerImageMaxSize; // getMaxWidth() is API 16+, so store
it locally. |
| 601 private float mRequiredWidth; |
| 602 private float mMatchContentsWidth; |
| 603 private boolean mForceIsFocused; |
| 604 |
| 605 private final Runnable mRelayoutRunnable = new Runnable() { |
| 606 @Override |
| 607 public void run() { |
| 608 requestLayout(); |
| 609 } |
| 610 }; |
| 611 |
| 612 @SuppressLint("InlinedApi") |
| 613 SuggestionContentsContainer(Context context, Drawable backgroundDrawable
) { |
| 614 super(context); |
| 615 |
| 616 ApiCompatibilityUtils.setLayoutDirection(this, View.LAYOUT_DIRECTION
_INHERIT); |
| 617 |
| 618 setBackground(backgroundDrawable); |
| 619 setClickable(true); |
| 620 setFocusable(true); |
| 621 setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, mSuggest
ionHeight)); |
| 622 setOnClickListener(new OnClickListener() { |
| 623 @Override |
| 624 public void onClick(View v) { |
| 625 // Post the selection action to the end of the UI thread to
allow the suggestion |
| 626 // view a chance to update their background selection state. |
| 627 PerformSelectSuggestion performSelection = new PerformSelect
Suggestion(); |
| 628 if (!post(performSelection)) performSelection.run(); |
| 629 } |
| 630 }); |
| 631 setOnLongClickListener(new OnLongClickListener() { |
| 632 @Override |
| 633 public boolean onLongClick(View v) { |
| 634 RecordUserAction.record("MobileOmniboxDeleteGesture"); |
| 635 if (!mSuggestion.isDeletable()) return true; |
| 636 |
| 637 AlertDialog.Builder b = |
| 638 new AlertDialog.Builder(getContext(), R.style.AlertD
ialogTheme); |
| 639 b.setTitle(mSuggestion.getDisplayText()); |
| 640 b.setMessage(R.string.omnibox_confirm_delete); |
| 641 DialogInterface.OnClickListener okListener = |
| 642 new DialogInterface.OnClickListener() { |
| 643 @Override |
| 644 public void onClick(DialogInterface dialog, int
which) { |
| 645 RecordUserAction.record("MobileOmniboxDelete
Requested"); |
| 646 mSuggestionDelegate.onDeleteSuggestion(mPosi
tion); |
| 647 } |
| 648 }; |
| 649 b.setPositiveButton(android.R.string.ok, okListener); |
| 650 DialogInterface.OnClickListener cancelListener = |
| 651 new DialogInterface.OnClickListener() { |
| 652 @Override |
| 653 public void onClick(DialogInterface dialog, int
which) { |
| 654 dialog.cancel(); |
| 655 } |
| 656 }; |
| 657 b.setNegativeButton(android.R.string.cancel, cancelListener)
; |
| 658 |
| 659 AlertDialog dialog = b.create(); |
| 660 dialog.setOnDismissListener(new DialogInterface.OnDismissLis
tener() { |
| 661 @Override |
| 662 public void onDismiss(DialogInterface dialog) { |
| 663 mSuggestionDelegate.onHideModal(); |
| 664 } |
| 665 }); |
| 666 |
| 667 mSuggestionDelegate.onShowModal(); |
| 668 dialog.show(); |
| 669 return true; |
| 670 } |
| 671 }); |
| 672 |
| 673 mTextLine1 = new TextView(context); |
| 674 mTextLine1.setLayoutParams( |
| 675 new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeigh
t)); |
| 676 mTextLine1.setSingleLine(); |
| 677 mTextLine1.setTextColor(getStandardFontColor()); |
| 678 addView(mTextLine1); |
| 679 |
| 680 mTextLine2 = new TextView(context); |
| 681 mTextLine2.setLayoutParams( |
| 682 new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeigh
t)); |
| 683 mTextLine2.setSingleLine(); |
| 684 mTextLine2.setVisibility(INVISIBLE); |
| 685 addView(mTextLine2); |
| 686 |
| 687 mAnswerImage = new ImageView(context); |
| 688 mAnswerImage.setVisibility(GONE); |
| 689 mAnswerImage.setScaleType(ImageView.ScaleType.FIT_CENTER); |
| 690 mAnswerImage.setLayoutParams(new LayoutParams(0, 0)); |
| 691 mAnswerImageMaxSize = 0; |
| 692 addView(mAnswerImage); |
| 693 } |
| 694 |
| 695 private void resetTextWidths() { |
| 696 mRequiredWidth = 0; |
| 697 mMatchContentsWidth = 0; |
| 698 } |
| 699 |
| 700 @Override |
| 701 protected void onDraw(Canvas canvas) { |
| 702 super.onDraw(canvas); |
| 703 |
| 704 if (DeviceFormFactor.isTablet(getContext())) { |
| 705 // Use the same image transform matrix as the navigation icon to
ensure the same |
| 706 // scaling, which requires centering vertically based on the hei
ght of the |
| 707 // navigation icon view and not the image itself. |
| 708 canvas.save(); |
| 709 mSuggestionIconLeft = getSuggestionIconLeftPosition(); |
| 710 canvas.translate( |
| 711 mSuggestionIconLeft, |
| 712 (getMeasuredHeight() - mNavigationButton.getMeasuredHeig
ht()) / 2f); |
| 713 canvas.concat(mNavigationButton.getImageMatrix()); |
| 714 mSuggestionIcon.draw(canvas); |
| 715 canvas.restore(); |
| 716 } |
| 717 } |
| 718 |
| 719 @Override |
| 720 protected boolean drawChild(Canvas canvas, View child, long drawingTime)
{ |
| 721 if (child != mTextLine1 && child != mTextLine2 && child != mAnswerIm
age) { |
| 722 return super.drawChild(canvas, child, drawingTime); |
| 723 } |
| 724 |
| 725 int height = getMeasuredHeight(); |
| 726 int line1Height = mTextLine1.getMeasuredHeight(); |
| 727 int line2Height = mTextLine2.getVisibility() == VISIBLE |
| 728 ? mTextLine2.getMeasuredHeight() : 0; |
| 729 |
| 730 int verticalOffset = 0; |
| 731 if (line1Height + line2Height > height) { |
| 732 // The text lines total height is larger than this view, snap th
em to the top and |
| 733 // bottom of the view. |
| 734 if (child == mTextLine1) { |
| 735 verticalOffset = 0; |
| 736 } else { |
| 737 verticalOffset = height - line2Height; |
| 738 } |
| 739 } else { |
| 740 // The text lines fit comfortably, so vertically center them. |
| 741 verticalOffset = (height - line1Height - line2Height) / 2; |
| 742 if (child == mTextLine2) verticalOffset += line1Height; |
| 743 |
| 744 // When one line is larger than the other, it contains extra ver
tical padding. This |
| 745 // produces more apparent whitespace above or below the text lin
es. Add a small |
| 746 // offset to compensate. |
| 747 if (line1Height != line2Height) { |
| 748 verticalOffset += (line2Height - line1Height) / 10; |
| 749 } |
| 750 |
| 751 // The image is positioned vertically aligned with the second te
xt line but |
| 752 // requires a small additional offset to align with the ascent o
f the text instead |
| 753 // of the top of the text which includes some whitespace. |
| 754 if (child == mAnswerImage) { |
| 755 verticalOffset += ANSWER_IMAGE_VERTICAL_SPACING_DP |
| 756 * getResources().getDisplayMetrics().density; |
| 757 } |
| 758 } |
| 759 |
| 760 canvas.save(); |
| 761 canvas.translate(0, verticalOffset); |
| 762 boolean retVal = super.drawChild(canvas, child, drawingTime); |
| 763 canvas.restore(); |
| 764 return retVal; |
| 765 } |
| 766 |
| 767 @Override |
| 768 protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| 769 View locationBarView = mLocationBar.getContainerView(); |
| 770 if (mUrlBar == null) { |
| 771 mUrlBar = (UrlBar) locationBarView.findViewById(R.id.url_bar); |
| 772 mUrlBar.addOnLayoutChangeListener(this); |
| 773 } |
| 774 if (mNavigationButton == null) { |
| 775 mNavigationButton = |
| 776 (ImageView) locationBarView.findViewById(R.id.navigation
_button); |
| 777 mNavigationButton.addOnLayoutChangeListener(this); |
| 778 } |
| 779 |
| 780 // Align the text to be pixel perfectly aligned with the text in the
url bar. |
| 781 mTextLeft = getSuggestionTextLeftPosition(); |
| 782 mTextRight = getSuggestionTextRightPosition(); |
| 783 boolean isRTL = ApiCompatibilityUtils.isLayoutRtl(this); |
| 784 if (DeviceFormFactor.isTablet(getContext())) { |
| 785 int textWidth = isRTL ? mTextRight : (r - l - mTextLeft); |
| 786 final float maxRequiredWidth = mSuggestionDelegate.getMaxRequire
dWidth(); |
| 787 final float maxMatchContentsWidth = mSuggestionDelegate.getMaxMa
tchContentsWidth(); |
| 788 float paddingStart = (textWidth > maxRequiredWidth) |
| 789 ? (mRequiredWidth - mMatchContentsWidth) |
| 790 : Math.max(textWidth - maxMatchContentsWidth, 0); |
| 791 ApiCompatibilityUtils.setPaddingRelative( |
| 792 mTextLine1, (int) paddingStart, mTextLine1.getPaddingTop
(), |
| 793 0, // TODO(skanuj) : Change to ApiCompatibilityUtils.get
PaddingEnd(...). |
| 794 mTextLine1.getPaddingBottom()); |
| 795 } |
| 796 |
| 797 int imageWidth = mAnswerImageMaxSize; |
| 798 int imageSpacing = 0; |
| 799 if (mAnswerImage.getVisibility() == VISIBLE && imageWidth > 0) { |
| 800 float density = getResources().getDisplayMetrics().density; |
| 801 imageSpacing = (int) (ANSWER_IMAGE_HORIZONTAL_SPACING_DP * densi
ty); |
| 802 } |
| 803 if (isRTL) { |
| 804 mTextLine1.layout(0, t, mTextRight, b); |
| 805 mAnswerImage.layout(mTextRight - imageWidth , t, mTextRight, b); |
| 806 mTextLine2.layout(0, t, mTextRight - (imageWidth + imageSpacing)
, b); |
| 807 } else { |
| 808 mTextLine1.layout(mTextLeft, t, r - l, b); |
| 809 mAnswerImage.layout(mTextLeft, t, mTextLeft + imageWidth, b); |
| 810 mTextLine2.layout(mTextLeft + imageWidth + imageSpacing, t, r -
l, b); |
| 811 } |
| 812 |
| 813 int suggestionIconPosition = getSuggestionIconLeftPosition(); |
| 814 if (mSuggestionIconLeft != suggestionIconPosition |
| 815 && mSuggestionIconLeft != Integer.MIN_VALUE) { |
| 816 mContentsView.postInvalidateOnAnimation(); |
| 817 } |
| 818 mSuggestionIconLeft = suggestionIconPosition; |
| 819 } |
| 820 |
| 821 /** |
| 822 * @return The left offset for the suggestion text that will align it wi
th the url text. |
| 823 */ |
| 824 private int getSuggestionTextLeftPosition() { |
| 825 if (mLocationBar == null) return 0; |
| 826 |
| 827 int textLeftPosition = getUrlBarTextLeftPosition(); |
| 828 getLocationOnScreen(mViewPositionHolder); |
| 829 return textLeftPosition - mViewPositionHolder[0]; |
| 830 } |
| 831 |
| 832 /** |
| 833 * @return The right offset for the suggestion text that will align it w
ith the url text. |
| 834 */ |
| 835 private int getSuggestionTextRightPosition() { |
| 836 if (mLocationBar == null) return 0; |
| 837 |
| 838 int textRightPosition = getUrlBarTextRightPosition(); |
| 839 getLocationOnScreen(mViewPositionHolder); |
| 840 return textRightPosition - mViewPositionHolder[0]; |
| 841 } |
| 842 |
| 843 /** |
| 844 * @return The left offset for the suggestion type icon that aligns it w
ith the url bar. |
| 845 */ |
| 846 private int getSuggestionIconLeftPosition() { |
| 847 if (mNavigationButton == null) return 0; |
| 848 |
| 849 // Ensure the suggestion icon matches the location of the navigation
icon in the omnibox |
| 850 // perfectly. |
| 851 mNavigationButton.getLocationOnScreen(mViewPositionHolder); |
| 852 int navButtonXPosition = mViewPositionHolder[0] + mNavigationButton.
getPaddingLeft(); |
| 853 |
| 854 getLocationOnScreen(mViewPositionHolder); |
| 855 |
| 856 return navButtonXPosition - mViewPositionHolder[0]; |
| 857 } |
| 858 |
| 859 @Override |
| 860 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 861 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 862 |
| 863 int width = MeasureSpec.getSize(widthMeasureSpec); |
| 864 int height = MeasureSpec.getSize(heightMeasureSpec); |
| 865 |
| 866 if (mTextLine1.getMeasuredWidth() != width |
| 867 || mTextLine1.getMeasuredHeight() != height) { |
| 868 mTextLine1.measure( |
| 869 MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpe
c.AT_MOST), |
| 870 MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSp
ec.AT_MOST)); |
| 871 } |
| 872 |
| 873 if (mTextLine2.getMeasuredWidth() != width |
| 874 || mTextLine2.getMeasuredHeight() != height) { |
| 875 mTextLine2.measure( |
| 876 MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpe
c.AT_MOST), |
| 877 MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSp
ec.AT_MOST)); |
| 878 } |
| 879 } |
| 880 |
| 881 @Override |
| 882 public void invalidate() { |
| 883 if (getSuggestionTextLeftPosition() != mTextLeft |
| 884 || getSuggestionTextRightPosition() != mTextRight) { |
| 885 // When the text position is changed, it typically is caused by
the suggestions |
| 886 // appearing while the URL bar on the phone is gaining focus (if
you trigger an |
| 887 // intent that will result in suggestions being shown before foc
using the omnibox). |
| 888 // Triggering a relayout will cause any animations to stutter, s
o we continually |
| 889 // push the relayout to end of the UI queue until the animation
is complete. |
| 890 removeCallbacks(mRelayoutRunnable); |
| 891 postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS); |
| 892 } else { |
| 893 super.invalidate(); |
| 894 } |
| 895 } |
| 896 |
| 897 @Override |
| 898 public boolean isFocused() { |
| 899 return mForceIsFocused || super.isFocused(); |
| 900 } |
| 901 |
| 902 @Override |
| 903 protected int[] onCreateDrawableState(int extraSpace) { |
| 904 // When creating the drawable states, treat selected as focused to g
et the proper |
| 905 // highlight when in non-touch mode (i.e. physical keyboard). This
is because only |
| 906 // a single view in a window can have focus, and the these will only
appear if |
| 907 // the omnibox has focus, so we trick the drawable state into believ
ing it has it. |
| 908 mForceIsFocused = isSelected() && !isInTouchMode(); |
| 909 int[] drawableState = super.onCreateDrawableState(extraSpace); |
| 910 mForceIsFocused = false; |
| 911 return drawableState; |
| 912 } |
| 913 |
| 914 private void setSuggestionIcon(SuggestionIconType type, boolean invalida
teCurrentIcon) { |
| 915 if (mSuggestionIconType == type && !invalidateCurrentIcon) return; |
| 916 |
| 917 int drawableId = R.drawable.ic_omnibox_page; |
| 918 switch (type) { |
| 919 case BOOKMARK: |
| 920 drawableId = R.drawable.btn_star; |
| 921 break; |
| 922 case MAGNIFIER: |
| 923 drawableId = R.drawable.ic_suggestion_magnifier; |
| 924 break; |
| 925 case HISTORY: |
| 926 drawableId = R.drawable.ic_suggestion_history; |
| 927 break; |
| 928 case VOICE: |
| 929 drawableId = R.drawable.btn_mic; |
| 930 break; |
| 931 default: |
| 932 break; |
| 933 } |
| 934 mSuggestionIcon = ApiCompatibilityUtils.getDrawable(getResources(),
drawableId); |
| 935 mSuggestionIcon.setColorFilter(mUseDarkColors |
| 936 ? getResources().getColor(R.color.light_normal_color) |
| 937 : Color.WHITE, PorterDuff.Mode.SRC_IN); |
| 938 mSuggestionIcon.setBounds( |
| 939 0, 0, |
| 940 mSuggestionIcon.getIntrinsicWidth(), |
| 941 mSuggestionIcon.getIntrinsicHeight()); |
| 942 mSuggestionIconType = type; |
| 943 invalidate(); |
| 944 } |
| 945 |
| 946 @Override |
| 947 public void onLayoutChange( |
| 948 View v, int left, int top, int right, int bottom, int oldLeft, |
| 949 int oldTop, int oldRight, int oldBottom) { |
| 950 boolean needsInvalidate = false; |
| 951 if (v == mNavigationButton) { |
| 952 if (mSuggestionIconLeft != getSuggestionIconLeftPosition() |
| 953 && mSuggestionIconLeft != Integer.MIN_VALUE) { |
| 954 needsInvalidate = true; |
| 955 } |
| 956 } else { |
| 957 if (mTextLeft != getSuggestionTextLeftPosition() |
| 958 && mTextLeft != Integer.MIN_VALUE) { |
| 959 needsInvalidate = true; |
| 960 } |
| 961 if (mTextRight != getSuggestionTextRightPosition() |
| 962 && mTextRight != Integer.MIN_VALUE) { |
| 963 needsInvalidate = true; |
| 964 } |
| 965 } |
| 966 if (needsInvalidate) { |
| 967 removeCallbacks(mRelayoutRunnable); |
| 968 postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS); |
| 969 } |
| 970 } |
| 971 |
| 972 @Override |
| 973 protected void onAttachedToWindow() { |
| 974 super.onAttachedToWindow(); |
| 975 if (mNavigationButton != null) mNavigationButton.addOnLayoutChangeLi
stener(this); |
| 976 if (mUrlBar != null) mUrlBar.addOnLayoutChangeListener(this); |
| 977 if (mLocationBar != null) { |
| 978 mLocationBar.getContainerView().addOnLayoutChangeListener(this); |
| 979 } |
| 980 getRootView().addOnLayoutChangeListener(this); |
| 981 } |
| 982 |
| 983 @Override |
| 984 protected void onDetachedFromWindow() { |
| 985 if (mNavigationButton != null) mNavigationButton.removeOnLayoutChang
eListener(this); |
| 986 if (mUrlBar != null) mUrlBar.removeOnLayoutChangeListener(this); |
| 987 if (mLocationBar != null) { |
| 988 mLocationBar.getContainerView().removeOnLayoutChangeListener(thi
s); |
| 989 } |
| 990 getRootView().removeOnLayoutChangeListener(this); |
| 991 |
| 992 super.onDetachedFromWindow(); |
| 993 } |
| 994 } |
| 995 } |
OLD | NEW |