1 // Copyright 2013 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.components.web_contents_delegate_android; 6 7 import android.graphics.Point; 8 import android.graphics.RectF; 9 import android.text.TextUtils; 10 import android.view.Gravity; 11 import android.view.View; 12 import android.view.ViewGroup; 13 import android.widget.PopupWindow; 14 import android.widget.RelativeLayout; 15 import android.widget.TextView; 16 17 import org.chromium.base.ApiCompatibilityUtils; 18 import org.chromium.base.CalledByNative; 19 import org.chromium.content.R; 20 import org.chromium.content.browser.ContentViewCore; 21 import org.chromium.content.browser.RenderCoordinates; 22 23 /** 24 * This class is an implementation of validation message bubble UI. 25 */ 26 class ValidationMessageBubble { 27 private PopupWindow mPopup; 28 29 /** 30 * Creates a popup window to show the specified messages, and show it on 31 * the specified anchor rectangle. 32 * 33 * @param contentViewCore The ContentViewCore object to provide various 34 * information. 35 * @param anchorX Anchor position in the CSS unit. 36 * @param anchorY Anchor position in the CSS unit. 37 * @param anchorWidth Anchor size in the CSS unit. 38 * @param anchorHeight Anchor size in the CSS unit. 39 * @param mainText The main message. It will shown at the top of the popup 40 * window, and its font size is larger. 41 * @param subText The sub message. It will shown below the main message, and 42 * its font size is smaller. 43 */ 44 @CalledByNative 45 private static ValidationMessageBubble createAndShow( 46 ContentViewCore contentViewCore, int anchorX, int anchorY, 47 int anchorWidth, int anchorHeight, String mainText, String subText) { 48 final RectF anchorPixInScreen = makePixRectInScreen( 49 contentViewCore, anchorX, anchorY, anchorWidth, anchorHeight); 50 return new ValidationMessageBubble(contentViewCore, anchorPixInScreen, mainText, subText); 51 } 52 53 private ValidationMessageBubble( 54 ContentViewCore contentViewCore, RectF anchor, String mainText, String subText) { 55 final ViewGroup root = (ViewGroup) View.inflate(contentViewCore.getContext(), 56 R.layout.validation_message_bubble, null); 57 mPopup = new PopupWindow(root); 58 updateTextViews(root, mainText, subText); 59 measure(contentViewCore.getRenderCoordinates()); 60 Point origin = adjustWindowPosition( 61 contentViewCore, (int) (anchor.centerX() - getAnchorOffset()), (int) anchor.bottom); 62 mPopup.showAtLocation( 63 contentViewCore.getContainerView(), Gravity.NO_GRAVITY, origin.x, origin.y); 64 } 65 66 @CalledByNative 67 private void close() { 68 if (mPopup == null) return; 69 mPopup.dismiss(); 70 mPopup = null; 71 } 72 73 /** 74 * Moves the popup window on the specified anchor rectangle. 75 * 76 * @param contentViewCore The ContentViewCore object to provide various 77 * information. 78 * @param anchorX Anchor position in the CSS unit. 79 * @param anchorY Anchor position in the CSS unit. 80 * @param anchorWidth Anchor size in the CSS unit. 81 * @param anchorHeight Anchor size in the CSS unit. 82 */ 83 @CalledByNative 84 private void setPositionRelativeToAnchor(ContentViewCore contentViewCore, 85 int anchorX, int anchorY, int anchorWidth, int anchorHeight) { 86 RectF anchor = makePixRectInScreen( 87 contentViewCore, anchorX, anchorY, anchorWidth, anchorHeight); 88 Point origin = adjustWindowPosition( 89 contentViewCore, (int) (anchor.centerX() - getAnchorOffset()), (int) anchor.bottom); 90 mPopup.update(origin.x, origin.y, mPopup.getWidth(), mPopup.getHeight()); 91 } 92 93 private static RectF makePixRectInScreen(ContentViewCore contentViewCore, 94 int anchorX, int anchorY, int anchorWidth, int anchorHeight) { 95 final RenderCoordinates coordinates = contentViewCore.getRenderCoordinates(); 96 final float yOffset = getWebViewOffsetYPixInScreen(contentViewCore); 97 return new RectF( 98 coordinates.fromLocalCssToPix(anchorX), 99 coordinates.fromLocalCssToPix(anchorY) + yOffset, 100 coordinates.fromLocalCssToPix(anchorX + anchorWidth), 101 coordinates.fromLocalCssToPix(anchorY + anchorHeight) + yOffset); 102 } 103 104 private static float getWebViewOffsetYPixInScreen(ContentViewCore contentViewCore) { 105 int[] location = new int[2]; 106 contentViewCore.getContainerView().getLocationOnScreen(location); 107 return location[1] + contentViewCore.getRenderCoordinates().getContentOffsetYPix(); 108 } 109 110 private static void updateTextViews(ViewGroup root, String mainText, String subText) { 111 ((TextView) root.findViewById(R.id.main_text)).setText(mainText); 112 final TextView subTextView = (TextView) root.findViewById(R.id.sub_text); 113 if (!TextUtils.isEmpty(subText)) { 114 subTextView.setText(subText); 115 } else { 116 ((ViewGroup) subTextView.getParent()).removeView(subTextView); 117 } 118 } 119 120 private void measure(RenderCoordinates coordinates) { 121 mPopup.setWindowLayoutMode( 122 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 123 mPopup.getContentView().setLayoutParams( 124 new RelativeLayout.LayoutParams( 125 RelativeLayout.LayoutParams.WRAP_CONTENT, 126 RelativeLayout.LayoutParams.WRAP_CONTENT)); 127 mPopup.getContentView().measure( 128 View.MeasureSpec.makeMeasureSpec(coordinates.getLastFrameViewportWidthPixInt(), 129 View.MeasureSpec.AT_MOST), 130 View.MeasureSpec.makeMeasureSpec(coordinates.getLastFrameViewportHeightPixInt(), 131 View.MeasureSpec.AT_MOST)); 132 } 133 134 private float getAnchorOffset() { 135 final View root = mPopup.getContentView(); 136 final int width = root.getMeasuredWidth(); 137 final int arrowWidth = root.findViewById(R.id.arrow_image).getMeasuredWidth(); 138 return ApiCompatibilityUtils.isLayoutRtl(root) ? 139 (width * 3 / 4 - arrowWidth / 2) : (width / 4 + arrowWidth / 2); 140 } 141 142 /** 143 * This adjusts the position if the popup protrudes the web view. 144 */ 145 private Point adjustWindowPosition(ContentViewCore contentViewCore, int x, int y) { 146 final RenderCoordinates coordinates = contentViewCore.getRenderCoordinates(); 147 final int viewWidth = coordinates.getLastFrameViewportWidthPixInt(); 148 final int viewBottom = (int) getWebViewOffsetYPixInScreen(contentViewCore) + 149 coordinates.getLastFrameViewportHeightPixInt(); 150 final int width = mPopup.getContentView().getMeasuredWidth(); 151 final int height = mPopup.getContentView().getMeasuredHeight(); 152 if (x < 0) { 153 x = 0; 154 } else if (x + width > viewWidth) { 155 x = viewWidth - width; 156 } 157 if (y + height > viewBottom) { 158 y = viewBottom - height; 159 } 160 return new Point(x, y); 161 } 162 } 163