blob: 2b1f112d1cb70a75f21a991230935fdb8357910f [file] [log] [blame]
// Copyright 2020 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.weblayer_private;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
import org.chromium.base.LifetimeAssert;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.omnibox.SecurityButtonAnimationDelegate;
import org.chromium.components.omnibox.SecurityStatusIcon;
import org.chromium.components.page_info.PageInfoController;
import org.chromium.components.page_info.PermissionParamsListBuilderDelegate;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.content_public.browser.WebContents;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.IUrlBarController;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import org.chromium.weblayer_private.interfaces.UrlBarOptionsKeys;
/**
* Implementation of {@link IUrlBarController}.
*/
@JNINamespace("weblayer")
public class UrlBarControllerImpl extends IUrlBarController.Stub {
public static final float DEFAULT_TEXT_SIZE = 10.0F;
public static final float MINIMUM_TEXT_SIZE = 5.0F;
private BrowserImpl mBrowserImpl;
private long mNativeUrlBarController;
// A count of how many Views created by this controller are attached to a Window.
private int mActiveViewCount;
private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
private String getUrlForDisplay() {
return UrlBarControllerImplJni.get().getUrlForDisplay(mNativeUrlBarController);
}
void destroy() {
UrlBarControllerImplJni.get().deleteUrlBarController(mNativeUrlBarController);
mNativeUrlBarController = 0;
mBrowserImpl = null;
// If mLifetimeAssert is GC'ed before this is called, it will throw an exception
// with a stack trace showing the stack during LifetimeAssert.create().
LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
}
public UrlBarControllerImpl(BrowserImpl browserImpl, long nativeBrowser) {
mBrowserImpl = browserImpl;
mNativeUrlBarController =
UrlBarControllerImplJni.get().createUrlBarController(nativeBrowser);
}
void addActiveView() {
mActiveViewCount++;
}
void removeActiveView() {
mActiveViewCount--;
}
boolean hasActiveView() {
return mActiveViewCount != 0;
}
@Override
public IObjectWrapper /* View */ createUrlBarView(Bundle options,
@Nullable IObjectWrapper /* OnLongClickListener */ clickListener,
@Nullable IObjectWrapper /* OnLongClickListener */ longClickListener) {
StrictModeWorkaround.apply();
if (mBrowserImpl == null) {
throw new IllegalStateException("UrlBarView cannot be created without a valid Browser");
}
Context context = mBrowserImpl.getContext();
if (context == null) throw new IllegalStateException("BrowserFragment not attached yet.");
UrlBarView urlBarView =
new UrlBarView(this, context, options, clickListener, longClickListener);
return ObjectWrapper.wrap(urlBarView);
}
protected class UrlBarView
extends LinearLayout implements BrowserImpl.VisibleSecurityStateObserver {
private final UrlBarControllerImpl mController;
private float mTextSize;
private boolean mShowPageInfoWhenUrlTextClicked;
private boolean mShowPublisherUrl;
// These refer to the resources in the embedder's APK, not WebLayer's.
private @ColorRes int mUrlTextColor;
private @ColorRes int mUrlIconColor;
private TextView mUrlTextView;
private ImageButton mSecurityButton;
private final SecurityButtonAnimationDelegate mSecurityButtonAnimationDelegate;
OnClickListener mUrlBarClickListener;
OnLongClickListener mUrlBarLongClickListener;
public UrlBarView(@NonNull UrlBarControllerImpl controller, @NonNull Context context,
@NonNull Bundle options,
@Nullable IObjectWrapper /* OnClickListener */ clickListener,
@Nullable IObjectWrapper /* OnLongClickListener */ longClickListener) {
super(context);
mController = controller;
setGravity(Gravity.CENTER_HORIZONTAL);
mTextSize = options.getFloat(UrlBarOptionsKeys.URL_TEXT_SIZE, DEFAULT_TEXT_SIZE);
mShowPageInfoWhenUrlTextClicked = options.getBoolean(
UrlBarOptionsKeys.SHOW_PAGE_INFO_WHEN_URL_TEXT_CLICKED, /*default= */ false);
mShowPublisherUrl =
options.getBoolean(UrlBarOptionsKeys.SHOW_PUBLISHER_URL, /*default= */ false);
mUrlTextColor = options.getInt(UrlBarOptionsKeys.URL_TEXT_COLOR, /*default= */ 0);
mUrlIconColor = options.getInt(UrlBarOptionsKeys.URL_ICON_COLOR, /*default= */ 0);
View.inflate(getContext(), R.layout.weblayer_url_bar, this);
setOrientation(LinearLayout.HORIZONTAL);
setBackgroundColor(Color.TRANSPARENT);
mUrlTextView = findViewById(R.id.url_text);
mSecurityButton = (ImageButton) findViewById(R.id.security_button);
mSecurityButtonAnimationDelegate = new SecurityButtonAnimationDelegate(
mSecurityButton, mUrlTextView, R.dimen.security_status_icon_size);
mUrlBarClickListener = ObjectWrapper.unwrap(clickListener, OnClickListener.class);
mUrlBarLongClickListener =
ObjectWrapper.unwrap(longClickListener, OnLongClickListener.class);
updateView();
}
// BrowserImpl.VisibleSecurityStateObserver
@Override
public void onVisibleSecurityStateOfActiveTabChanged() {
updateView();
}
@Override
protected void onAttachedToWindow() {
if (mBrowserImpl != null) {
mBrowserImpl.addVisibleSecurityStateObserver(this);
updateView();
}
super.onAttachedToWindow();
mController.addActiveView();
}
@Override
protected void onDetachedFromWindow() {
if (mBrowserImpl != null) mBrowserImpl.removeVisibleSecurityStateObserver(this);
super.onDetachedFromWindow();
mController.removeActiveView();
}
private void updateView() {
if (mBrowserImpl == null) return;
int securityLevel = UrlBarControllerImplJni.get().getConnectionSecurityLevel(
mNativeUrlBarController);
String publisherUrl =
UrlBarControllerImplJni.get().getPublisherUrl(mNativeUrlBarController);
String displayUrl;
int securityIcon;
if (mShowPublisherUrl && securityLevel != ConnectionSecurityLevel.DANGEROUS
&& !TextUtils.isEmpty(publisherUrl)) {
displayUrl = getContext().getResources().getString(R.string.amp_publisher_url,
UrlUtilities.extractPublisherFromPublisherUrl(publisherUrl));
securityIcon = R.drawable.amp_icon;
} else {
displayUrl = getUrlForDisplay();
securityIcon = getSecurityIcon();
}
mUrlTextView.setText(displayUrl);
mUrlTextView.setTextSize(
TypedValue.COMPLEX_UNIT_SP, Math.max(MINIMUM_TEXT_SIZE, mTextSize));
Context embedderContext = mBrowserImpl.getEmbedderActivityContext();
if (mUrlTextColor != 0 && embedderContext != null) {
mUrlTextView.setTextColor(ContextCompat.getColor(embedderContext, mUrlTextColor));
}
mSecurityButtonAnimationDelegate.updateSecurityButton(securityIcon);
mSecurityButton.setContentDescription(getContext().getResources().getString(
SecurityStatusIcon.getSecurityIconContentDescriptionResourceId(securityLevel)));
if (mUrlIconColor != 0 && embedderContext != null) {
ImageViewCompat.setImageTintList(mSecurityButton,
ColorStateList.valueOf(
ContextCompat.getColor(embedderContext, mUrlIconColor)));
}
if (mShowPageInfoWhenUrlTextClicked) {
// Set clicklisteners on the entire UrlBarView.
assert (mUrlBarClickListener == null);
mSecurityButton.setClickable(false);
setOnClickListener(v -> { showPageInfoUi(v); });
if (mUrlBarLongClickListener != null) {
setOnLongClickListener(mUrlBarLongClickListener);
}
} else {
// Set a clicklistener on the security status and TextView separately. This mode
// can be used to create an editable URL bar using WebLayer.
mSecurityButton.setOnClickListener(v -> { showPageInfoUi(v); });
if (mUrlBarClickListener != null) {
mUrlTextView.setOnClickListener(mUrlBarClickListener);
}
if (mUrlBarLongClickListener != null) {
mUrlTextView.setOnLongClickListener(mUrlBarLongClickListener);
}
}
}
private void showPageInfoUi(View v) {
WebContents webContents = mBrowserImpl.getActiveTab().getWebContents();
String publisherUrl = null;
if (mShowPublisherUrl) {
String publisherUrlMaybeNull =
UrlBarControllerImplJni.get().getPublisherUrl(mNativeUrlBarController);
if (publisherUrlMaybeNull != null && !TextUtils.isEmpty(publisherUrlMaybeNull)) {
publisherUrl =
UrlUtilities.extractPublisherFromPublisherUrl(publisherUrlMaybeNull);
}
}
PageInfoController.show(mBrowserImpl.getWindowAndroid().getActivity().get(),
webContents, publisherUrl, PageInfoController.OpenedFromSource.TOOLBAR,
PageInfoControllerDelegateImpl.create(webContents),
new PermissionParamsListBuilderDelegate(mBrowserImpl.getProfile()) {
@Override
public String getDelegateAppName(
Origin origin, @ContentSettingsType int type) {
if (type == ContentSettingsType.GEOLOCATION
&& WebLayerImpl.isLocationPermissionManaged(origin)) {
return WebLayerImpl.getClientApplicationName();
}
return null;
}
},
PageInfoController.NO_HIGHLIGHTED_PERMISSION);
}
@DrawableRes
private int getSecurityIcon() {
return SecurityStatusIcon.getSecurityIconResource(
UrlBarControllerImplJni.get().getConnectionSecurityLevel(
mNativeUrlBarController),
mBrowserImpl.isWindowOnSmallDevice(),
/* skipIconForNeutralState= */ true);
}
}
@NativeMethods()
interface Natives {
long createUrlBarController(long browserPtr);
void deleteUrlBarController(long urlBarControllerImplPtr);
String getUrlForDisplay(long nativeUrlBarControllerImpl);
String getPublisherUrl(long nativeUrlBarControllerImpl);
int getConnectionSecurityLevel(long nativeUrlBarControllerImpl);
}
}