Bug 1347037 - improve SecurityModeUtil and add test cases r=sebastian

* Rename enum Mode to IconType, and added some types
* to map IconType to drawable-image-level
* add unit test

function `resolve` now returns IconType. IconType is abstraction and
independent from Drawable. It defines possible values of resolving
SiteIdentity.

We use a map to get correspond Drawable image level, then no need to
worry about Drawable-changing and algorithm.

Although unit test haven't cover all possible cases. But it passed
original implementation of `ToolbarDisplayLayout.updateSiteIdentity`.
It means `SecurityModelUtil.resolve` will not have obviously
regression....theoretically.

MozReview-Commit-ID: 3E0JMeBlg8D

--HG--
extra : rebase_source : 27d9511f2a7e01137abc0ca10c88c7929119df11
This commit is contained in:
Julian_Chu 2017-06-06 18:43:36 +08:00
Родитель c066ef2a2b
Коммит 527f8e75cd
5 изменённых файлов: 348 добавлений и 47 удалений

Просмотреть файл

@ -6,8 +6,6 @@
package org.mozilla.gecko.customtabs;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@ -227,12 +225,12 @@ public class ActionBarPresenter {
if (identity == null) {
mIconView.setVisibility(View.INVISIBLE);
} else {
final SecurityModeUtil.Mode mode = SecurityModeUtil.resolve(identity);
final SecurityModeUtil.IconType type = SecurityModeUtil.resolve(identity);
mIconView.setVisibility(View.VISIBLE);
mIconView.setImageLevel(mode.ordinal());
mIconView.setImageLevel(SecurityModeUtil.getImageLevel(type));
mIdentityPopup.setSiteIdentity(identity);
if (mode == SecurityModeUtil.Mode.LOCK_SECURE) {
if (type == SecurityModeUtil.IconType.LOCK_SECURE) {
// Lock-Secure is special case. Keep its original green color.
DrawableCompat.setTintList(mIconView.getDrawable(), null);
} else {

Просмотреть файл

@ -5,8 +5,11 @@
package org.mozilla.gecko.toolbar;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.MixedMode;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
@ -16,32 +19,91 @@ import java.util.HashMap;
import java.util.Map;
/**
* Util class which encapsulate logic of how CustomTabsActivity treats SiteIdentity.
* TODO: Bug 1347037 - This class should be reusable for other components
* Util class to help on resolving SiteIdentity to get corresponding visual result.
*/
public class SecurityModeUtil {
// defined basic mapping between SecurityMode and SecurityModeUtil.Mode
private static final Map<SecurityMode, Mode> securityModeMap;
/**
* Abstract icon type for SiteIdentity resolving algorithm. Hence no need to worry about
* Drawable level value.
*/
public enum IconType {
UNKNOWN,
DEFAULT,
SEARCH,
LOCK_SECURE,
LOCK_WARNING, // not used for now. reserve for MixedDisplayContent icon, if any.
LOCK_INSECURE,
WARNING,
TRACKING_CONTENT_BLOCKED,
TRACKING_CONTENT_LOADED
}
// Defined mapping between IconType and Drawable image-level
private static final Map<IconType, Integer> imgLevelMap = new HashMap<>();
// http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/site_security_icon.xml
static {
imgLevelMap.put(IconType.UNKNOWN, 0);
imgLevelMap.put(IconType.DEFAULT, 0);
imgLevelMap.put(IconType.LOCK_SECURE, 1);
imgLevelMap.put(IconType.WARNING, 2);
imgLevelMap.put(IconType.LOCK_INSECURE, 3);
imgLevelMap.put(IconType.TRACKING_CONTENT_BLOCKED, 4);
imgLevelMap.put(IconType.TRACKING_CONTENT_LOADED, 5);
imgLevelMap.put(IconType.SEARCH, 6);
}
// defined basic mapping between SecurityMode and SecurityModeUtil.IconType
private static final Map<SecurityMode, IconType> securityModeMap;
static {
securityModeMap = new HashMap<>();
securityModeMap.put(SecurityMode.UNKNOWN, Mode.UNKNOWN);
securityModeMap.put(SecurityMode.IDENTIFIED, Mode.LOCK_SECURE);
securityModeMap.put(SecurityMode.VERIFIED, Mode.LOCK_SECURE);
securityModeMap.put(SecurityMode.CHROMEUI, Mode.UNKNOWN);
securityModeMap.put(SecurityMode.UNKNOWN, IconType.UNKNOWN);
securityModeMap.put(SecurityMode.IDENTIFIED, IconType.LOCK_SECURE);
securityModeMap.put(SecurityMode.VERIFIED, IconType.LOCK_SECURE);
securityModeMap.put(SecurityMode.CHROMEUI, IconType.UNKNOWN);
}
/**
* To resolve which mode to be used for given SiteIdentity. Its logic is similar to
* ToolbarDisplayLayout.updateSiteIdentity
* Get image level from IconType, and to use in ImageView.setImageLevel().
*
* @param type IconType which is resolved by method resolve()
* @return the image level which defined in Drawable
*/
public static int getImageLevel(@NonNull final IconType type) {
return imgLevelMap.containsKey(type)
? imgLevelMap.get(type)
: imgLevelMap.get(IconType.UNKNOWN);
}
/**
* To resolve which icon-type to be used for given SiteIdentity.
*
* @param identity An identity of a site to be resolved
* @return Corresponding mode for resolved SiteIdentity, UNKNOWN as default.
* @return Corresponding type for resolved SiteIdentity, UNKNOWN as default.
*/
public static Mode resolve(final @Nullable SiteIdentity identity) {
public static IconType resolve(@Nullable final SiteIdentity identity) {
return resolve(identity, null);
}
/**
* To resolve which icon-type to be used for given SiteIdentity.
*
* @param identity An identity of a site to be resolved
* @param url current page url
* @return Corresponding type for resolved SiteIdentity, UNKNOWN as default.
*/
public static IconType resolve(@Nullable final SiteIdentity identity,
@Nullable final String url) {
if (!TextUtils.isEmpty(url) && AboutPages.isTitlelessAboutPage(url)) {
// We always want to just show a search icon on about:home
return IconType.SEARCH;
}
if (identity == null) {
return Mode.UNKNOWN;
return IconType.UNKNOWN;
}
final SecurityMode securityMode = identity.getSecurityMode();
@ -50,35 +112,40 @@ public class SecurityModeUtil {
final TrackingMode trackingMode = identity.getTrackingMode();
final boolean securityException = identity.isSecurityException();
if (securityMode == SiteIdentity.SecurityMode.CHROMEUI) {
return Mode.UNKNOWN;
if (securityException) {
return IconType.WARNING;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
return IconType.TRACKING_CONTENT_LOADED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
return IconType.TRACKING_CONTENT_BLOCKED;
} else if (activeMixedMode == MixedMode.LOADED) {
return IconType.LOCK_INSECURE;
} else if (displayMixedMode == MixedMode.LOADED) {
return IconType.WARNING;
}
if (securityException) {
return Mode.MIXED_MODE;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
return Mode.TRACKING_CONTENT_LOADED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
return Mode.TRACKING_CONTENT_BLOCKED;
} else if (activeMixedMode == MixedMode.LOADED) {
return Mode.MIXED_MODE;
} else if (displayMixedMode == MixedMode.LOADED) {
return Mode.WARNING;
// Chrome-UI checking is after tracking/mixed-content, even for about: pages, as they
// can still load external sites.
if (securityMode == SiteIdentity.SecurityMode.CHROMEUI) {
return IconType.DEFAULT;
}
return securityModeMap.containsKey(securityMode)
? securityModeMap.get(securityMode)
: Mode.UNKNOWN;
: IconType.UNKNOWN;
}
// Security mode constants, which map to the icons / levels defined in:
// http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/customtabs_site_security_level.xml
public enum Mode {
UNKNOWN,
LOCK_SECURE,
WARNING,
MIXED_MODE,
TRACKING_CONTENT_BLOCKED,
TRACKING_CONTENT_LOADED
/**
* For a given SiteIdentity, to check whether its tracking protection is enabled.
*
* @param identity to be checked
* @return true if tracking protection is enabled.
*/
public static boolean isTrackingProtectionEnabled(final @Nullable SiteIdentity identity) {
final TrackingMode trackingMode = (identity == null)
? TrackingMode.UNKNOWN
: identity.getTrackingMode();
return (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
}
}

Просмотреть файл

@ -1,8 +1,10 @@
<?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/. -->
<!-- level value is matter. It should match SecurityModeUtil.getImageLevel -->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
@ -23,5 +25,8 @@
<item
android:drawable="@drawable/shield_disabled"
android:maxLevel="5" />
<item
android:drawable="@drawable/search_icon_inactive"
android:maxLevel="6" />
</level-list>

Просмотреть файл

@ -24,7 +24,7 @@
android:contentDescription="@string/site_security"
android:padding="3dp"
android:scaleType="fitCenter"
android:src="@drawable/customtabs_site_security_icon"
android:src="@drawable/site_security_icon"
android:visibility="invisible"/>
</FrameLayout>

Просмотреть файл

@ -0,0 +1,231 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.toolbar;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.MixedMode;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.SiteIdentity.TrackingMode;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mozilla.gecko.toolbar.SecurityModeUtil.IconType;
import static org.mozilla.gecko.toolbar.SecurityModeUtil.resolve;
@RunWith(TestRunner.class)
public class TestSecurityModeUtil {
private SiteIdentity identity;
@Before
public void setUp() {
identity = spy(new SiteIdentity());
}
/**
* To test resolve function if there is not any SiteIdentity
*/
@Test
public void testNoSiteIdentity() {
assertEquals(IconType.UNKNOWN, resolve(null));
assertEquals(IconType.SEARCH, resolve(null, "about:home"));
assertEquals(IconType.UNKNOWN, resolve(null, "about:firefox"));
assertEquals(IconType.UNKNOWN, resolve(null, "https://mozilla.com"));
}
/**
* To test resolve function for SecurityException.
* SecurityException detection is prior than SecurityMode, TrackingMode and MixedMode.
* If SecurityException exists, it should keep returning WARNING until url is "about:home".
*/
@Test
public void testSecurityException() {
// SecurityException exists for each of below cases
doReturn(true).when(identity).isSecurityException();
doReturn(SecurityMode.UNKNOWN).when(identity).getSecurityMode();
// about:home always show Search Icon
assertEquals(IconType.SEARCH, resolve(identity, "about:home"));
// other cases, return WARNING
assertEquals(IconType.WARNING, resolve(identity));
assertEquals(IconType.WARNING, resolve(identity, "https://mozilla.com"));
// even about:* pages, they can still load external sites.
assertEquals(IconType.WARNING, resolve(identity, "about:firefox"));
// even specify ChromeUI, still respect SecurityException
doReturn(SecurityMode.CHROMEUI).when(identity).getSecurityMode();
assertEquals(IconType.WARNING, resolve(identity, "about:firefox"));
// TrackingMode does not matter
doReturn(TrackingMode.TRACKING_CONTENT_BLOCKED).when(identity).getTrackingMode();
assertEquals(IconType.WARNING, resolve(identity));
doReturn(TrackingMode.TRACKING_CONTENT_LOADED).when(identity).getTrackingMode();
assertEquals(IconType.WARNING, resolve(identity));
// MixedModeDisplay does not matter
doReturn(MixedMode.LOADED).when(identity).getMixedModeDisplay();
assertEquals(IconType.WARNING, resolve(identity));
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeDisplay();
assertEquals(IconType.WARNING, resolve(identity));
// MixedModeActive does not matter
doReturn(MixedMode.LOADED).when(identity).getMixedModeActive();
assertEquals(IconType.WARNING, resolve(identity));
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeActive();
assertEquals(IconType.WARNING, resolve(identity));
}
/**
* To test resolve function for TrackingContentLoaded, without SecurityException.
* TrackingMode detection is prior than SecurityMode and MixedMode.
* If TrackingMode is TRACKING_CONTENT_LOADED, it should keep returning TRACKING_CONTENT_LOADED
* icon until url is "about:home".
*/
@Test
public void testTrackingContentLoaded() {
doReturn(false).when(identity).isSecurityException();
// enable TRACKING_CONTENT_LOADED
doReturn(TrackingMode.TRACKING_CONTENT_LOADED).when(identity).getTrackingMode();
// about:home always show Search Icon
doReturn(SecurityMode.UNKNOWN).when(identity).getSecurityMode();
assertEquals(IconType.SEARCH, resolve(identity, "about:home"));
// otherwise, return icon for TRACKING_CONTENT_LOADED
assertEquals(IconType.TRACKING_CONTENT_LOADED, resolve(identity));
assertEquals(IconType.TRACKING_CONTENT_LOADED, resolve(identity, "https://mozilla.com"));
// even for SecurityMode.CHROMEUI, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.CHROMEUI).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_LOADED, resolve(identity));
// even for SecurityMode.VERIFIED, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.VERIFIED).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_LOADED, resolve(identity));
// even for SecurityMode.IDENTIFIED, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.IDENTIFIED).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_LOADED, resolve(identity));
}
/**
* To test resolve function for TrackingContentBlocked, without SecurityException.
* TrackingMode detection is prior than SecurityMode and MixedMode.
* If TrackingMode is TRACKING_CONTENT_BLOCKED, it should keep returning TRACKING_CONTENT_BLOCKED
* icon until url is "about:home".
*/
@Test
public void testTrackingContentBlocked() {
doReturn(false).when(identity).isSecurityException();
// enable TRACKING_CONTENT_BLOCKED
doReturn(TrackingMode.TRACKING_CONTENT_BLOCKED).when(identity).getTrackingMode();
// about:home always show Search Icon
doReturn(SecurityMode.UNKNOWN).when(identity).getSecurityMode();
assertEquals(IconType.SEARCH, resolve(identity, "about:home"));
// otherwise, return icon for TRACKING_CONTENT_BLOCKED
assertEquals(IconType.TRACKING_CONTENT_BLOCKED, resolve(identity));
// even for SecurityMode.CHROMEUI, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.CHROMEUI).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_BLOCKED, resolve(identity));
// even for SecurityMode.VERIFIED, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.VERIFIED).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_BLOCKED, resolve(identity));
// even for SecurityMode.IDENTIFIED, TrackingMode still prior than SecurityMode.
doReturn(SecurityMode.IDENTIFIED).when(identity).getSecurityMode();
assertEquals(IconType.TRACKING_CONTENT_BLOCKED, resolve(identity));
}
/**
* To test resolve function for MixedMode, without SecurityException nor TrackingMode.
* MixedMode detection is prior than SecurityMode. And in MixedMode, MixedActiveContent
* is prior than MixedDisplayContent.
*/
@Test
public void testMixedMode() {
doReturn(false).when(identity).isSecurityException();
doReturn(TrackingMode.UNKNOWN).when(identity).getTrackingMode();
// MixedActiveContent loaded, MixedDisplayContent loaded
doReturn(MixedMode.LOADED).when(identity).getMixedModeActive();
doReturn(MixedMode.LOADED).when(identity).getMixedModeDisplay();
// about:home always show Search Icon
doReturn(SecurityMode.UNKNOWN).when(identity).getSecurityMode();
assertEquals(IconType.SEARCH, resolve(identity, "about:home"));
// otherwise, return icon for MixedModeActive-loaded
assertEquals(IconType.LOCK_INSECURE, resolve(identity));
// MixedActiveContent loaded, MixedDisplayContent blocked
// It should be same as ActiveContent-loaded-DisplayContent-loaded
doReturn(MixedMode.LOADED).when(identity).getMixedModeActive();
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeDisplay();
assertEquals(IconType.LOCK_INSECURE, resolve(identity));
// MixedActiveContent blocked, MixedDisplayContent loaded
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeActive();
doReturn(MixedMode.LOADED).when(identity).getMixedModeDisplay();
assertEquals(IconType.WARNING, resolve(identity));
}
/**
* To test resolve function for SecurityMode, without SecurityException nor TrackingMode nor
* MixedMode.
*/
@Test
public void testSecurityMode() {
doReturn(false).when(identity).isSecurityException();
doReturn(TrackingMode.UNKNOWN).when(identity).getTrackingMode();
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeActive();
doReturn(MixedMode.BLOCKED).when(identity).getMixedModeDisplay();
// about:home always show Search Icon
doReturn(SecurityMode.UNKNOWN).when(identity).getSecurityMode();
assertEquals(IconType.SEARCH, resolve(identity, "about:home"));
// for SecurityMode.CHROMEUI, We should show a global icon
doReturn(SecurityMode.CHROMEUI).when(identity).getSecurityMode();
assertEquals(IconType.DEFAULT, resolve(identity));
// for SecurityMode.VERIFIED, We should show a VERIFIED icon
doReturn(SecurityMode.VERIFIED).when(identity).getSecurityMode();
assertEquals(IconType.LOCK_SECURE, resolve(identity));
// for SecurityMode.IDENTIFIED, We should show a IDENTIFIED icon
doReturn(SecurityMode.IDENTIFIED).when(identity).getSecurityMode();
assertEquals(IconType.LOCK_SECURE, resolve(identity));
}
@Test
public void testIsTrackingProtectionEnabled() {
// no identity, the tracking protection should be regard as disabled
Assert.assertFalse(SecurityModeUtil.isTrackingProtectionEnabled(null));
doReturn(TrackingMode.UNKNOWN).when(identity).getTrackingMode();
Assert.assertFalse(SecurityModeUtil.isTrackingProtectionEnabled(identity));
doReturn(TrackingMode.TRACKING_CONTENT_LOADED).when(identity).getTrackingMode();
Assert.assertFalse(SecurityModeUtil.isTrackingProtectionEnabled(identity));
doReturn(TrackingMode.TRACKING_CONTENT_BLOCKED).when(identity).getTrackingMode();
Assert.assertTrue(SecurityModeUtil.isTrackingProtectionEnabled(identity));
}
}