diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
index 1c6cc9d86f359dc248f9c30f54198fc4274d94c6..02a5dc0ff71fad378ff9ecc856fee8d87e304b33 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
@@ -1,5 +1,7 @@
 package org.briarproject.briar.android;
 
+import android.content.SharedPreferences;
+
 import org.briarproject.bramble.BrambleAndroidModule;
 import org.briarproject.bramble.BrambleCoreEagerSingletons;
 import org.briarproject.bramble.BrambleCoreModule;
@@ -89,6 +91,8 @@ public interface AndroidComponent
 
 	AndroidNotificationManager androidNotificationManager();
 
+	SharedPreferences sharedPreferences();
+
 	ScreenFilterMonitor screenFilterMonitor();
 
 	ConnectionRegistry connectionRegistry();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
index c0e06897655f5c442ed6473a00814ad2e8673533..eba5d57eec64dfdc9645227d19e8809ecc793702 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
@@ -1,6 +1,7 @@
 package org.briarproject.briar.android;
 
 import android.app.Application;
+import android.content.SharedPreferences;
 
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.crypto.PublicKey;
@@ -157,6 +158,11 @@ public class AppModule {
 		return devConfig;
 	}
 
+	@Provides
+	SharedPreferences provideSharedPreferences(Application app) {
+		return app.getSharedPreferences("db", MODE_PRIVATE);
+	}
+
 	@Provides
 	@Singleton
 	ReferenceManager provideReferenceManager() {
@@ -174,8 +180,11 @@ public class AppModule {
 	}
 
 	@Provides
+	@Singleton
 	ScreenFilterMonitor provideScreenFilterMonitor(
+			LifecycleManager lifecycleManager,
 			ScreenFilterMonitorImpl screenFilterMonitor) {
+		lifecycleManager.registerService(screenFilterMonitor);
 		return screenFilterMonitor;
 	}
 
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java
index e1a88b592841989d0f536b3c6601f2fe21d5088d..fa9122c451fad394af99141c7b3716951b5897cd 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java
@@ -1,13 +1,22 @@
 package org.briarproject.briar.android;
 
+import android.annotation.SuppressLint;
 import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
 import android.support.annotation.UiThread;
 
+import org.briarproject.bramble.api.lifecycle.Service;
+import org.briarproject.bramble.api.lifecycle.ServiceException;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.system.AndroidExecutor;
 import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.android.ScreenFilterMonitor;
 
@@ -16,23 +25,33 @@ import java.io.InputStream;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.ACTION_PACKAGE_REPLACED;
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
+import static android.os.Build.VERSION.SDK_INT;
 import static java.util.logging.Level.WARNING;
 
 @NotNullByDefault
-class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
+class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
 
 	private static final Logger LOG =
 			Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
@@ -56,54 +75,93 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
 					"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
 					"0B145B6AA192858E79020103";
 
+	private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
+
 	private final PackageManager pm;
+	private final Application app;
+	private final AndroidExecutor androidExecutor;
+	private final SharedPreferences prefs;
+	private final AtomicBoolean used = new AtomicBoolean(false);
+
+	// UiThread
+	@Nullable
+	private BroadcastReceiver receiver = null;
+
+	// UiThread
+	@Nullable
+	private Collection<AppDetails> cachedApps = null;
 
 	@Inject
-	ScreenFilterMonitorImpl(Application app) {
+	ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
+			SharedPreferences prefs) {
 		pm = app.getPackageManager();
+		this.app = app;
+		this.androidExecutor = androidExecutor;
+		this.prefs = prefs;
 	}
 
 	@Override
 	@UiThread
-	public Set<String> getApps() {
-		Set<String> screenFilterApps = new TreeSet<>();
+	public Collection<AppDetails> getApps() {
+		if (cachedApps != null) return cachedApps;
+		Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
+				Collections.emptySet());
+		List<AppDetails> apps = new ArrayList<>();
 		List<PackageInfo> packageInfos =
 				pm.getInstalledPackages(GET_PERMISSIONS);
 		for (PackageInfo packageInfo : packageInfos) {
-			if (isOverlayApp(packageInfo)) {
-				String name = pkgToString(packageInfo);
-				if (name != null) {
-					screenFilterApps.add(name);
-				}
+			if (!allowed.contains(packageInfo.packageName)
+					&& isOverlayApp(packageInfo)) {
+				String name = getAppName(packageInfo);
+				apps.add(new AppDetails(name, packageInfo.packageName));
 			}
 		}
-		return screenFilterApps;
+		Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
+		apps = Collections.unmodifiableList(apps);
+		cachedApps = apps;
+		return apps;
 	}
 
-	// Fetches the application name for a given package.
-	@Nullable
-	private String pkgToString(PackageInfo pkgInfo) {
+	@Override
+	@UiThread
+	public void allowApps(Collection<String> packageNames) {
+		cachedApps = null;
+		Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
+				Collections.emptySet());
+		Set<String> merged = new HashSet<>(allowed);
+		merged.addAll(packageNames);
+		prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply();
+	}
+
+	// Returns the application name for a given package, or the package name
+	// if no application name is available
+	private String getAppName(PackageInfo pkgInfo) {
 		CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
-		if (seq != null) {
-			return seq.toString();
-		}
-		return null;
+		return seq == null ? pkgInfo.packageName : seq.toString();
 	}
 
 	// Checks if an installed package is a user app using the permission.
 	private boolean isOverlayApp(PackageInfo packageInfo) {
 		int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
 		// Ignore system apps
-		if ((packageInfo.applicationInfo.flags & mask) != 0) {
-			return false;
-		}
+		if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
 		// Ignore Play Services, it's effectively a system app
-		if (isPlayServices(packageInfo.packageName)) {
-			return false;
-		}
+		if (isPlayServices(packageInfo.packageName)) return false;
 		// Get permissions
 		String[] requestedPermissions = packageInfo.requestedPermissions;
-		if (requestedPermissions != null) {
+		if (requestedPermissions == null) return false;
+		if (SDK_INT >= 16 && SDK_INT < 23) {
+			// Check whether the permission has been requested and granted
+			int[] flags = packageInfo.requestedPermissionsFlags;
+			for (int i = 0; i < requestedPermissions.length; i++) {
+				if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) {
+					// 'flags' may be null on Robolectric
+					return flags == null ||
+							(flags[i] & REQUESTED_PERMISSION_GRANTED) != 0;
+				}
+			}
+		} else {
+			// Check whether the permission has been requested
 			for (String requestedPermission : requestedPermissions) {
 				if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
 					return true;
@@ -113,6 +171,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
 		return false;
 	}
 
+	@SuppressLint("PackageManagerGetSignatures")
 	private boolean isPlayServices(String pkg) {
 		if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
 		try {
@@ -135,4 +194,36 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
 			return false;
 		}
 	}
+
+	@Override
+	public void startService() throws ServiceException {
+		if (used.getAndSet(true)) throw new IllegalStateException();
+		androidExecutor.runOnUiThread(() -> {
+			IntentFilter filter = new IntentFilter();
+			filter.addAction(ACTION_PACKAGE_ADDED);
+			filter.addAction(ACTION_PACKAGE_CHANGED);
+			filter.addAction(ACTION_PACKAGE_REMOVED);
+			filter.addAction(ACTION_PACKAGE_REPLACED);
+			filter.addDataScheme("package");
+			receiver = new PackageBroadcastReceiver();
+			app.registerReceiver(receiver, filter);
+			cachedApps = null;
+		});
+	}
+
+	@Override
+	public void stopService() throws ServiceException {
+		androidExecutor.runOnUiThread(() -> {
+			if (receiver != null) app.unregisterReceiver(receiver);
+		});
+	}
+
+	private class PackageBroadcastReceiver extends BroadcastReceiver {
+
+		@Override
+		@UiThread
+		public void onReceive(Context context, Intent intent) {
+			cachedApps = null;
+		}
+	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
index f02774ae517e3669d75d62c1e2b3d9e5a494bf7a..4066c384a7fe756d0546e654dea7a7113e753b84 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
@@ -21,6 +21,7 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
 import org.briarproject.briar.android.forum.ForumActivity;
 import org.briarproject.briar.android.forum.ForumListFragment;
 import org.briarproject.briar.android.forum.ForumModule;
+import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
 import org.briarproject.briar.android.introduction.ContactChooserFragment;
 import org.briarproject.briar.android.introduction.IntroductionActivity;
 import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
@@ -152,7 +153,9 @@ public interface ActivityComponent {
 
 	// Fragments
 	void inject(AuthorNameFragment fragment);
+
 	void inject(PasswordFragment fragment);
+
 	void inject(DozeFragment fragment);
 
 	void inject(ContactListFragment fragment);
@@ -189,4 +192,5 @@ public interface ActivityComponent {
 
 	void inject(SettingsFragment fragment);
 
+	void inject(ScreenFilterDialogFragment fragment);
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
index 2a00be5ccfd4d5eb1a0810adf73945933091e61a..2651833f194b065326a5e063c17e2f0bc3ac2285 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityModule.java
@@ -1,7 +1,6 @@
 package org.briarproject.briar.android.activity;
 
 import android.app.Activity;
-import android.content.SharedPreferences;
 
 import org.briarproject.briar.android.controller.BriarController;
 import org.briarproject.briar.android.controller.BriarControllerImpl;
@@ -19,7 +18,6 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
 import dagger.Module;
 import dagger.Provides;
 
-import static android.content.Context.MODE_PRIVATE;
 import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
 
 @Module
@@ -57,12 +55,6 @@ public class ActivityModule {
 		return configController;
 	}
 
-	@ActivityScope
-	@Provides
-	SharedPreferences provideSharedPreferences(Activity activity) {
-		return activity.getSharedPreferences("db", MODE_PRIVATE);
-	}
-
 	@ActivityScope
 	@Provides
 	PasswordController providePasswordController(
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
index 88e6302ecb7fb01ae19de8dc65f1a0737483da0b..7c8f5061ae0c8777f3bebd1679174dba8e545a39 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
@@ -4,6 +4,7 @@ import android.os.Bundle;
 import android.os.IBinder;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.UiThread;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
@@ -21,13 +22,15 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
 import org.briarproject.briar.android.forum.ForumModule;
 import org.briarproject.briar.android.fragment.BaseFragment;
 import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
+import org.briarproject.briar.android.util.UiUtils;
 import org.briarproject.briar.android.widget.TapSafeFrameLayout;
 import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
 import org.briarproject.briar.api.android.ScreenFilterMonitor;
+import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -48,7 +51,10 @@ public abstract class BaseActivity extends AppCompatActivity
 	private final List<ActivityLifecycleController> lifecycleControllers =
 			new ArrayList<>();
 	private boolean destroyed = false;
-	private ScreenFilterDialogFragment dialogFrag;
+
+	@Nullable
+	private Toolbar toolbar = null;
+	private boolean searchedForToolbar = false;
 
 	public abstract void injectActivity(ActivityComponent component);
 
@@ -57,8 +63,8 @@ public abstract class BaseActivity extends AppCompatActivity
 	}
 
 	@Override
-	public void onCreate(@Nullable Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
+	public void onCreate(@Nullable Bundle state) {
+		super.onCreate(state);
 
 		if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
 
@@ -97,6 +103,16 @@ public abstract class BaseActivity extends AppCompatActivity
 		for (ActivityLifecycleController alc : lifecycleControllers) {
 			alc.onActivityStart();
 		}
+		protectToolbar();
+		ScreenFilterDialogFragment f = findDialogFragment();
+		if (f != null) f.setDismissListener(this::protectToolbar);
+	}
+
+	@Nullable
+	private ScreenFilterDialogFragment findDialogFragment() {
+		Fragment f = getSupportFragmentManager().findFragmentByTag(
+				ScreenFilterDialogFragment.TAG);
+		return (ScreenFilterDialogFragment) f;
 	}
 
 	@Override
@@ -107,15 +123,6 @@ public abstract class BaseActivity extends AppCompatActivity
 		}
 	}
 
-	@Override
-	protected void onPause() {
-		super.onPause();
-		if (dialogFrag != null) {
-			dialogFrag.dismiss();
-			dialogFrag = null;
-		}
-	}
-
 	protected void showInitialFragment(BaseFragment f) {
 		getSupportFragmentManager().beginTransaction()
 				.replace(R.id.fragmentContainer, f, f.getUniqueTag())
@@ -132,16 +139,27 @@ public abstract class BaseActivity extends AppCompatActivity
 				.commit();
 	}
 
-	private void showScreenFilterWarning() {
-		if (dialogFrag != null && dialogFrag.isVisible()) return;
-		Set<String> apps = screenFilterMonitor.getApps();
-		if (apps.isEmpty()) return;
-		dialogFrag =
-				ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
-		dialogFrag.setCancelable(false);
+	private boolean showScreenFilterWarning() {
+		// If the dialog is already visible, filter the tap
+		ScreenFilterDialogFragment f = findDialogFragment();
+		if (f != null && f.isVisible()) return false;
+		Collection<AppDetails> apps = screenFilterMonitor.getApps();
+		// If all overlay apps have been allowed, allow the tap
+		if (apps.isEmpty()) return true;
 		// Show dialog unless onSaveInstanceState() has been called, see #1112
 		FragmentManager fm = getSupportFragmentManager();
-		if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag());
+		if (!fm.isStateSaved()) {
+			// Create dialog
+			f = ScreenFilterDialogFragment.newInstance(apps);
+			// When dialog is dismissed, update protection of toolbar
+			f.setDismissListener(this::protectToolbar);
+			// Hide soft keyboard when (re)showing dialog
+			View focus = getCurrentFocus();
+			if (focus != null) hideSoftKeyboard(focus);
+			f.show(fm, ScreenFilterDialogFragment.TAG);
+		}
+		// Filter the tap
+		return false;
 	}
 
 	@Override
@@ -195,15 +213,25 @@ public abstract class BaseActivity extends AppCompatActivity
 	 * is outside the wrapper.
 	 */
 	private void protectToolbar() {
-		View decorView = getWindow().getDecorView();
-		if (decorView instanceof ViewGroup) {
-			Toolbar toolbar = findToolbar((ViewGroup) decorView);
-			if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
+		findToolbar();
+		if (toolbar != null) {
+			boolean filter = !screenFilterMonitor.getApps().isEmpty();
+			UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
 		}
 	}
 
+	private void findToolbar() {
+		if (searchedForToolbar) return;
+		View decorView = getWindow().getDecorView();
+		if (decorView instanceof ViewGroup)
+			toolbar = findToolbar((ViewGroup) decorView);
+		searchedForToolbar = true;
+	}
+
 	@Nullable
 	private Toolbar findToolbar(ViewGroup vg) {
+		// Views inside tap-safe layouts are already protected
+		if (vg instanceof TapSafeFrameLayout) return null;
 		for (int i = 0, len = vg.getChildCount(); i < len; i++) {
 			View child = vg.getChildAt(i);
 			if (child instanceof Toolbar) return (Toolbar) child;
@@ -223,23 +251,20 @@ public abstract class BaseActivity extends AppCompatActivity
 	@Override
 	public void setContentView(View v) {
 		super.setContentView(makeTapSafeWrapper(v));
-		protectToolbar();
 	}
 
 	@Override
 	public void setContentView(View v, LayoutParams layoutParams) {
 		super.setContentView(makeTapSafeWrapper(v), layoutParams);
-		protectToolbar();
 	}
 
 	@Override
 	public void addContentView(View v, LayoutParams layoutParams) {
 		super.addContentView(makeTapSafeWrapper(v), layoutParams);
-		protectToolbar();
 	}
 
 	@Override
-	public void onTapFiltered() {
-		showScreenFilterWarning();
+	public boolean shouldAllowTap() {
+		return showScreenFilterWarning();
 	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
index 37e8ed1409aacf17906adae907251bc4ce40b884..4996961820534ddedb08168fda130eb4f455b6a5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java
@@ -80,7 +80,7 @@ public abstract class BriarActivity extends BaseActivity {
 	public void setSceneTransitionAnimation() {
 		if (SDK_INT < 21) return;
 		// workaround for #1007
-		if (isSamsung7(this)) {
+		if (isSamsung7()) {
 			return;
 		}
 		Transition slide = new Slide(Gravity.RIGHT);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java
index 3126d87042f0fcec175a84da50c59091e641b370..e579aea13b2211704466811a858df33ca199f5ce 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/blog/BlogPostViewHolder.java
@@ -131,7 +131,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
 			i.putExtra(GROUP_ID, item.getGroupId().getBytes());
 			i.putExtra(POST_ID, item.getId().getBytes());
 
-			if (Build.VERSION.SDK_INT >= 23 && !isSamsung7(ctx)) {
+			if (Build.VERSION.SDK_INT >= 23 && !isSamsung7()) {
 				ActivityOptionsCompat options =
 						makeSceneTransitionAnimation((Activity) ctx, layout,
 								getTransitionName(item.getId()));
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
index b1e644e96a35c4cee93f321abd32f3ffcb032474..b7c6116653ed3d7540e0553b2b2c1426d03a05ac 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
@@ -1,40 +1,106 @@
 package org.briarproject.briar.android.fragment;
 
+import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.Dialog;
+import android.content.DialogInterface;
 import android.os.Bundle;
 import android.support.v4.app.DialogFragment;
 import android.support.v7.app.AlertDialog;
 import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
 
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
 import org.briarproject.briar.R;
+import org.briarproject.briar.android.activity.BaseActivity;
+import org.briarproject.briar.api.android.ScreenFilterMonitor;
+import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
 
 import java.util.ArrayList;
+import java.util.Collection;
 
 import javax.annotation.Nullable;
+import javax.inject.Inject;
 
-@NotNullByDefault
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
 public class ScreenFilterDialogFragment extends DialogFragment {
 
+	public static final String TAG = ScreenFilterDialogFragment.class.getName();
+
+	@Inject
+	ScreenFilterMonitor screenFilterMonitor;
+
+	DismissListener dismissListener = null;
+
 	public static ScreenFilterDialogFragment newInstance(
-			ArrayList<String> apps) {
+			Collection<AppDetails> apps) {
 		ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
 		Bundle args = new Bundle();
-		args.putStringArrayList("apps", apps);
+		ArrayList<String> appNames = new ArrayList<>();
+		for (AppDetails a : apps) appNames.add(a.name);
+		args.putStringArrayList("appNames", appNames);
+		ArrayList<String> packageNames = new ArrayList<>();
+		for (AppDetails a : apps) packageNames.add(a.packageName);
+		args.putStringArrayList("packageNames", packageNames);
 		frag.setArguments(args);
 		return frag;
 	}
 
+	public void setDismissListener(DismissListener dismissListener) {
+		this.dismissListener = dismissListener;
+	}
+
+	@Override
+	public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		Activity activity = getActivity();
+		if (activity == null) throw new IllegalStateException();
+		((BaseActivity) activity).getActivityComponent().inject(this);
+	}
+
 	@Override
 	public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
+		Activity activity = getActivity();
+		if (activity == null) throw new IllegalStateException();
+		AlertDialog.Builder builder = new AlertDialog.Builder(activity,
 				R.style.BriarDialogThemeNoFilter);
 		builder.setTitle(R.string.screen_filter_title);
-		ArrayList<String> apps = getArguments().getStringArrayList("apps");
-		builder.setMessage(getString(R.string.screen_filter_body,
-				TextUtils.join("\n", apps)));
-		builder.setNeutralButton(R.string.continue_button,
-				(dialog, which) -> dialog.dismiss());
+		Bundle args = getArguments();
+		if (args == null) throw new IllegalStateException();
+		ArrayList<String> appNames = args.getStringArrayList("appNames");
+		ArrayList<String> packageNames =
+				args.getStringArrayList("packageNames");
+		if (appNames == null || packageNames == null)
+			throw new IllegalStateException();
+		LayoutInflater inflater = activity.getLayoutInflater();
+		// See https://stackoverflow.com/a/24720976/6314875
+		@SuppressLint("InflateParams")
+		View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
+		builder.setView(dialogView);
+		TextView message = dialogView.findViewById(R.id.screen_filter_message);
+		message.setText(getString(R.string.screen_filter_body,
+				TextUtils.join("\n", appNames)));
+		CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox);
+		builder.setNeutralButton(R.string.continue_button, (dialog, which) -> {
+			if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames);
+			dialog.dismiss();
+		});
+		builder.setCancelable(false);
 		return builder.create();
 	}
+
+	@Override
+	public void onDismiss(DialogInterface dialog) {
+		super.onDismiss(dialog);
+		if (dismissListener != null) dismissListener.onDialogDismissed();
+	}
+
+	public interface DismissListener {
+		void onDialogDismissed();
+	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
index 7e31452feb806b53346f46187cf432dbf41803e0..26e61f37f839fa092fc457a88410142e5e5489d5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
@@ -6,7 +6,6 @@ import android.content.Context;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Build;
 import android.os.PowerManager;
 import android.support.design.widget.TextInputLayout;
 import android.support.v4.app.FragmentManager;
@@ -162,7 +161,7 @@ public class UiUtils {
 	}
 
 	public static boolean needsDozeWhitelisting(Context ctx) {
-		if (Build.VERSION.SDK_INT < 23) return false;
+		if (SDK_INT < 23) return false;
 		PowerManager pm = (PowerManager) ctx.getSystemService(POWER_SERVICE);
 		String packageName = ctx.getPackageName();
 		if (pm == null) throw new AssertionError();
@@ -178,8 +177,15 @@ public class UiUtils {
 		return i;
 	}
 
-	public static boolean isSamsung7(Context context) {
+	public static boolean isSamsung7() {
 		return SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung");
 	}
 
+	public static void setFilterTouchesWhenObscured(View v, boolean filter) {
+		v.setFilterTouchesWhenObscured(filter);
+		// Workaround for Android bug #13530806, see
+		// https://android.googlesource.com/platform/frameworks/base/+/aba566589e0011c4b973c0d4f77be4e9ee176089%5E%21/core/java/android/view/View.java
+		if (v.getFilterTouchesWhenObscured() != filter)
+			v.setFilterTouchesWhenObscured(!filter);
+	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java b/briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java
index 840e68b0852e6f3ff5f4aaf372d31bfa77ef1068..6d285682f438aec5207d9adc678af41f699ac443 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java
@@ -7,6 +7,7 @@ import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.briar.android.util.UiUtils;
 
 import javax.annotation.Nullable;
 
@@ -20,18 +21,18 @@ public class TapSafeFrameLayout extends FrameLayout {
 
 	public TapSafeFrameLayout(Context context) {
 		super(context);
-		setFilterTouchesWhenObscured(false);
+		UiUtils.setFilterTouchesWhenObscured(this, false);
 	}
 
 	public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) {
 		super(context, attrs);
-		setFilterTouchesWhenObscured(false);
+		UiUtils.setFilterTouchesWhenObscured(this, false);
 	}
 
 	public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs,
 			@AttrRes int defStyleAttr) {
 		super(context, attrs, defStyleAttr);
-		setFilterTouchesWhenObscured(false);
+		UiUtils.setFilterTouchesWhenObscured(this, false);
 	}
 
 	public void setOnTapFilteredListener(OnTapFilteredListener listener) {
@@ -40,12 +41,12 @@ public class TapSafeFrameLayout extends FrameLayout {
 
 	@Override
 	public boolean onFilterTouchEventForSecurity(MotionEvent e) {
-		boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
-		if (filter && listener != null) listener.onTapFiltered();
-		return !filter;
+		boolean obscured = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
+		if (obscured && listener != null) return listener.shouldAllowTap();
+		else return !obscured;
 	}
 
 	public interface OnTapFilteredListener {
-		void onTapFiltered();
+		boolean shouldAllowTap();
 	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/api/android/ScreenFilterMonitor.java b/briar-android/src/main/java/org/briarproject/briar/api/android/ScreenFilterMonitor.java
index 9df5add2f704673a7000300c039d8e2bcfdf3616..a14b3b078b5bf814dbaec6ff7d26c5482282a0f8 100644
--- a/briar-android/src/main/java/org/briarproject/briar/api/android/ScreenFilterMonitor.java
+++ b/briar-android/src/main/java/org/briarproject/briar/api/android/ScreenFilterMonitor.java
@@ -2,10 +2,37 @@ package org.briarproject.briar.api.android;
 
 import android.support.annotation.UiThread;
 
-import java.util.Set;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 
+import java.util.Collection;
+
+@NotNullByDefault
 public interface ScreenFilterMonitor {
 
+	/**
+	 * Returns the details of all apps that have requested the
+	 * SYSTEM_ALERT_WINDOW permission, excluding system apps, Google Play
+	 * Services, and any apps that have been allowed by calling
+	 * {@link #allowApps(Collection)}.
+	 */
+	@UiThread
+	Collection<AppDetails> getApps();
+
+	/**
+	 * Allows the apps with the given package names to use overlay windows.
+	 * They will not be returned by future calls to {@link #getApps()}.
+	 */
 	@UiThread
-	Set<String> getApps();
+	void allowApps(Collection<String> packageNames);
+
+	class AppDetails {
+
+		public final String name;
+		public final String packageName;
+
+		public AppDetails(String name, String packageName) {
+			this.name = name;
+			this.packageName = packageName;
+		}
+	}
 }
diff --git a/briar-android/src/main/res/layout/dialog_screen_filter.xml b/briar-android/src/main/res/layout/dialog_screen_filter.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b883d16e71408101e42e4f0e890932ca29bc8493
--- /dev/null
+++ b/briar-android/src/main/res/layout/dialog_screen_filter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical"
+              android:padding="@dimen/margin_large">
+
+	<ScrollView
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_weight="1">
+
+		<TextView
+			android:id="@+id/screen_filter_message"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"/>
+
+	</ScrollView>
+
+	<CheckBox
+		android:id="@+id/screen_filter_checkbox"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_weight="0"
+		android:layout_marginTop="@dimen/margin_large"
+		android:text="@string/screen_filter_allow"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index 97da612ac0fd9f2437bde989bfae6ba8c1834c1f..a990a022f21768f3e5a22956ac86c2c29f71d85e 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -396,7 +396,8 @@
 
 	<!-- Screen Filters & Tapjacking -->
 	<string name="screen_filter_title">Screen overlay detected</string>
-	<string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nTry turning off the following apps when using Briar:\n\n%1$s</string>
+	<string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nThe following apps might be drawing on top:\n\n%1$s</string>
+	<string name="screen_filter_allow">Allow these apps to draw on top</string>
 
 	<!-- Permission Requests -->
 	<string name="permission_camera_title">Camera permission</string>