diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index 6493051295..0e435536c0 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -486,7 +486,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 d0ee6f5a3a..0a13248a05 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/PermissionDelegate.java @@ -287,7 +287,7 @@ public void setPermissionAllowed(String uri, @SitePermission.Category int catego mSitePermissionModel.deleteSite(site); } else { if (site == null) { - site = new SitePermission(uri, false, category); + site = new SitePermission(uri, uri, false, category); 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 mListeners; + private SharedPreferences mPrefs; + private List mCurrentSitePermissions; + private List mSitePermissions; + + public TrackingProtectionStore(@NonNull Context context, + @NonNull GeckoRuntime runtime) { + mContext = context; + mRuntime = runtime; + mContentBlockingController = mRuntime.getContentBlockingController(); + mListeners = new ArrayList<>(); + mCurrentSitePermissions = new ArrayList<>(); + mSitePermissions = 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(mSitePermissionObserver); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + mViewModel.getAll(SitePermission.SITE_PERMISSION_TRACKING).removeObserver(mSitePermissionObserver); + } + + private Observer> mSitePermissionObserver = this::notifyDiff; + + @Override + public void onInserted(int position, int count) { + if (mSitePermissions == null) { + return; + } + + final List exceptionsList = new ArrayList<>(); + mContentBlockingController.clearExceptionList(); + for (int i=0; i listener.onExcludedTrackingProtectionChange( + UrlUtils.getHost(exception.uri), + true)); + } + } + mContentBlockingController.restoreExceptionList(exceptionsList); + } + + @Override + public void onRemoved(int position, int count) { + if (mCurrentSitePermissions == null) { + return; + } + + for (int i=0; i listener.onExcludedTrackingProtectionChange( + UrlUtils.getHost(exception.uri), + false)); + } + } + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + // We never move from the exceptions list + } + + @Override + public void onChanged(int position, int count, @Nullable Object payload) { + // We never update from the exceptions list + } + + private void notifyDiff(List newList) { + if (newList == null) { + return; + } + + DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mSitePermissions.size(); + } + + @Override + public int getNewListSize() { + return newList.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mSitePermissions.get(oldItemPosition).url.equals(newList.get(newItemPosition).url) && + mSitePermissions.get(oldItemPosition).principal.equals(newList.get(newItemPosition).principal) && + mSitePermissions.get(oldItemPosition).category == newList.get(newItemPosition).category; + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + SitePermission newSite = newList.get(newItemPosition); + SitePermission olSite = mSitePermissions.get(oldItemPosition); + return newSite.url.equals(olSite.url) + && Objects.equals(newSite.category, olSite.category) + && Objects.equals(newSite.principal, olSite.principal) + && newSite.allowed == olSite.allowed; + } + }); + + mCurrentSitePermissions = mSitePermissions; + mSitePermissions = newList; + result.dispatchUpdatesTo(this); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + mLifeCycle.removeObserver(this); + mPrefs.unregisterOnSharedPreferenceChangeListener(this); + } + + public void contains(@NonNull Session session, Function onResult) { + mContentBlockingController.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<>(); + mContentBlockingController.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) { + mContentBlockingController.addException(session.getGeckoSession()); + mListeners.forEach(listener -> listener.onExcludedTrackingProtectionChange( + UrlUtils.getHost(session.getCurrentUri()), + true)); + persist(); + } + + public void remove(@NonNull Session session) { + mContentBlockingController.removeException(session.getGeckoSession()); + mListeners.forEach(listener -> listener.onExcludedTrackingProtectionChange( + UrlUtils.getHost(session.getCurrentUri()), + false)); + persist(); + } + + public void remove(@NonNull SitePermission permission) { + ContentBlockingException exception = toContentBlockingException(permission); + if (exception != null) { + mContentBlockingController.removeException(exception); + persist(); + } + } + + public void removeAll(@NonNull List activeSessions) { + mContentBlockingController.clearExceptionList(); + activeSessions.forEach(session -> + mListeners.forEach(listener -> + listener.onExcludedTrackingProtectionChange( + UrlUtils.getHost(session.getCurrentUri()), + false))); + persist(); + } + + private void persist() { + mViewModel.deleteAll(SitePermission.SITE_PERMISSION_TRACKING); + mRuntime.getContentBlockingController().saveExceptionList().accept(contentBlockingExceptions -> { + if (contentBlockingExceptions != null && !contentBlockingExceptions.isEmpty()) { + contentBlockingExceptions.forEach(exception -> { + Log.d("TrackingProtectionStore", "Excluded site: " + exception.uri); + 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(int level) { + ContentBlocking.Settings settings = mRuntime.getSettings().getContentBlocking(); + TrackingProtectionPolicy policy = TrackingProtectionPolicy.recommended(); + if (mRuntime != null) { + switch (level) { + case ContentBlocking.EtpLevel.NONE: + policy = TrackingProtectionPolicy.none(); + break; + case ContentBlocking.EtpLevel.DEFAULT: + policy = TrackingProtectionPolicy.recommended(); + break; + case ContentBlocking.EtpLevel.STRICT: + policy = TrackingProtectionPolicy.strict(); + break; + } + + settings.setEnhancedTrackingProtectionLevel(level); + settings.setStrictSocialTrackingProtection(policy.shouldBlockContent()); + settings.setAntiTracking(policy.getAntiTrackingPolicy()); + settings.setCookieBehavior(policy.getCookiePolicy()); + + mListeners.forEach(listener -> listener.onTrackingProtectionLevelUpdated(level)); + } + } + + public static TrackingProtectionPolicy getTrackingProtectionPolicy(Context mContext) { + int level = SettingsStore.getInstance(mContext).getTrackingProtectionLevel(); + switch (level) { + case ContentBlocking.EtpLevel.NONE: + return TrackingProtectionPolicy.none(); + case ContentBlocking.EtpLevel.DEFAULT: + return TrackingProtectionPolicy.recommended(); + case ContentBlocking.EtpLevel.STRICT: + return TrackingProtectionPolicy.strict(); + } + + return TrackingProtectionPolicy.recommended(); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/EngineProvider.kt b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/EngineProvider.kt index 42c6824e6e..eb0450a2fa 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/EngineProvider.kt +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/EngineProvider.kt @@ -25,7 +25,11 @@ object EngineProvider { builder.crashHandler(CrashReporterService::class.java) builder.contentBlocking(ContentBlocking.Settings.Builder() - .antiTracking(ContentBlocking.AntiTracking.AD or ContentBlocking.AntiTracking.SOCIAL or ContentBlocking.AntiTracking.ANALYTIC) + .antiTracking( + ContentBlocking.AntiTracking.AD or + ContentBlocking.AntiTracking.SOCIAL or + ContentBlocking.AntiTracking.ANALYTIC) + .enhancedTrackingProtectionLevel(SettingsStore.getInstance(context).trackingProtectionLevel) .build()) builder.consoleOutput(SettingsStore.getInstance(context).isConsoleLogsEnabled) builder.displayDensityOverride(SettingsStore.getInstance(context).displayDensity) @@ -52,8 +56,6 @@ object EngineProvider { val path = "resource://android/assets/web_extensions/$extension/" runtime!!.registerWebExtension(WebExtension(path, runtime!!.webExtensionController)) } - - } return runtime!! diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java index 1309d42880..41f3a2b644 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java @@ -37,6 +37,8 @@ import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.UserAgentOverride; import org.mozilla.vrbrowser.browser.VideoAvailabilityListener; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionPolicy; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionStore; import org.mozilla.vrbrowser.geolocation.GeolocationData; import org.mozilla.vrbrowser.telemetry.GleanMetricsService; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; @@ -337,6 +339,7 @@ private void setupSessionListeners(GeckoSession aSession) { aSession.setMediaDelegate(this); aSession.setHistoryDelegate(this); aSession.setSelectionActionDelegate(this); + aSession.setContentBlockingDelegate(this); } private void cleanSessionListeners(GeckoSession aSession) { @@ -350,6 +353,7 @@ private void cleanSessionListeners(GeckoSession aSession) { aSession.setMediaDelegate(null); aSession.setHistoryDelegate(null); aSession.setSelectionActionDelegate(null); + aSession.setContentBlockingDelegate(null); } public void suspend() { @@ -462,6 +466,10 @@ private GeckoSession createGeckoSession(@NonNull SessionSettings aSettings) { public void recreateSession() { SessionState previous = mState; mState = mState.recreate(); + + TrackingProtectionPolicy policy = TrackingProtectionStore.getTrackingProtectionPolicy(mContext); + mState.mSettings.setTrackingProtectionEnabled(policy.shouldBlockContent()); + restore(); GeckoSession previousGeckoSession = null; @@ -873,16 +881,6 @@ public void setUaMode(int mode) { } } - - protected void setTrackingProtection(final boolean aEnabled) { - if (mState.mSettings.isTrackingProtectionEnabled() != aEnabled) { - mState.mSettings.setTrackingProtectionEnabled(aEnabled); - if (mState.mSession != null) { - mState.mSession.getSettings().setUseTrackingProtection(aEnabled); - } - } - } - public void updateLastUse() { mState.mLastUse = System.currentTimeMillis(); } @@ -1227,19 +1225,38 @@ public void updateCursorAnchorInfo(@NonNull GeckoSession aSession, @NonNull Curs @Override public void onContentBlocked(@NonNull final GeckoSession session, @NonNull final ContentBlocking.BlockEvent event) { if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.AD) != 0) { - Log.i(LOGTAG, "Blocking Ad: " + event.uri); + Log.d(LOGTAG, "Blocking Ad: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.ANALYTIC) != 0) { + Log.d(LOGTAG, "Blocking Analytic: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.CONTENT) != 0) { + Log.d(LOGTAG, "Blocking Content: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.SOCIAL) != 0) { + Log.d(LOGTAG, "Blocking Social: " + event.uri); + } + } + + @Override + public void onContentLoaded(@NonNull GeckoSession geckoSession, @NonNull ContentBlocking.BlockEvent event) { + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.AD) != 0) { + Log.d(LOGTAG, "Loading Ad: " + event.uri); } if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.ANALYTIC) != 0) { - Log.i(LOGTAG, "Blocking Analytic: " + event.uri); + Log.d(LOGTAG, "Loading Analytic: " + event.uri); } if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.CONTENT) != 0) { - Log.i(LOGTAG, "Blocking Content: " + event.uri); + Log.d(LOGTAG, "Loading Content: " + event.uri); } if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.SOCIAL) != 0) { - Log.i(LOGTAG, "Blocking Social: " + event.uri); + Log.d(LOGTAG, "Loading Social: " + event.uri); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionSettings.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionSettings.java index 2100edf5eb..744fcb84d1 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionSettings.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionSettings.java @@ -6,6 +6,8 @@ import org.mozilla.geckoview.GeckoSessionSettings; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionStore; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionPolicy; class SessionSettings { @@ -125,9 +127,10 @@ public Builder withDefaultSettings(Context context) { int viewport = ua == GeckoSessionSettings.USER_AGENT_MODE_DESKTOP ? GeckoSessionSettings.VIEWPORT_MODE_DESKTOP : GeckoSessionSettings.VIEWPORT_MODE_MOBILE; + TrackingProtectionPolicy policy = TrackingProtectionStore.getTrackingProtectionPolicy(context); return new SessionSettings.Builder() .withPrivateBrowsing(false) - .withTrackingProtection(SettingsStore.getInstance(context).isTrackingProtectionEnabled()) + .withTrackingProtection(policy.shouldBlockContent()) .withSuspendMediaWhenInactive(true) .withUserAgent(ua) .withViewport(viewport) diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java index 5d26220c44..4699593be4 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java @@ -16,14 +16,16 @@ import org.mozilla.vrbrowser.browser.HistoryStore; import org.mozilla.vrbrowser.browser.PermissionDelegate; import org.mozilla.vrbrowser.browser.Services; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionStore; import org.mozilla.vrbrowser.db.SitePermission; import org.mozilla.vrbrowser.utils.SystemUtils; +import org.mozilla.vrbrowser.utils.UrlUtils; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -public class SessionStore implements GeckoSession.PermissionDelegate { +public class SessionStore implements GeckoSession.PermissionDelegate{ private static final String LOGTAG = SystemUtils.createLogtag(SessionStore.class); private static final int MAX_GECKO_SESSIONS = 5; @@ -45,6 +47,7 @@ public static SessionStore get() { private HistoryStore mHistoryStore; private Services mServices; private boolean mSuspendPending; + private TrackingProtectionStore mTrackingProtectionStore; private SessionStore() { mSessions = new ArrayList<>(); @@ -57,6 +60,24 @@ 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 onExcludedTrackingProtectionChange(@NonNull String host, boolean excluded) { + mSessions.forEach(existingSession -> { + String existingHost = UrlUtils.getHost(existingSession.getCurrentUri()); + if (existingHost.equals(host)) { + existingSession.recreateSession(); + } + }); + } + + @Override + public void onTrackingProtectionLevelUpdated(int level) { + mSessions.forEach(Session::recreateSession); + } + }); } public void initializeServices() { @@ -210,6 +231,10 @@ public HistoryStore getHistoryStore() { return mHistoryStore; } + public TrackingProtectionStore getTrackingProtectionStore() { + return mTrackingProtectionStore; + } + public void purgeSessionHistory() { for (Session session: mSessions) { session.purgeHistory(); @@ -246,18 +271,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/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/DataRepository.java b/app/src/common/shared/org/mozilla/vrbrowser/db/DataRepository.java index 535867ab09..d2dae61a25 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/db/DataRepository.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/db/DataRepository.java @@ -60,9 +60,7 @@ public LiveData> getSitePermissions() { public CompletableFuture getSitePermission(String aURL, @SitePermission.Category int category) { CompletableFuture future = new CompletableFuture<>(); - mExecutors.diskIO().execute(() -> { - mDatabase.sitePermissionDao().findByUrl(aURL, category); - }); + mExecutors.diskIO().execute(() -> mDatabase.sitePermissionDao().findByUrl(aURL, category)); return future; } 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 49c048ca1f..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,7 +67,7 @@ public class WindowViewModel extends AndroidViewModel { private MediatorLiveData navigationBarUrl; private MutableLiveData isWebXRUsed; private MutableLiveData isWebXRBlocked; - private MediatorLiveData isContentFeed; + private MutableLiveData isTrackingEnabled; public WindowViewModel(Application application) { super(application); @@ -160,8 +160,7 @@ public WindowViewModel(Application application) { isWebXRUsed = new MutableLiveData<>(new ObservableBoolean(false)); isWebXRBlocked = new MutableLiveData<>(new ObservableBoolean(false)); - isContentFeed = new MediatorLiveData<>(); - isContentFeed.addSource(url, mIsContentFeedObserver); + isTrackingEnabled = new MutableLiveData<>(new ObservableBoolean(true)); } private Observer mIsTopBarVisibleObserver = new Observer() { @@ -289,14 +288,6 @@ public void onChanged(Spannable aUrl) { } }; - private Observer mIsContentFeedObserver = new Observer() { - @Override - public void onChanged(Spannable o) { - boolean feed = UrlUtils.isContentFeed(getApplication(), url.getValue().toString()); - isContentFeed.postValue(new ObservableBoolean(feed)); - } - }; - public void refresh() { url.postValue(url.getValue()); hint.postValue(getHintValue()); @@ -322,6 +313,7 @@ public void refresh() { isMediaPlaying.postValue(isMediaPlaying.getValue()); isWebXRUsed.postValue(isWebXRUsed.getValue()); isWebXRBlocked.postValue(isWebXRBlocked.getValue()); + isTrackingEnabled.postValue(isTrackingEnabled.getValue()); } @NonNull @@ -677,7 +669,11 @@ public void setIsPopUpAvailable(boolean isPopUpAvailable) { } @NonNull - public MutableLiveData getIsContentFeed() { - return isContentFeed; + 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 18e5f1bd7a..9d41773977 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(); @@ -409,6 +412,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) { @@ -559,5 +572,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 a61c3941c6..94f72d47d8 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,6 +11,7 @@ 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; @@ -37,6 +38,7 @@ import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; +import org.mozilla.vrbrowser.browser.content.TrackingProtectionStore; import org.mozilla.vrbrowser.databinding.NavigationBarBinding; import org.mozilla.vrbrowser.db.SitePermission; import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; @@ -62,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, @@ -109,6 +112,7 @@ public interface NavigationListener { private int mBlockedCount; private Executor mUIThreadExecutor; private ArrayList mNavigationListeners; + private TrackingProtectionStore mTrackingDelegate; public NavigationBarWidget(Context aContext) { super(aContext); @@ -158,6 +162,8 @@ private void initialize(@NonNull Context aContext) { mSuggestionsProvider = new SuggestionsProvider(getContext()); + mTrackingDelegate = SessionStore.get().getTrackingProtectionStore(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(mAppContext); mPrefs.registerOnSharedPreferenceChangeListener(this); } @@ -362,6 +368,19 @@ private void updateUI() { } } + TrackingProtectionStore.TrackingProtectionListener mTrackingListener = new TrackingProtectionStore.TrackingProtectionListener() { + @Override + public void onExcludedTrackingProtectionChange(@NonNull String host, boolean excluded) { + Session currentSession = getSession(); + if (currentSession != null) { + String existingHost = UrlUtils.getHost(currentSession.getCurrentUri()); + if (existingHost.equals(host)) { + mViewModel.setIsTrackingEnabled(!excluded); + } + } + } + }; + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -458,8 +477,12 @@ public void detachFromWindow() { mBinding.navigationBarNavigation.urlBar.detachFromWindow(); + mTrackingDelegate.removeListener(mTrackingListener); + if (mViewModel != null) { mViewModel.getIsFullscreen().removeObserver(mIsFullscreenObserver); + mViewModel.getIsActiveWindow().removeObserver(mIsActiveWindowObserver); + mViewModel.getUrl().removeObserver(mUrlObserver); mViewModel = null; } } @@ -481,9 +504,13 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mBinding.setViewmodel(mViewModel); - mViewModel.getIsFullscreen().observe((VRBrowserActivity)getContext(), mIsFullscreenObserver); + mViewModel.getIsFullscreen().observeForever( mIsFullscreenObserver); + mViewModel.getIsActiveWindow().observeForever(mIsActiveWindowObserver); + mViewModel.getUrl().observeForever(mUrlObserver); mBinding.navigationBarNavigation.urlBar.attachToWindow(mAttachedWindow); + mTrackingDelegate.addListener(mTrackingListener); + mAttachedWindow.addWindowListener(this); mAttachedWindow.setPopUpDelegate(mPopUpDelegate); @@ -804,6 +831,30 @@ public void onFullScreen(@NonNull GeckoSession session, boolean aFullScreen) { } }; + private Observer mUrlObserver = sitePermissions -> updateTrackingProtection(); + + private Observer mIsActiveWindowObserver = aIsActiveWindow -> updateTrackingProtection(); + + private void updateTrackingProtection() { + if (getSession() != null) { + mTrackingDelegate.contains(getSession(), isExcluded -> { + if (isExcluded != null) { + mViewModel.setIsTrackingEnabled(!isExcluded); + } + + return null; + }); + + mTrackingDelegate.fetchAll(sitePermissions -> { + Log.d(LOGTAG, "Start"); + sitePermissions.forEach(site -> Log.d(LOGTAG, site.url + " - " + site.allowed)); + Log.d(LOGTAG, "End"); + + return null; + }); + } + } + // WidgetManagerDelegate.UpdateListener @Override public void onWidgetUpdate(Widget aWidget) { @@ -921,6 +972,13 @@ public void onWebXRButtonClicked() { mViewModel.getIsWebXRBlocked().getValue().get()); } + @Override + public void onTrackingButtonClicked() { + toggleQuickPermission(mBinding.navigationBarNavigation.urlBar.getTrackingRButton(), + SitePermission.SITE_PERMISSION_TRACKING, + mViewModel.getIsTrackingEnabled().getValue().get()); + } + // VoiceSearch Delegate @Override @@ -1179,13 +1237,27 @@ private void toggleQuickPermission(UIButton target, @SitePermission.Category int 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(); } @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(); } }); 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 977edd826e..0bc72d15d8 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 231c89355a..19c215aef7 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 @@ -26,6 +26,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; @@ -105,14 +106,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); @@ -136,6 +134,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) { @@ -161,8 +169,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) -> { @@ -198,8 +206,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) { @@ -242,14 +250,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 0fb6f04758..6bf8563bfa 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 @@ -431,6 +431,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 ceba85d3df..facf461a6a 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 @@ -88,6 +88,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 2fc7ca1fe5..41f06158a7 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"> + + android:layout_marginStart="2dp" + android:addStatesFromChildren="true" + android:orientation="horizontal" + app:visibleGone="@{!viewmodel.isLibraryVisible}"> - - - - - - - + app:visibleGone="@{!UrlUtils.isContentFeed(context, viewmodel.url.toString())}" + android:tooltipText="@{viewmodel.isTrackingEnabled ? @string/tracking_allowed_tooltip : @string/tracking_disabled_tooltip}" /> - + android:orientation="horizontal" + app:visibleGone="@{viewmodel.isPopUpAvailable}"> + + app:privateMode="@{viewmodel.isPrivateSession}" /> + + + - - + android:tooltipText="@{viewmodel.isWebXRBlocked ? @string/webxr_blocked_tooltip : @string/webxr_allowed_tooltip}" /> - - - - + - + + android:layout_toEndOf="@id/startButtonsLayout" + 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