// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.webapps;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.omnibox.LocationBarLayout;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.ui.base.DeviceFormFactor;
import java.net.URI;
/**
* Maintains a URL bar that is displayed above the webapp's content.
* For security reasons, this bar will appear when a user navigates to a website that is not
* considered the same as the one that was used to open a WebappActivity originally.
* The URL bar will disappear again once the user navigates back to the original website.
*
* Example scenario:
* 0) User opens a webapp for http://domain1.com. URL bar is hidden
* 1) User navigates to http://domain1.com/some.html URL bar is hidden
* 2) User navigates to http://domain2.com/ URL bar is shown
* 3) User navigates back to http://domain1.com/some.html URL bar is hidden
*/
public class WebappUrlBar extends FrameLayout implements View.OnLayoutChangeListener {
private static final String TAG = "WebappUrlBar";
private final TextView mUrlBar;
private final View mSeparator;
private final SparseIntArray mIconResourceWidths;
private String mCurrentlyDisplayedUrl;
private int mCurrentIconResource;
/**
* Creates a WebappUrlBar.
* @param context Context to grab resources from.
*/
public WebappUrlBar(Context context, AttributeSet attrSet) {
super(context, attrSet);
mIconResourceWidths = new SparseIntArray();
mUrlBar = new TextView(context);
mUrlBar.setSingleLine(true);
mUrlBar.setGravity(Gravity.CENTER_VERTICAL);
mUrlBar.setMovementMethod(ScrollingMovementMethod.getInstance());
mUrlBar.setHorizontalFadingEdgeEnabled(true);
mSeparator = new View(context);
addView(mUrlBar,
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
addView(mSeparator,
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.BOTTOM));
// Set the colors.
mSeparator.setBackgroundColor(ApiCompatibilityUtils.getColor(context.getResources(),
R.color.webapp_url_bar_separator));
setBackgroundColor(ApiCompatibilityUtils.getColor(context.getResources(),
R.color.webapp_url_bar_bg));
// Listen for changes in the URL bar's size.
mUrlBar.addOnLayoutChangeListener(this);
}
/**
* Updates the URL bar for the current URL.
* @param url URL to display.
* @param securityLevel Security level of the Tab.
*/
public void update(String url, int securityLevel) {
URI uri = createURI(url);
updateSecurityIcon(securityLevel);
updateDisplayedUrl(url, uri);
}
/**
* @return the security icon being displayed for the current URL.
*/
@VisibleForTesting
protected int getCurrentIconResourceForTests() {
return mCurrentIconResource;
}
/**
* @return the URL being displayed.
*/
@VisibleForTesting
protected CharSequence getDisplayedUrlForTests() {
return mUrlBar.getText();
}
/**
* Show the end of the URL rather than the beginning.
*/
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
Layout layout = mUrlBar.getLayout();
if (layout == null) return;
// Android doesn't account for the compound Drawable in its width calculations, leading to
// improper scrolling and even Android improperly placing the horizontal fade in its
// TextView calculation. Get around it by calculating that width manually: crbug.com/303908
int urlBarWidth = mUrlBar.getWidth();
int iconWidth =
mCurrentIconResource == 0 ? 0 : mIconResourceWidths.get(mCurrentIconResource);
int availableTextWidth = urlBarWidth - iconWidth;
int desiredWidth = (int) Layout.getDesiredWidth(layout.getText(), layout.getPaint());
if (desiredWidth > availableTextWidth) {
mUrlBar.scrollTo(desiredWidth - availableTextWidth, 0);
} else {
mUrlBar.scrollTo(0, 0);
}
}
private static URI createURI(String url) {
// Get rid of spaces temporarily: crbug.com/298465
// Get rid of the need for this hack eventually: crbug.com/296870
url = url.replace(" ", "%20");
try {
return URI.create(url);
} catch (IllegalArgumentException exception) {
Log.e(TAG, "Failed to convert URI: ", exception);
return null;
}
}
private void updateSecurityIcon(int securityLevel) {
boolean isSmallDevice = !DeviceFormFactor.isTablet(getContext());
mCurrentIconResource =
LocationBarLayout.getSecurityIconResource(securityLevel, isSmallDevice, false);
if (mCurrentIconResource != 0 && mIconResourceWidths.get(mCurrentIconResource, -1) == -1) {
Drawable icon = ApiCompatibilityUtils.getDrawable(getResources(), mCurrentIconResource);
mIconResourceWidths.put(mCurrentIconResource, icon.getIntrinsicWidth());
}
ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mUrlBar,
mCurrentIconResource, 0, 0, 0);
}
private void updateDisplayedUrl(String originalUrl, URI uri) {
boolean showScheme = mCurrentIconResource == 0;
String displayUrl = originalUrl;
if (uri != null) {
String shortenedUrl = UrlFormatter.formatUrlForSecurityDisplay(uri, showScheme);
if (!TextUtils.isEmpty(shortenedUrl)) displayUrl = shortenedUrl;
}
mUrlBar.setText(displayUrl);
if (!TextUtils.equals(mCurrentlyDisplayedUrl, displayUrl)) {
mCurrentlyDisplayedUrl = displayUrl;
}
}
}