diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index 40a533fa38..fe0006c9fa 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -484,7 +484,6 @@ protected void onDestroy() { SessionStore.get().onDestroy(); - super.onDestroy(); mLifeCycle.setCurrentState(Lifecycle.State.DESTROYED); mViewModelStore.clear(); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java index 6e8a031f72..93aa327a90 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserApplication.java @@ -34,7 +34,6 @@ public void onCreate() { mAppExecutors = new AppExecutors(); mBitmapCache = new BitmapCache(this, mAppExecutors.diskIO(), mAppExecutors.mainThread()); - TelemetryWrapper.init(this); GleanMetricsService.init(this); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java index 30f994abb7..8bfd4bd84e 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java @@ -286,7 +286,7 @@ public void setPermissionAllowed(String uri, @SitePermission.Category int catego mSitePermissionModel.deleteSite(site); } else { if (site == null) { - site = new SitePermission(uri, false, SitePermission.SITE_PERMISSION_WEBXR); + site = new SitePermission(uri, uri, false, SitePermission.SITE_PERMISSION_WEBXR); mSitePermissions.add(site); } site.allowed = false; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/PromptDelegate.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/PromptDelegate.java index 59652d76b4..faab03b5cb 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/PromptDelegate.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/PromptDelegate.java @@ -357,7 +357,7 @@ private void showPopUp(int sessionId, @NonNull Pair(); @@ -57,6 +60,14 @@ public void setContext(Context context, Bundle aExtras) { SessionUtils.vrPrefsWorkAround(context, aExtras); mRuntime = EngineProvider.INSTANCE.getOrCreateRuntime(context); + + mTrackingProtectionStore = new TrackingProtectionStore(context, mRuntime); + mTrackingProtectionStore.addListener(new TrackingProtectionStore.TrackingProtectionListener() { + @Override + public void onTrackingProtectionLevelUpdated(@TrackingProtectionPolicy.ETP_Level int level) { + mSessions.forEach(session -> session.reload(GeckoSession.LOAD_FLAGS_NONE)); + } + }); } public void initializeServices() { @@ -210,6 +221,10 @@ public HistoryStore getHistoryStore() { return mHistoryStore; } + public TrackingProtectionStore getTrackingProtectionStore() { + return mTrackingProtectionStore; + } + public void purgeSessionHistory() { for (Session session: mSessions) { session.purgeHistory(); @@ -246,18 +261,6 @@ public void setServo(final boolean enabled) { } } - public void setUaMode(final int mode) { - for (Session session: mSessions) { - session.setUaMode(mode); - } - } - - public void setTrackingProtection(final boolean aEnabled) { - for (Session session: mSessions) { - session.setTrackingProtection(aEnabled); - } - } - // Runtime Settings public void setConsoleOutputEnabled(boolean enabled) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionPolicy.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionPolicy.java new file mode 100644 index 0000000000..50edbbf3bd --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionPolicy.java @@ -0,0 +1,133 @@ +package org.mozilla.vrbrowser.browser.tracking; + +import androidx.annotation.IntDef; + +import org.mozilla.geckoview.ContentBlocking; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TrackingProtectionPolicy { + + @IntDef(value = { ETP_NONE, ETP_DEFAULT, ETP_STRICT}) + public @interface ETP_Level {} + public static final int ETP_NONE = ContentBlocking.EtpLevel.NONE; + public static final int ETP_DEFAULT = ContentBlocking.EtpLevel.DEFAULT; + public static final int ETP_STRICT = ContentBlocking.EtpLevel.STRICT; + + private List trackingCategory; + private CookiePolicy cookiePolicy; + private Boolean strictSocialTrackingProtection = null; + + private TrackingProtectionPolicy() { + trackingCategory = new ArrayList<>(); + } + + /** + * Strict policy. + * Combining the [TrackingCategory.STRICT] plus a cookiePolicy of [ACCEPT_NON_TRACKERS] + * This is the strictest setting and may cause issues on some web sites. + */ + public static TrackingProtectionPolicy strict() { + TrackingProtectionPolicy policy = new TrackingProtectionPolicy(); + policy.trackingCategory = Collections.singletonList(TrackingCategory.STRICT); + policy.cookiePolicy = CookiePolicy.ACCEPT_NON_TRACKERS; + policy.strictSocialTrackingProtection = true; + return policy; + } + + /** + * Recommended policy. + * Combining the [TrackingCategory.RECOMMENDED] plus a [CookiePolicy] of [ACCEPT_NON_TRACKERS]. + * This is the recommended setting. + */ + public static TrackingProtectionPolicy recommended() { + TrackingProtectionPolicy policy = new TrackingProtectionPolicy(); + policy.trackingCategory = Collections.singletonList(TrackingCategory.RECOMMENDED); + policy.cookiePolicy = CookiePolicy.ACCEPT_NON_TRACKERS; + policy.strictSocialTrackingProtection = false; + return policy; + } + + public static TrackingProtectionPolicy none() { + TrackingProtectionPolicy policy = new TrackingProtectionPolicy(); + policy.trackingCategory = Collections.singletonList(TrackingCategory.NONE); + policy.cookiePolicy = CookiePolicy.ACCEPT_ALL; + return policy; + } + + public enum TrackingCategory { + NONE(0), + AD(1 << 1), + ANALYTICS(1 << 2), + SOCIAL(1 << 3), + CONTENT(1 << 4), + TEST(1 << 5), + CRYPTOMINING(1 << 6), + FINGERPRINTING(1 << 7), + MOZILLA_SOCIAL(1 << 8), + SCRIPTS_AND_SUB_RESOURCES(1 << 9999), + RECOMMENDED(AD.value + ANALYTICS.value + SOCIAL.value + + TEST.value + MOZILLA_SOCIAL.value + CRYPTOMINING.value), + STRICT(RECOMMENDED.value + SCRIPTS_AND_SUB_RESOURCES.value + + FINGERPRINTING.value); + + private final int value; + + TrackingCategory(final int newValue) { + value = newValue; + } + + public int getValue() { return value; } + } + + public enum CookiePolicy { + ACCEPT_ALL(0), + ACCEPT_ONLY_FIRST_PARTY(1), + ACCEPT_NONE(2), + ACCEPT_VISITED(3), + ACCEPT_NON_TRACKERS(4); + + private final int value; + + CookiePolicy(final int newValue) { + value = newValue; + } + + public int getValue() { return value; } + } + + public List getTrackingCategory() { + return trackingCategory; + } + + public Boolean getStrictSocialTrackingProtection() { + return strictSocialTrackingProtection; + } + + public CookiePolicy getCookiePolicy() { + return cookiePolicy; + } + + public int getAntiTrackingPolicy() { + /* + * The [TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES] is an + * artificial category, created with the sole purpose of going around this bug + * https://bugzilla.mozilla.org/show_bug.cgi?id=1579264, for this reason we have to + * remove its value from the valid anti tracking categories, when is present. + */ + int total = trackingCategory.stream().mapToInt(TrackingCategory::getValue).sum(); + if (contains(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)) { + return total - TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.getValue(); + } else { + return total; + } + } + + public boolean contains(TrackingCategory category) { + int sum = trackingCategory.stream().mapToInt(TrackingCategory::getValue).sum(); + return (sum & category.getValue()) != 0; + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionStore.java new file mode 100644 index 0000000000..c6963a6554 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/tracking/TrackingProtectionStore.java @@ -0,0 +1,269 @@ +package org.mozilla.vrbrowser.browser.tracking; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; + +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.geckoview.ContentBlocking; +import org.mozilla.geckoview.ContentBlockingController.ContentBlockingException; +import org.mozilla.geckoview.GeckoRuntime; +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.VRBrowserActivity; +import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.engine.Session; +import org.mozilla.vrbrowser.browser.tracking.TrackingProtectionPolicy; +import org.mozilla.vrbrowser.db.SitePermission; +import org.mozilla.vrbrowser.ui.viewmodel.SitePermissionViewModel; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class TrackingProtectionStore implements DefaultLifecycleObserver, + SharedPreferences.OnSharedPreferenceChangeListener { + + public interface TrackingProtectionListener { + default void onExcludedTrackingProtectionChange(@NonNull Session session, boolean excluded) {}; + default void onTrackingProtectionStoreUpdated() {}; + default void onTrackingProtectionLevelUpdated(@TrackingProtectionPolicy.ETP_Level int level) {}; + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(mContext.getString(R.string.settings_key_tracking_protection_level))) { + setTrackingProtectionLevel(SettingsStore.getInstance(mContext).getTrackingProtectionLevel()); + } + } + + private Context mContext; + private GeckoRuntime mRuntime; + private Lifecycle mLifeCycle; + private SitePermissionViewModel mViewModel; + private List mListeners; + private SharedPreferences mPrefs; + + public TrackingProtectionStore(@NonNull Context context, + @NonNull GeckoRuntime runtime) { + mContext = context; + mRuntime = runtime; + mListeners = new ArrayList<>(); + + mLifeCycle = ((VRBrowserActivity) context).getLifecycle(); + mLifeCycle.addObserver(this); + + mViewModel = new SitePermissionViewModel(((Application)context.getApplicationContext())); + + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + mPrefs.registerOnSharedPreferenceChangeListener(this); + + setTrackingProtectionLevel(SettingsStore.getInstance(mContext).getTrackingProtectionLevel()); + } + + public void addListener(@NonNull TrackingProtectionListener listener) { + mListeners.add(listener); + } + + public void removeListener(@NonNull TrackingProtectionListener listener) { + mListeners.remove(listener); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + mViewModel.getAll(SitePermission.SITE_PERMISSION_TRACKING).observeForever(sitePermissions -> restore()); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + mViewModel.getAll(SitePermission.SITE_PERMISSION_TRACKING).removeObserver(sitePermissions -> restore()); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + mLifeCycle.removeObserver(this); + mPrefs.unregisterOnSharedPreferenceChangeListener(this); + } + + public void restore() { + List permissionList = mViewModel.getAll().getValue(); + final List exceptionsList = new ArrayList<>(); + if (permissionList != null && !permissionList.isEmpty()) { + permissionList.forEach(sitePermission -> { + ContentBlockingException exception = toContentBlockingException(sitePermission); + if (exception != null) { + exceptionsList.add(exception); + } + }); + mRuntime.getContentBlockingController().restoreExceptionList(exceptionsList); + + } else { + mRuntime.getContentBlockingController().clearExceptionList(); + } + + mListeners.forEach(TrackingProtectionListener::onTrackingProtectionStoreUpdated); + } + + public void contains(@NonNull Session session, Function onResult) { + mRuntime.getContentBlockingController().checkException(session.getGeckoSession()).accept(aBoolean -> { + if (aBoolean != null) { + onResult.apply(aBoolean); + + } else { + onResult.apply(false); + } + }); + } + + public void fetchAll(Function, Void> onResult) { + final List list = new ArrayList<>(); + mRuntime.getContentBlockingController().saveExceptionList().accept(contentBlockingExceptions -> { + if (contentBlockingExceptions != null) { + contentBlockingExceptions.forEach(exception -> { + SitePermission site = toSitePermission(exception); + if (site != null) { + list.add(site); + } + }); + } + onResult.apply(list); + }); + } + + public void add(@NonNull Session session) { + mRuntime.getContentBlockingController().addException(session.getGeckoSession()); + mListeners.forEach(listener -> listener.onExcludedTrackingProtectionChange(session, true)); + persist(); + } + + public void remove(@NonNull Session session) { + mRuntime.getContentBlockingController().removeException(session.getGeckoSession()); + mListeners.forEach(listener -> listener.onExcludedTrackingProtectionChange(session,false)); + persist(); + } + + public void remove(@NonNull SitePermission permission) { + ContentBlockingException exception = toContentBlockingException(permission); + if (exception != null) { + mRuntime.getContentBlockingController().removeException(exception); + persist(); + } + } + + public void removeAll(@NonNull List activeSessions) { + mRuntime.getContentBlockingController().clearExceptionList(); + activeSessions.forEach(session -> + mListeners.forEach(listener -> + listener.onExcludedTrackingProtectionChange(session, false))); + persist(); + } + + private void persist() { + mViewModel.deleteAll(SitePermission.SITE_PERMISSION_TRACKING); + mRuntime.getContentBlockingController().saveExceptionList().accept(contentBlockingExceptions -> { + if (contentBlockingExceptions != null && !contentBlockingExceptions.isEmpty()) { + contentBlockingExceptions.forEach(exception -> { + SitePermission site = toSitePermission(exception); + if (site != null) { + mViewModel.insertSite(site); + } + }); + } + }); + + } + + @Nullable + private static SitePermission toSitePermission(@NonNull ContentBlockingException exception) { + try { + JSONObject json = exception.toJson(); + return new SitePermission( + json.getString("uri"), + json.getString("principal"), + false, + SitePermission.SITE_PERMISSION_TRACKING); + + } catch (JSONException e) { + e.printStackTrace(); + } + + return null; + } + + @Nullable + private static ContentBlockingException toContentBlockingException(@NonNull SitePermission permission) { + try { + JSONObject json = new JSONObject(); + json.put("uri", permission.url); + json.put("principal", permission.principal); + + return ContentBlockingException.fromJson(json); + + } catch (JSONException e) { + e.printStackTrace(); + } + + return null; + } + + private void setTrackingProtectionLevel(@TrackingProtectionPolicy.ETP_Level int level) { + ContentBlocking.Settings settings = mRuntime.getSettings().getContentBlocking(); + TrackingProtectionPolicy policy = TrackingProtectionPolicy.recommended(); + if (mRuntime != null) { + switch (level) { + case TrackingProtectionPolicy.ETP_NONE: + policy = TrackingProtectionPolicy.none(); + break; + case TrackingProtectionPolicy.ETP_DEFAULT: + policy = TrackingProtectionPolicy.recommended(); + break; + case TrackingProtectionPolicy.ETP_STRICT: + policy = TrackingProtectionPolicy.strict(); + break; + } + + boolean activateStrictSocialTracking = + policy.getStrictSocialTrackingProtection() != null && + policy.getTrackingCategory().contains(TrackingProtectionPolicy.TrackingCategory.STRICT); + + @TrackingProtectionPolicy.ETP_Level int etpLevel; + if (policy.getTrackingCategory().contains(TrackingProtectionPolicy.TrackingCategory.NONE)) { + etpLevel = TrackingProtectionPolicy.ETP_NONE; + } else { + etpLevel = TrackingProtectionPolicy.ETP_STRICT; + } + + settings.setEnhancedTrackingProtectionLevel(etpLevel); + settings.setStrictSocialTrackingProtection(activateStrictSocialTracking); + settings.setAntiTracking(policy.getAntiTrackingPolicy()); + settings.setCookieBehavior(policy.getCookiePolicy().getValue()); + + mListeners.forEach(listener -> { + listener.onTrackingProtectionLevelUpdated(etpLevel); + }); + } + } + + public static TrackingProtectionPolicy getTrackingProtectionPolicy(Context mContext) { + @TrackingProtectionPolicy.ETP_Level int level = SettingsStore.getInstance(mContext).getTrackingProtectionLevel(); + switch (level) { + case TrackingProtectionPolicy.ETP_NONE: + return TrackingProtectionPolicy.none(); + case TrackingProtectionPolicy.ETP_DEFAULT: + return TrackingProtectionPolicy.recommended(); + case TrackingProtectionPolicy.ETP_STRICT: + return TrackingProtectionPolicy.strict(); + } + + return TrackingProtectionPolicy.recommended(); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/db/AppDatabase.java b/app/src/common/shared/org/mozilla/vrbrowser/db/AppDatabase.java index 693674b19b..d7a071129b 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/db/AppDatabase.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/db/AppDatabase.java @@ -13,7 +13,7 @@ import org.mozilla.vrbrowser.AppExecutors; -@Database(entities = {SitePermission.class}, version = 2) +@Database(entities = {SitePermission.class}, version = 3) public abstract class AppDatabase extends RoomDatabase { private static final String DATABASE_NAME = "app"; @@ -87,4 +87,20 @@ public void migrate(SupportSQLiteDatabase database) { } }; + private static final Migration MIGRATION_1_3 = new Migration(1, 3) { + @Override + public void migrate(SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE PopUpSite RENAME TO SitePermission"); + database.execSQL("ALTER TABLE SitePermission ADD COLUMN category INTEGER NOT NULL DEFAULT 0"); + database.execSQL("ALTER TABLE SitePermission ADD COLUMN principal STRING NOT NULL DEFAULT ''"); + } + }; + + private static final Migration MIGRATION_2_3 = new Migration(2, 3) { + @Override + public void migrate(SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE SitePermission ADD COLUMN principal STRING NOT NULL DEFAULT ''"); + } + }; + } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/db/SitePermission.java b/app/src/common/shared/org/mozilla/vrbrowser/db/SitePermission.java index 537206af43..3da5f36571 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/db/SitePermission.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/db/SitePermission.java @@ -2,19 +2,22 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey; @Entity public class SitePermission { - @IntDef(value = { SITE_PERMISSION_POPUP, SITE_PERMISSION_WEBXR}) + @IntDef(value = { SITE_PERMISSION_POPUP, SITE_PERMISSION_WEBXR, SITE_PERMISSION_TRACKING}) public @interface Category {} public static final int SITE_PERMISSION_POPUP = 0; public static final int SITE_PERMISSION_WEBXR = 1; + public static final int SITE_PERMISSION_TRACKING = 2; - public SitePermission(@NonNull String url, boolean allowed, @Category int category) { + public SitePermission(@NonNull String url, @NonNull String principal, boolean allowed, @Category int category) { this.url = url; + this.principal = principal; this.allowed = allowed; this.category = category; } @@ -25,6 +28,10 @@ public SitePermission(@NonNull String url, boolean allowed, @Category int catego @NonNull public String url; + @NonNull + @ColumnInfo(name = "principal") + public String principal; + @ColumnInfo(name = "allowed") public boolean allowed; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java index cb3a3c96e5..a40682f0a2 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/viewmodel/WindowViewModel.java @@ -67,6 +67,7 @@ public class WindowViewModel extends AndroidViewModel { private MediatorLiveData navigationBarUrl; private MutableLiveData isWebXRUsed; private MutableLiveData isWebXRBlocked; + private MutableLiveData isTrackingEnabled; public WindowViewModel(Application application) { super(application); @@ -158,6 +159,8 @@ public WindowViewModel(Application application) { isWebXRUsed = new MutableLiveData<>(new ObservableBoolean(false)); isWebXRBlocked = new MutableLiveData<>(new ObservableBoolean(false)); + + isTrackingEnabled = new MutableLiveData<>(new ObservableBoolean(true)); } private Observer mIsTopBarVisibleObserver = new Observer() { @@ -310,6 +313,7 @@ public void refresh() { isMediaPlaying.postValue(isMediaPlaying.getValue()); isWebXRUsed.postValue(isWebXRUsed.getValue()); isWebXRBlocked.postValue(isWebXRBlocked.getValue()); + isTrackingEnabled.postValue(isTrackingEnabled.getValue()); } @NonNull @@ -663,4 +667,13 @@ public MutableLiveData getIsPopUpAvailable() { public void setIsPopUpAvailable(boolean isPopUpAvailable) { this.isPopUpAvailable.postValue(new ObservableBoolean(isPopUpAvailable)); } + + @NonNull + public MutableLiveData getIsTrackingEnabled() { + return isTrackingEnabled; + } + + public void setIsTrackingEnabled(boolean isTrackingEnabled) { + this.isTrackingEnabled.postValue(new ObservableBoolean(isTrackingEnabled)); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/LinkTextView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/LinkTextView.java new file mode 100644 index 0000000000..3776df1f74 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/LinkTextView.java @@ -0,0 +1,32 @@ +package org.mozilla.vrbrowser.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.mozilla.vrbrowser.utils.ViewUtils; + +public class LinkTextView extends TextView { + public LinkTextView(Context context) { + super(context); + } + + public LinkTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public LinkTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public LinkTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setLinkClickListener(@NonNull ViewUtils.LinkClickableSpan listener) { + ViewUtils.setTextViewHTML(this, getText().toString(), listener::onClick); + } +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java index 60ebcc59b4..a635322f51 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java @@ -49,8 +49,6 @@ import org.mozilla.vrbrowser.utils.UrlUtils; import org.mozilla.vrbrowser.utils.ViewUtils; -import java.net.URI; -import java.net.URL; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.Executor; @@ -78,17 +76,15 @@ public class NavigationURLBar extends FrameLayout { private int lastTouchDownOffset = 0; private Unit domainAutocompleteFilter(String text) { - if (mBinding.urlEditText != null) { - DomainAutocompleteResult result = mAutocompleteProvider.getAutocompleteSuggestion(text); - if (result != null) { - mBinding.urlEditText.applyAutocompleteResult(new InlineAutocompleteEditText.AutocompleteResult( - result.getText(), - result.getSource(), - result.getTotalItems(), - null)); - } else { - mBinding.urlEditText.noAutocompleteResult(); - } + DomainAutocompleteResult result = mAutocompleteProvider.getAutocompleteSuggestion(text); + if (result != null) { + mBinding.urlEditText.applyAutocompleteResult(new InlineAutocompleteEditText.AutocompleteResult( + result.getText(), + result.getSource(), + result.getTotalItems(), + null)); + } else { + mBinding.urlEditText.noAutocompleteResult(); } return Unit.INSTANCE; } @@ -100,6 +96,7 @@ public interface NavigationURLBarDelegate { void onURLSelectionAction(EditText aURLEdit, float centerX, SelectionActionWidget actionMenu); void onPopUpButtonClicked(); void onWebXRButtonClicked(); + void onTrackingButtonClicked(); } public NavigationURLBar(Context context, AttributeSet attrs) { @@ -229,6 +226,7 @@ private void initialize(Context aContext) { mBinding.popup.setOnClickListener(mPopUpListener); mBinding.webxr.setOnClickListener(mWebXRButtonClick); + mBinding.tracking.setOnClickListener(mTrackingButtonClick); // Bookmarks mBinding.bookmarkButton.setOnClickListener(v -> handleBookmarkClick()); @@ -263,6 +261,7 @@ public void setSession(Session session) { public void onPause() { if (mViewModel.getIsLoading().getValue().get()) { mBinding.loadingView.clearAnimation(); + } } @@ -335,6 +334,10 @@ public UIButton getWebxRButton() { return mBinding.webxr; } + public UIButton getTrackingRButton() { + return mBinding.tracking; + } + public void handleURLEdit(String text) { text = text.trim(); @@ -410,6 +413,16 @@ public void setClickable(boolean clickable) { } }; + private OnClickListener mTrackingButtonClick = view -> { + if (mAudio != null) { + mAudio.playSound(AudioEngine.Sound.CLICK); + } + + if (mDelegate != null) { + mDelegate.onTrackingButtonClicked(); + } + }; + private TextWatcher mURLTextWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { @@ -560,5 +573,4 @@ private void hideSelectionMenu() { mSelectionMenu = null; } } - } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/ButtonSetting.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/ButtonSetting.java index 29bbeead37..3759ce1e60 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/ButtonSetting.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/ButtonSetting.java @@ -9,10 +9,12 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.StringRes; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; +import org.mozilla.vrbrowser.utils.ViewUtils; public class ButtonSetting extends LinearLayout { @@ -101,7 +103,7 @@ public void setDescription(Spanned description) { } public String getDescription() { - return mDescription; + return mDescriptionView.getText().toString(); } public void setFooterButtonVisibility(int visibility) { @@ -121,4 +123,8 @@ public void setHovered(boolean hovered) { mButton.setHovered(hovered); } + + public void setLinkClickListener(@NonNull ViewUtils.LinkClickableSpan listener) { + ViewUtils.setTextViewHTML(mDescriptionView, mDescriptionView.getText().toString(), listener::onClick); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java index 8bfc2c7a45..64cf4fe31e 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/settings/RadioGroupSetting.java @@ -4,6 +4,10 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.text.InputType; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; import android.util.AttributeSet; import android.util.TypedValue; import android.view.ContextThemeWrapper; @@ -12,13 +16,13 @@ import android.widget.RadioGroup; import android.widget.TextView; -import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.audio.AudioEngine; - import androidx.annotation.IdRes; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.audio.AudioEngine; + public class RadioGroupSetting extends LinearLayout { public interface OnCheckedChangeListener { @@ -28,6 +32,7 @@ public interface OnCheckedChangeListener { private AudioEngine mAudio; protected String mDescription; private CharSequence[] mOptions; + private CharSequence[] mDescriptions; private Object[] mValues; protected RadioGroup mRadioGroup; protected TextView mRadioDescription; @@ -45,6 +50,7 @@ public RadioGroupSetting(Context context, AttributeSet attrs, int defStyleAttr) mLayout = attributes.getResourceId(R.styleable.RadioGroupSetting_layout, R.layout.setting_radio_group); mDescription = attributes.getString(R.styleable.RadioGroupSetting_description); mOptions = attributes.getTextArray(R.styleable.RadioGroupSetting_options); + mDescriptions = attributes.getTextArray(R.styleable.RadioGroupSetting_descriptions); int id = attributes.getResourceId(R.styleable.RadioGroupSetting_values, 0); try { TypedArray array = context.getResources().obtainTypedArray(id); @@ -83,11 +89,25 @@ protected void initialize(Context aContext, AttributeSet attrs, int defStyleAttr if (mOptions != null) { for (int i = 0; i < mOptions.length; i++) { + SpannableString spannable = new SpannableString(mOptions[i]); + if (mDescriptions != null && mDescriptions[i] != null) { + spannable = new SpannableString(mOptions[i] + + System.getProperty ("line.separator") + + mDescriptions[i]); + int start = (mOptions[i].toString() + System.getProperty ("line.separator")).length(); + int end = spannable.length(); + spannable.setSpan(new RelativeSizeSpan(0.8f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new ForegroundColorSpan( + getResources().getColor(R.color.rhino, getContext().getTheme())), + start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + RadioButton button = new RadioButton(new ContextThemeWrapper(getContext(), R.style.radioButtonTheme), null, 0); button.setInputType(InputType.TYPE_NULL); button.setClickable(true); button.setId(i); - button.setText(mOptions[i]); + button.setText(spannable); + button.setSingleLine(false); mRadioGroup.addView(button); } } @@ -154,11 +174,25 @@ public void setOptions(@NonNull String[] options) { mRadioGroup.removeAllViews(); for (int i=0; i listener.onClick(widget, url)); + ViewUtils.setTextViewHTML(mSwitchDescription, mText, listener::onClick); } public void setValue(boolean value, boolean doApply) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java index ec4ca96139..01c8863339 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java @@ -11,12 +11,12 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.preference.PreferenceManager; +import android.text.Spannable; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; -import android.webkit.URLUtil; import android.widget.EditText; import androidx.annotation.NonNull; @@ -37,8 +37,8 @@ import org.mozilla.vrbrowser.browser.SessionChangeListener; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.engine.Session; -import org.mozilla.vrbrowser.browser.engine.SessionState; import org.mozilla.vrbrowser.browser.engine.SessionStore; +import org.mozilla.vrbrowser.browser.tracking.TrackingProtectionStore; import org.mozilla.vrbrowser.databinding.NavigationBarBinding; import org.mozilla.vrbrowser.db.SitePermission; import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; @@ -64,6 +64,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; +import static org.mozilla.vrbrowser.db.SitePermission.SITE_PERMISSION_TRACKING; import static org.mozilla.vrbrowser.ui.widgets.menus.VideoProjectionMenuWidget.VIDEO_PROJECTION_NONE; public class NavigationBarWidget extends UIWidget implements GeckoSession.NavigationDelegate, @@ -111,6 +112,7 @@ public interface NavigationListener { private int mBlockedCount; private Executor mUIThreadExecutor; private ArrayList mNavigationListeners; + private TrackingProtectionStore mTrackingDelegate; public NavigationBarWidget(Context aContext) { super(aContext); @@ -160,6 +162,8 @@ private void initialize(@NonNull Context aContext) { mSuggestionsProvider = new SuggestionsProvider(getContext()); + mTrackingDelegate = SessionStore.get().getTrackingProtectionStore(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(mAppContext); mPrefs.registerOnSharedPreferenceChangeListener(this); } @@ -364,6 +368,23 @@ private void updateUI() { } } + TrackingProtectionStore.TrackingProtectionListener mTrackingListener = new TrackingProtectionStore.TrackingProtectionListener() { + @Override + public void onExcludedTrackingProtectionChange(@NonNull Session session, boolean excluded) { + if (session == getSession()) { + session.reload(GeckoSession.LOAD_FLAGS_NONE); + mViewModel.setIsTrackingEnabled(!excluded); + } + } + + @Override + public void onTrackingProtectionStoreUpdated() { + if (getSession() != null && mAttachedWindow.isFirstPaintReady()) { + getSession().reload(GeckoSession.LOAD_FLAGS_NONE); + } + } + }; + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -460,8 +481,11 @@ public void detachFromWindow() { mBinding.navigationBarNavigation.urlBar.detachFromWindow(); + mTrackingDelegate.removeListener(mTrackingListener); + if (mViewModel != null) { mViewModel.getIsFullscreen().removeObserver(mIsFullscreenObserver); + mViewModel.getUrl().removeObserver( mUrlObserver); mViewModel = null; } } @@ -483,9 +507,12 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mBinding.setViewmodel(mViewModel); - mViewModel.getIsFullscreen().observe((VRBrowserActivity)getContext(), mIsFullscreenObserver); + mViewModel.getIsFullscreen().observeForever( mIsFullscreenObserver); + mViewModel.getUrl().observeForever(mUrlObserver); mBinding.navigationBarNavigation.urlBar.attachToWindow(mAttachedWindow); + mTrackingDelegate.addListener(mTrackingListener); + mAttachedWindow.addWindowListener(this); mAttachedWindow.setPopUpDelegate(mPopUpDelegate); @@ -806,6 +833,21 @@ public void onFullScreen(@NonNull GeckoSession session, boolean aFullScreen) { } }; + private Observer mUrlObserver = new Observer() { + @Override + public void onChanged(Spannable sitePermissions) { + if (getSession() != null) { + mTrackingDelegate.contains(getSession(), isExcluded -> { + if (isExcluded != null) { + mViewModel.setIsTrackingEnabled(!isExcluded); + } + + return null; + }); + } + } + }; + // WidgetManagerDelegate.UpdateListener @Override public void onWidgetUpdate(Widget aWidget) { @@ -923,6 +965,13 @@ public void onWebXRButtonClicked() { mViewModel.getIsWebXRBlocked().getValue().get()); } + @Override + public void onTrackingButtonClicked() { + showQuickPermission(mBinding.navigationBarNavigation.urlBar.getTrackingRButton(), + SITE_PERMISSION_TRACKING, + mViewModel.getIsTrackingEnabled().getValue().get()); + } + // VoiceSearch Delegate @Override @@ -1173,14 +1222,28 @@ private void showQuickPermission(UIButton target, @SitePermission.Category int a mQuickPermissionWidget.setDelegate(new QuickPermissionWidget.Delegate() { @Override public void onBlock() { - SessionStore.get().setPermissionAllowed(uri, aCategory, false); + if (aCategory == SITE_PERMISSION_TRACKING) { + if (getSession() != null) { + mTrackingDelegate.remove(getSession()); + } + + } else { + SessionStore.get().setPermissionAllowed(uri, aCategory, false); + } mQuickPermissionWidget.onDismiss(); mAttachedWindow.getSession().reload(); } @Override public void onAllow() { - SessionStore.get().setPermissionAllowed(uri, aCategory, true); + if (aCategory == SITE_PERMISSION_TRACKING) { + if (getSession() != null) { + mTrackingDelegate.add(getSession()); + } + + } else { + SessionStore.get().setPermissionAllowed(uri, aCategory, true); + } mQuickPermissionWidget.onDismiss(); mAttachedWindow.getSession().reload(); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/QuickPermissionWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/QuickPermissionWidget.java index 7f79d119de..25e48ae011 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/QuickPermissionWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/QuickPermissionWidget.java @@ -4,8 +4,11 @@ import android.content.res.Configuration; import android.view.LayoutInflater; import android.view.View; + +import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.QuickPermissionDialogBinding; import org.mozilla.vrbrowser.db.SitePermission; import org.mozilla.vrbrowser.ui.widgets.UIWidget; @@ -59,8 +62,24 @@ public void updateUI() { mBinding.message.setText(getResources().getString(R.string.webxr_block_dialog_message, mDomain)); break; } + case SitePermission.SITE_PERMISSION_TRACKING: { + mBinding.message.setText( + getResources().getString(R.string.tracking_dialog_message, + mBinding.getBlocked() ? + getResources().getString(R.string.on).toUpperCase() : + getResources().getString(R.string.off).toUpperCase(), + getResources().getString(R.string.sumo_etp_url))); + mBinding.allowButton.setText(R.string.tracking_dialog_button_disable); + mBinding.blockButton.setText(R.string.tracking_dialog_button_enable); + break; + } } + mBinding.message.setLinkClickListener((widget, url) -> { + mWidgetManager.openNewTabForeground(url); + onDismiss(); + }); + mBinding.executePendingBindings(); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java index 91f25d80f5..78c74c5eab 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java @@ -25,6 +25,7 @@ import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.OptionsPrivacyBinding; import org.mozilla.vrbrowser.db.SitePermission; +import org.mozilla.vrbrowser.ui.views.settings.RadioGroupSetting; import org.mozilla.vrbrowser.ui.views.settings.SwitchSetting; import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; @@ -108,14 +109,11 @@ protected void updateUI() { mBinding.drmContentPlaybackSwitch.setOnCheckedChangeListener(mDrmContentListener); mBinding.drmContentPlaybackSwitch.setLinkClickListener((widget, url) -> { - SessionStore.get().getActiveSession().loadUri(url); + mWidgetManager.openNewTabForeground(url); exitWholeSettings(); }); setDrmContent(SettingsStore.getInstance(getContext()).isDrmContentPlaybackEnabled(), false); - mBinding.trackingProtectionSwitch.setOnCheckedChangeListener(mTrackingProtectionListener); - setTrackingProtection(SettingsStore.getInstance(getContext()).isTrackingProtectionEnabled(), false); - mBinding.notificationsPermissionSwitch.setOnCheckedChangeListener(mNotificationsListener); setNotifications(SettingsStore.getInstance(getContext()).isNotificationsEnabled(), false); @@ -139,6 +137,16 @@ protected void updateUI() { mBinding.webxrSwitch.setOnCheckedChangeListener(mWebXRListener); setWebXR(SettingsStore.getInstance(getContext()).isWebXREnabled(), false); mBinding.webxrExceptionsButton.setOnClickListener(v -> mDelegate.showView(SettingViewType.WEBXR_EXCEPTIONS)); + + mBinding.trackingProtectionButton.setOnClickListener(v -> mDelegate.showView(SettingViewType.TRACKING_EXCEPTION)); + mBinding.trackingProtectionButton.setDescription(getResources().getString(R.string.privacy_options_tracking, getResources().getString(R.string.sumo_etp_url))); + mBinding.trackingProtectionButton.setLinkClickListener((widget, url) -> { + mWidgetManager.openNewTabForeground(url); + exitWholeSettings(); + }); + int etpLevel = SettingsStore.getInstance(getContext()).getTrackingProtectionLevel(); + mBinding.trackingProtectionRadio.setOnCheckedChangeListener(mTrackingProtectionListener); + setTrackingProtection(mBinding.trackingProtectionRadio.getIdForValue(etpLevel), false); } private void togglePermission(SwitchSetting aButton, String aPermission) { @@ -164,8 +172,8 @@ public void reject() { setDrmContent(value, doApply); }; - private SwitchSetting.OnCheckedChangeListener mTrackingProtectionListener = (compoundButton, value, doApply) -> { - setTrackingProtection(value, doApply); + private RadioGroupSetting.OnCheckedChangeListener mTrackingProtectionListener = (radioGroup, checkedId, doApply) -> { + setTrackingProtection(checkedId, true); }; private SwitchSetting.OnCheckedChangeListener mNotificationsListener = (compoundButton, value, doApply) -> { @@ -201,8 +209,8 @@ private void resetOptions() { setDrmContent(SettingsStore.DRM_PLAYBACK_DEFAULT, true); } - if (mBinding.trackingProtectionSwitch.isChecked() != SettingsStore.TRACKING_DEFAULT) { - setTrackingProtection(SettingsStore.TRACKING_DEFAULT, true); + if (!mBinding.trackingProtectionRadio.getValueForId(mBinding.trackingProtectionRadio.getCheckedRadioButtonId()).equals(SettingsStore.MSAA_DEFAULT_LEVEL)) { + setTrackingProtection(mBinding.trackingProtectionRadio.getIdForValue(SettingsStore.TRACKING_DEFAULT), true); } if (mBinding.notificationsPermissionSwitch.isChecked() != SettingsStore.NOTIFICATIONS_DEFAULT) { @@ -245,14 +253,13 @@ private void setDrmContent(boolean value, boolean doApply) { } } - private void setTrackingProtection(boolean value, boolean doApply) { - mBinding.trackingProtectionSwitch.setOnCheckedChangeListener(null); - mBinding.trackingProtectionSwitch.setValue(value, false); - mBinding.trackingProtectionSwitch.setOnCheckedChangeListener(mTrackingProtectionListener); + private void setTrackingProtection(int checkedId, boolean doApply) { + mBinding.trackingProtectionRadio.setOnCheckedChangeListener(null); + mBinding.trackingProtectionRadio.setChecked(checkedId, false); + mBinding.trackingProtectionRadio.setOnCheckedChangeListener(mTrackingProtectionListener); if (doApply) { - SettingsStore.getInstance(getContext()).setTrackingProtectionEnabled(value); - SessionStore.get().setTrackingProtection(value); + SettingsStore.getInstance(getContext()).setTrackingProtectionLevel((Integer)mBinding.trackingProtectionRadio.getValueForId(checkedId)); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java index 77366583cc..1071c7f8e6 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsView.java @@ -28,7 +28,8 @@ public enum SettingViewType { DEVELOPER, FXA, ENVIRONMENT, - CONTROLLER + CONTROLLER, + TRACKING_EXCEPTION } protected Delegate mDelegate; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java index 1793614bac..9f86401938 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java @@ -436,6 +436,9 @@ public void showView(SettingsView.SettingViewType aType) { case CONTROLLER: showView(new ControllerOptionsView(getContext(), mWidgetManager)); break; + case TRACKING_EXCEPTION: + showView(new SitePermissionsOptionsView(getContext(), mWidgetManager, SitePermission.SITE_PERMISSION_TRACKING)); + break; } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SitePermissionsOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SitePermissionsOptionsView.java index 58d02d2eb0..4f1bcface3 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SitePermissionsOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SitePermissionsOptionsView.java @@ -84,6 +84,14 @@ protected void updateUI() { mBinding.emptyText.setText(R.string.settings_privacy_policy_webxr_empty_description); mBinding.footerLayout.setDescription(R.string.settings_privacy_policy_webxr_reset); break; + case SitePermission.SITE_PERMISSION_TRACKING: + mBinding.headerLayout.setTitle(R.string.settings_privacy_policy_tracking_title); + mBinding.headerLayout.setDescription(R.string.settings_privacy_policy_tracking_description); + mBinding.contentText.setText(R.string.settings_privacy_policy_tracking_description); + mBinding.emptyText.setText(R.string.settings_privacy_policy_tracking_empty_description); + mBinding.emptySecondText.setVisibility(GONE); + mBinding.footerLayout.setDescription(R.string.settings_privacy_policy_tracking_reset); + break; } mBinding.executePendingBindings(); diff --git a/app/src/main/res/drawable/ic_icon_tracking_disabled.xml b/app/src/main/res/drawable/ic_icon_tracking_disabled.xml new file mode 100644 index 0000000000..ba61dbe79a --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_tracking_disabled.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_icon_tracking_enabled.xml b/app/src/main/res/drawable/ic_icon_tracking_enabled.xml new file mode 100644 index 0000000000..291766dabd --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_tracking_enabled.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/navigation_url.xml b/app/src/main/res/layout/navigation_url.xml index 7f0457e5b6..094ada2d4b 100644 --- a/app/src/main/res/layout/navigation_url.xml +++ b/app/src/main/res/layout/navigation_url.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools"> + - - - - - - - - - - - + visibleGone="@{!UrlUtils.isHomeUri(context, viewmodel.url.toString())}" + android:tooltipText="@{viewmodel.isTrackingEnabled ? @string/tracking_allowed_tooltip : @string/tracking_disabled_tooltip}" /> - - + android:orientation="horizontal" + app:visibleGone="@{viewmodel.isPopUpAvailable}"> + + + + + + + + - - - + - + android:layout_toEndOf="@id/buttonsLayout" + app:visibleGone="@{viewmodel.isLibraryVisible || UrlUtils.isHomeUri(context, viewmodel.url.toString())}"/> + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/options_exceptions.xml b/app/src/main/res/layout/options_exceptions.xml index 585205dded..62f0e95463 100644 --- a/app/src/main/res/layout/options_exceptions.xml +++ b/app/src/main/res/layout/options_exceptions.xml @@ -50,7 +50,7 @@ android:id="@+id/empty_text" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:gravity="center_horizontal" android:textAlignment="center" android:paddingBottom="15dp" android:textColor="@color/rhino" @@ -59,9 +59,10 @@ tools:text="@string/privacy_options_popups_list_empty_first" /> + + + + + + + + + + + + - - - - - - - - - - - + diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 05c2b86917..5f9168db82 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -236,8 +236,8 @@ 10dp - 350dp - 200dp + 420dp + 220dp 300dp diff --git a/app/src/main/res/values/non_L10n.xml b/app/src/main/res/values/non_L10n.xml index db57bf1553..58c47b5062 100644 --- a/app/src/main/res/values/non_L10n.xml +++ b/app/src/main/res/values/non_L10n.xml @@ -14,7 +14,7 @@ settings_performance_monitor settings_environment_servo settings_key_drm_playback - settings_tracking_protection + settings_tracking_protection_level settings_key_speech_data_collection settings_key_speech_data_collection_accept settings_user_agent_version_v2 @@ -59,6 +59,7 @@ https://support.mozilla.org/kb/using-voice-search-firefox-reality https://support.mozilla.org/kb/use-firefox-another-language?as=u&utm_source=inproduct https://support.mozilla.org/kb/choose-display-languages-multilingual-web-pages?as=u&utm_source=inproduct + https://support.mozilla.org/kb/enhanced-tracking-protection-firefox-desktop view position view_id diff --git a/app/src/main/res/values/options_values.xml b/app/src/main/res/values/options_values.xml index 5995a04d26..1813c0e293 100644 --- a/app/src/main/res/values/options_values.xml +++ b/app/src/main/res/values/options_values.xml @@ -73,6 +73,25 @@ 2 + + + @string/privacy_options_tracking_etp + @string/privacy_options_tracking_strict + @string/privacy_options_tracking_off + + + + @string/privacy_options_tracking_etp_description + @string/privacy_options_tracking_strict_description + @string/privacy_options_tracking_off_description + + + + 1 + 2 + 0 + + @string/history_clear_range_today diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff281570ff..08e92a44a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -123,6 +123,19 @@ Reset the sites not allowed to access Virtual Reality devices. + + Exceptions for Enhanced Tracking Protection + + + You’ve turned off protections on these websites. + + + When you allow a website to track your activity, they will appear here. + + + Reset the Exceptions for Enhanced Tracking Protection. + Allow @@ -435,6 +448,24 @@ VR + + Enhanced Tracking Protection (ETP) + + + Balanced for protection and performance. Pages will load normally. + + + Strict + + + Stronger protection, but may cause some sites or content to break. + + + Off + + + Allow every page to follow you around online to collect you browsing habits and interests. + A list of sites with permissions will appear here. + + Tracking Protection. (<a href="%1$s">Learn More</a>) + + + Exceptions + Reset Controller Settings @@ -1090,6 +1128,14 @@ browser's navigation bar. The button it labels, when pressed, Shows the WebXR permission dialog. --> WebXR Blocked + + Tracking Protection Enabled + + + Tracking Protection Blocked + Enter Private Browsing @@ -1408,4 +1454,13 @@ the Select` button. When clicked it closes all the previously selected tabs --> ‘%1$s’ wants to access WebXR API + + + Enhanced Tracking Protection is %1$s for this site. (<a href="%2$s">Learn More</a>) + + + Enable + + + Disable