mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 11:15:34 +00:00
Bug 1377583 - Expose security information in custom tabs. r=snorp
This adds a class, CustomTabsSecurityPopup, which allows us to display information from the security certificate on secure sites.
This commit is contained in:
parent
4e2ab7b208
commit
0740754325
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_medium">
|
||||
|
||||
<ImageView android:id="@+id/site_identity_icon"
|
||||
android:layout_width="@dimen/doorhanger_icon_size"
|
||||
android:layout_height="@dimen/doorhanger_icon_size"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="@dimen/doorhanger_section_padding_small"
|
||||
android:layout_marginRight="@dimen/doorhanger_section_padding_small"/>
|
||||
|
||||
<LinearLayout android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<TextView android:id="@+id/site_identity_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
|
||||
|
||||
<TextView android:id="@+id/site_identity_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_subsection_padding"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"/>
|
||||
|
||||
<TextView android:id="@+id/mixed_content_activity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout android:id="@+id/site_identity_known_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:id="@+id/owner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView android:id="@+id/verifier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@ -29,11 +29,12 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mozilla.gecko.GeckoView;
|
||||
import org.mozilla.gecko.GeckoView.ProgressListener.SecurityInformation;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.SiteIdentity;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.toolbar.SecurityModeUtil;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
import org.mozilla.gecko.toolbar.CustomTabsSecurityPopup;
|
||||
import org.mozilla.gecko.util.ColorUtil;
|
||||
|
||||
/**
|
||||
@ -47,7 +48,7 @@ public class ActionBarPresenter {
|
||||
private static final long CUSTOM_VIEW_UPDATE_DELAY = 1000;
|
||||
|
||||
private final ActionBar mActionBar;
|
||||
private final SiteIdentityPopup mIdentityPopup;
|
||||
private final CustomTabsSecurityPopup mIdentityPopup;
|
||||
private final ImageButton mIconView;
|
||||
private final TextView mTitleView;
|
||||
private final TextView mUrlView;
|
||||
@ -73,7 +74,7 @@ public class ActionBarPresenter {
|
||||
mTitleView.setTextColor(mTextPrimaryColor);
|
||||
mUrlView.setTextColor(mTextPrimaryColor);
|
||||
|
||||
mIdentityPopup = new SiteIdentityPopup(mActionBar.getThemedContext());
|
||||
mIdentityPopup = new CustomTabsSecurityPopup(mActionBar.getThemedContext());
|
||||
mIdentityPopup.setAnchor(customView);
|
||||
mIconView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -85,29 +86,13 @@ public class ActionBarPresenter {
|
||||
initIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ActionBar is to start interacting with user. Usually this method is called from
|
||||
* Activity.onResume.
|
||||
*/
|
||||
public void onResume() {
|
||||
mIdentityPopup.registerListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ActionBar is going to background, but has not yet been killed. Usually this method
|
||||
* is called from Activity.onPause.
|
||||
*/
|
||||
public void onPause() {
|
||||
mIdentityPopup.unregisterListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* To display Url in CustomView only and immediately.
|
||||
*
|
||||
* @param url Url String to display
|
||||
*/
|
||||
public void displayUrlOnly(@NonNull final String url) {
|
||||
updateCustomView(null, url, /* isSecure */ false);
|
||||
updateCustomView(null, url, /* security */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,16 +100,16 @@ public class ActionBarPresenter {
|
||||
*
|
||||
* @param title Title for current website. Could be null if don't want to show title.
|
||||
* @param url URL for current website. At least Custom will show this url.
|
||||
* @param isSecure A boolean representing whether or not the site is secure.
|
||||
* @param security A SecurityInformation object giving the current security information
|
||||
*/
|
||||
public void update(final String title, final String url, final boolean isSecure) {
|
||||
public void update(final String title, final String url, final SecurityInformation security) {
|
||||
// Do not update CustomView immediately. If this method be invoked rapidly several times,
|
||||
// only apply last one.
|
||||
mHandler.removeCallbacks(mUpdateAction);
|
||||
mUpdateAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateCustomView(title, url, isSecure);
|
||||
updateCustomView(title, url, security);
|
||||
}
|
||||
};
|
||||
mHandler.postDelayed(mUpdateAction, CUSTOM_VIEW_UPDATE_DELAY);
|
||||
@ -215,18 +200,30 @@ public class ActionBarPresenter {
|
||||
*
|
||||
* @param title Title for current website. Could be null if don't want to show title.
|
||||
* @param url URL for current website. At least Custom will show this url.
|
||||
* @param isSecure A boolean representing whether or not the site is secure.
|
||||
* @param security A SecurityInformation object giving the current security information
|
||||
*/
|
||||
@UiThread
|
||||
private void updateCustomView(final String title, final String url, final boolean isSecure) {
|
||||
if (isSecure) {
|
||||
mIconView.setVisibility(View.VISIBLE);
|
||||
mIconView.setImageLevel(SecurityModeUtil.getImageLevel(SecurityModeUtil.IconType.LOCK_SECURE));
|
||||
// Lock-Secure is special case. Keep its original green color.
|
||||
DrawableCompat.setTintList(mIconView.getDrawable(), null);
|
||||
} else {
|
||||
private void updateCustomView(final String title, final String url, final SecurityInformation security) {
|
||||
if (security == null) {
|
||||
mIconView.setVisibility(View.INVISIBLE);
|
||||
DrawableCompat.setTint(mIconView.getDrawable(), mTextPrimaryColor);
|
||||
} else {
|
||||
SecurityModeUtil.IconType icon;
|
||||
if ("unknown".equals(security.securityMode)) {
|
||||
icon = SecurityModeUtil.IconType.UNKNOWN;
|
||||
} else {
|
||||
icon = SecurityModeUtil.IconType.LOCK_SECURE;
|
||||
}
|
||||
mIconView.setVisibility(View.VISIBLE);
|
||||
mIconView.setImageLevel(SecurityModeUtil.getImageLevel(icon));
|
||||
mIdentityPopup.setSecurityInformation(security);
|
||||
|
||||
if (icon == SecurityModeUtil.IconType.LOCK_SECURE) {
|
||||
// Lock-Secure is a special case. Keep its original green color.
|
||||
DrawableCompat.setTintList(mIconView.getDrawable(), null);
|
||||
} else {
|
||||
// Icon uses same color as TextView.
|
||||
DrawableCompat.setTint(mIconView.getDrawable(), mTextPrimaryColor);
|
||||
}
|
||||
}
|
||||
|
||||
// If no title to use, use Url as title
|
||||
|
@ -77,7 +77,7 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||
private boolean mCanStop = false;
|
||||
private String mCurrentUrl;
|
||||
private String mCurrentTitle;
|
||||
private boolean mIsSecure = false;
|
||||
private SecurityInformation mSecurityInformation = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@ -424,7 +424,7 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||
* Update the state of the action bar
|
||||
*/
|
||||
private void updateActionBar() {
|
||||
actionBarPresenter.update(mCurrentTitle, mCurrentUrl, mIsSecure);
|
||||
actionBarPresenter.update(mCurrentTitle, mCurrentUrl, mSecurityInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,7 +549,7 @@ public class CustomTabsActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void onSecurityChange(GeckoView view, SecurityInformation securityInfo) {
|
||||
mIsSecure = securityInfo.isSecure;
|
||||
mSecurityInformation = securityInfo;
|
||||
updateActionBar();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,250 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.toolbar;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.TextViewCompat;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoView;
|
||||
import org.mozilla.gecko.GeckoView.ProgressListener.SecurityInformation;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.SiteIdentity;
|
||||
import org.mozilla.gecko.SiteIdentity.SecurityMode;
|
||||
import org.mozilla.gecko.SiteIdentity.MixedMode;
|
||||
import org.mozilla.gecko.SiteIdentity.TrackingMode;
|
||||
import org.mozilla.gecko.SnackbarBuilder;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.AnchoredPopup;
|
||||
import org.mozilla.gecko.widget.DoorHanger;
|
||||
import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.widget.DoorhangerConfig;
|
||||
import org.mozilla.gecko.widget.SiteLogins;
|
||||
|
||||
/**
|
||||
* CustomTabsSecurityPopup is a singleton class that displays site identity data in
|
||||
* an arrow panel popup hanging from the lock icon in the browser toolbar.
|
||||
*
|
||||
* A site identity icon may be displayed in the url, and is set in <code>ToolbarDisplayLayout</code>.
|
||||
*/
|
||||
public class CustomTabsSecurityPopup extends AnchoredPopup {
|
||||
private static final String LOGTAG = "CustomTabsSecurityPopup";
|
||||
|
||||
private final Resources mResources;
|
||||
private SecurityInformation mSecurityInformation;
|
||||
|
||||
private LinearLayout mIdentity;
|
||||
|
||||
private LinearLayout mIdentityKnownContainer;
|
||||
|
||||
private ImageView mIcon;
|
||||
private TextView mTitle;
|
||||
private TextView mSecurityState;
|
||||
private TextView mMixedContentActivity;
|
||||
private TextView mOwner;
|
||||
private TextView mVerifier;
|
||||
|
||||
public CustomTabsSecurityPopup(Context context) {
|
||||
super(context);
|
||||
|
||||
mResources = mContext.getResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
// Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
|
||||
// which may reshow the popup (see bug 785156)
|
||||
setFocusable(true);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
mIdentity = (LinearLayout) inflater.inflate(R.layout.customtabs_site_identity, null);
|
||||
mContent.addView(mIdentity);
|
||||
|
||||
mIdentityKnownContainer =
|
||||
(LinearLayout) mIdentity.findViewById(R.id.site_identity_known_container);
|
||||
|
||||
mIcon = (ImageView) mIdentity.findViewById(R.id.site_identity_icon);
|
||||
mTitle = (TextView) mIdentity.findViewById(R.id.site_identity_title);
|
||||
mSecurityState = (TextView) mIdentity.findViewById(R.id.site_identity_state);
|
||||
mMixedContentActivity = (TextView) mIdentity.findViewById(R.id.mixed_content_activity);
|
||||
|
||||
mOwner = (TextView) mIdentityKnownContainer.findViewById(R.id.owner);
|
||||
mVerifier = (TextView) mIdentityKnownContainer.findViewById(R.id.verifier);
|
||||
}
|
||||
|
||||
private void updateSecurityInformation(final SecurityInformation security) {
|
||||
if (!mInflated) {
|
||||
init();
|
||||
}
|
||||
|
||||
final boolean isIdentityKnown = ("identified".equals(security.securityMode) ||
|
||||
"verified".equals(security.securityMode));
|
||||
updateConnectionState(security);
|
||||
toggleIdentityKnownContainerVisibility(isIdentityKnown);
|
||||
|
||||
if (isIdentityKnown) {
|
||||
updateIdentityInformation(security);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleIdentityKnownContainerVisibility(final boolean isIdentityKnown) {
|
||||
final int identityInfoVisibility = isIdentityKnown ? View.VISIBLE : View.GONE;
|
||||
mIdentityKnownContainer.setVisibility(identityInfoVisibility);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the SecurityInformation content to reflect connection state.
|
||||
*
|
||||
* The connection state should reflect the combination of:
|
||||
* a) Connection encryption
|
||||
* b) Mixed Content state (Active/Display Mixed content, loaded, blocked, none, etc)
|
||||
* and update the icons and strings to inform the user of that state.
|
||||
*
|
||||
* @param security SecurityInformation about the connection.
|
||||
*/
|
||||
private void updateConnectionState(final SecurityInformation security) {
|
||||
if (!security.isSecure) {
|
||||
if ("loaded".equals(security.mixedModeActive)) {
|
||||
// Active Mixed Content loaded because user has disabled blocking.
|
||||
mIcon.setImageResource(R.drawable.ic_lock_disabled);
|
||||
clearSecurityStateIcon();
|
||||
mMixedContentActivity.setVisibility(View.VISIBLE);
|
||||
mMixedContentActivity.setText(R.string.mixed_content_protection_disabled);
|
||||
} else if ("loaded".equals(security.mixedModePassive)) {
|
||||
// Passive Mixed Content loaded.
|
||||
mIcon.setImageResource(R.drawable.ic_lock_inactive);
|
||||
setSecurityStateIcon(R.drawable.ic_warning_major, 1);
|
||||
mMixedContentActivity.setVisibility(View.VISIBLE);
|
||||
if ("blocked".equals(security.mixedModeActive)) {
|
||||
mMixedContentActivity.setText(R.string.mixed_content_blocked_some);
|
||||
} else {
|
||||
mMixedContentActivity.setText(R.string.mixed_content_display_loaded);
|
||||
}
|
||||
} else {
|
||||
// Unencrypted connection with no mixed content.
|
||||
mIcon.setImageResource(R.drawable.globe_light);
|
||||
clearSecurityStateIcon();
|
||||
|
||||
mMixedContentActivity.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mSecurityState.setText(R.string.identity_connection_insecure);
|
||||
mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.placeholder_active_grey));
|
||||
|
||||
} else if (security.isException) {
|
||||
|
||||
mIcon.setImageResource(R.drawable.ic_lock_inactive);
|
||||
setSecurityStateIcon(R.drawable.ic_warning_major, 1);
|
||||
mSecurityState.setText(R.string.identity_connection_insecure);
|
||||
mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.placeholder_active_grey));
|
||||
|
||||
} else {
|
||||
// Connection is secure.
|
||||
mIcon.setImageResource(R.drawable.ic_lock);
|
||||
|
||||
setSecurityStateIcon(R.drawable.img_check, 2);
|
||||
mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.affirmative_green));
|
||||
mSecurityState.setText(R.string.identity_connection_secure);
|
||||
|
||||
// Mixed content has been blocked, if present.
|
||||
if ("blocked".equals(security.mixedModeActive) ||
|
||||
"blocked".equals(security.mixedModePassive)) {
|
||||
mMixedContentActivity.setVisibility(View.VISIBLE);
|
||||
mMixedContentActivity.setText(R.string.mixed_content_blocked_all);
|
||||
} else {
|
||||
mMixedContentActivity.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSecurityStateIcon() {
|
||||
mSecurityState.setCompoundDrawablePadding(0);
|
||||
TextViewCompat.setCompoundDrawablesRelative(mSecurityState, null, null, null, null);
|
||||
}
|
||||
|
||||
private void setSecurityStateIcon(int resource, int factor) {
|
||||
final Drawable stateIcon = ContextCompat.getDrawable(mContext, resource);
|
||||
stateIcon.setBounds(0, 0, stateIcon.getIntrinsicWidth() / factor, stateIcon.getIntrinsicHeight() / factor);
|
||||
TextViewCompat.setCompoundDrawablesRelative(mSecurityState, stateIcon, null, null, null);
|
||||
mSecurityState.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
}
|
||||
|
||||
private void updateIdentityInformation(final SecurityInformation security) {
|
||||
String owner = security.organization;
|
||||
if (owner == null) {
|
||||
mOwner.setVisibility(View.GONE);
|
||||
} else {
|
||||
mOwner.setText(owner);
|
||||
mOwner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// TODO: This will differ from Fennec currently, as SiteIdentityPopup uses a localized string
|
||||
mVerifier.setText(security.issuerCommonName);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param identityData An object that holds the current tab's identity data.
|
||||
*/
|
||||
public void setSecurityInformation(SecurityInformation security) {
|
||||
mSecurityInformation = security;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (mSecurityInformation == null) {
|
||||
Log.e(LOGTAG, "Can't show site identity popup for undefined state");
|
||||
return;
|
||||
}
|
||||
|
||||
updateSecurityInformation(mSecurityInformation);
|
||||
|
||||
mTitle.setText(mSecurityInformation.host);
|
||||
|
||||
// TODO: Might be nice to revive this at some point, but very low priority.
|
||||
/*final Bitmap favicon = selectedTab.getFavicon();
|
||||
if (favicon != null) {
|
||||
final Drawable faviconDrawable = new BitmapDrawable(mResources, favicon);
|
||||
final int dimen = (int) mResources.getDimension(R.dimen.browser_toolbar_favicon_size);
|
||||
faviconDrawable.setBounds(0, 0, dimen, dimen);
|
||||
|
||||
TextViewCompat.setCompoundDrawablesRelative(mTitle, faviconDrawable, null, null, null);
|
||||
mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
}*/
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mTitle, null, null, null, null);
|
||||
}
|
||||
}
|
@ -554,6 +554,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'CustomEditText.java',
|
||||
'customtabs/ActionBarPresenter.java',
|
||||
'customtabs/CustomTabsActivity.java',
|
||||
'customtabs/CustomTabsSecurityPopup.java',
|
||||
'customtabs/GeckoCustomTabsService.java',
|
||||
'customtabs/IntentUtil.java',
|
||||
'DataReportingNotification.java',
|
||||
|
Loading…
Reference in New Issue
Block a user