From 8b32f82566b00d5be7283c792a6e8ddc172f389b Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Thu, 29 Jun 2017 18:18:39 +0100
Subject: [PATCH] Don't show tap protection dialog until it's needed.

---
 .../android/ScreenFilterMonitorImpl.java      |  16 ++-
 .../briar/android/StartupFailureActivity.java |   2 +-
 .../briar/android/activity/BaseActivity.java  | 111 ++++++++++++++----
 ...t.java => ScreenFilterDialogFragment.java} |  31 ++---
 .../android/splash/SplashScreenActivity.java  |   3 +-
 .../android/widget/TapSafeFrameLayout.java    |  51 ++++++++
 .../api/android/ScreenFilterMonitor.java      |   2 +-
 .../main/res/layout/alert_dialog_checkbox.xml |  13 +-
 briar-android/src/main/res/values/strings.xml |   5 +-
 briar-android/src/main/res/values/themes.xml  |   1 -
 10 files changed, 172 insertions(+), 63 deletions(-)
 rename briar-android/src/main/java/org/briarproject/briar/android/fragment/{SFDialogFragment.java => ScreenFilterDialogFragment.java} (68%)
 create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java

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 6acf752557..b6f0ea10fc 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
@@ -183,15 +183,13 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
 
 	@Override
 	@UiThread
-	public void storeAppsAsShown(Collection<String> s, boolean persistent) {
-		HashSet<String> buf = new HashSet<>(s);
-		shownApps.addAll(buf);
-		if (persistent && !s.isEmpty()) {
-			buf.addAll(getShownScreenFilterApps());
-			prefs.edit()
-					.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
-					.apply();
-		}
+	public void storeAppsAsShown(Collection<String> shown) {
+		shownApps.addAll(shown);
+		HashSet<String> buf = new HashSet<>(shown);
+		buf.addAll(getShownScreenFilterApps());
+		prefs.edit()
+				.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
+				.apply();
 	}
 
 	private Set<String> getInstalledScreenFilterApps() {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java
index 01a18b2f49..6a1292da63 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/StartupFailureActivity.java
@@ -27,7 +27,7 @@ public class StartupFailureActivity extends BaseActivity {
 	}
 
 	@Override
-	protected void showNewScreenFilterWarning() {
+	protected void showScreenFilterWarning() {
 		// Don't show here, service might not be available
 	}
 
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 8b12f7e2b1..53bdc6e1fe 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
@@ -2,9 +2,13 @@ package org.briarproject.briar.android.activity;
 
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.annotation.LayoutRes;
 import android.support.annotation.UiThread;
 import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.inputmethod.InputMethodManager;
 
 import org.briarproject.bramble.api.db.DbException;
@@ -13,7 +17,9 @@ import org.briarproject.briar.android.BriarApplication;
 import org.briarproject.briar.android.DestroyableContext;
 import org.briarproject.briar.android.controller.ActivityLifecycleController;
 import org.briarproject.briar.android.forum.ForumModule;
-import org.briarproject.briar.android.fragment.SFDialogFragment;
+import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
+import org.briarproject.briar.android.widget.TapSafeFrameLayout;
+import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
 import org.briarproject.briar.api.android.ScreenFilterMonitor;
 
 import java.util.ArrayList;
@@ -23,21 +29,23 @@ import java.util.Set;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
 import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
 
 public abstract class BaseActivity extends AppCompatActivity
-		implements DestroyableContext {
+		implements DestroyableContext, OnTapFilteredListener {
+
+	@Inject
+	protected ScreenFilterMonitor screenFilterMonitor;
+
 	protected ActivityComponent activityComponent;
 
 	private final List<ActivityLifecycleController> lifecycleControllers =
 			new ArrayList<>();
 	private boolean destroyed = false;
-
-	@Inject
-	protected ScreenFilterMonitor screenFilterMonitor;
-	private SFDialogFragment dialogFrag;
+	private ScreenFilterDialogFragment dialogFrag;
 
 	public abstract void injectActivity(ActivityComponent component);
 
@@ -65,7 +73,6 @@ public abstract class BaseActivity extends AppCompatActivity
 		for (ActivityLifecycleController alc : lifecycleControllers) {
 			alc.onActivityCreate(this);
 		}
-
 	}
 
 	public ActivityComponent getActivityComponent() {
@@ -97,12 +104,6 @@ public abstract class BaseActivity extends AppCompatActivity
 		}
 	}
 
-	@Override
-	protected void onPostResume() {
-		super.onPostResume();
-		showNewScreenFilterWarning();
-	}
-
 	@Override
 	protected void onPause() {
 		super.onPause();
@@ -112,18 +113,18 @@ public abstract class BaseActivity extends AppCompatActivity
 		}
 	}
 
-	protected void showNewScreenFilterWarning() {
-		final Set<String> apps = screenFilterMonitor.getApps();
-		if (apps.isEmpty()) {
-			return;
-		}
-		dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
+	protected 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);
-		dialogFrag.show(getSupportFragmentManager(), "SFDialog");
+		dialogFrag.show(getSupportFragmentManager(), dialogFrag.getTag());
 	}
 
-	public void rememberShownApps(ArrayList<String> s, boolean permanent) {
-		screenFilterMonitor.storeAppsAsShown(s, permanent);
+	public void rememberShownApps(ArrayList<String> s) {
+		screenFilterMonitor.storeAppsAsShown(s);
 	}
 
 	@Override
@@ -161,4 +162,70 @@ public abstract class BaseActivity extends AppCompatActivity
 		supportFinishAfterTransition();
 	}
 
+	/*
+	 * Wraps the given view in a wrapper that notifies this activity when an
+	 * obscured touch has been filtered, and returns the wrapper.
+	 */
+	private View makeTapSafeWrapper(View v) {
+		TapSafeFrameLayout wrapper = new TapSafeFrameLayout(this);
+		wrapper.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+		wrapper.setOnTapFilteredListener(this);
+		wrapper.addView(v);
+		return wrapper;
+	}
+
+	/*
+	 * Finds the AppCompat toolbar, if any, and configures it to filter
+	 * obscured touches. If a custom toolbar is used, it will be part of the
+	 * content view and thus protected by the wrapper. But the default toolbar
+	 * 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);
+		}
+	}
+
+	@Nullable
+	private Toolbar findToolbar(ViewGroup vg) {
+		for (int i = 0, len = vg.getChildCount(); i < len; i++) {
+			View child = vg.getChildAt(i);
+			if (child instanceof Toolbar) return (Toolbar) child;
+			if (child instanceof ViewGroup) {
+				Toolbar toolbar = findToolbar((ViewGroup) child);
+				if (toolbar != null) return toolbar;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public void setContentView(@LayoutRes int layoutRes) {
+		setContentView(getLayoutInflater().inflate(layoutRes, null));
+	}
+
+	@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();
+	}
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/fragment/SFDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
similarity index 68%
rename from briar-android/src/main/java/org/briarproject/briar/android/fragment/SFDialogFragment.java
rename to briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
index 248d4c49ad..24c6539732 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/fragment/SFDialogFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java
@@ -22,10 +22,11 @@ import javax.annotation.Nullable;
 
 @MethodsNotNullByDefault
 @ParametersNotNullByDefault
-public class SFDialogFragment extends DialogFragment {
+public class ScreenFilterDialogFragment extends DialogFragment {
 
-	public static SFDialogFragment newInstance(ArrayList<String> apps) {
-		SFDialogFragment frag = new SFDialogFragment();
+	public static ScreenFilterDialogFragment newInstance(
+			ArrayList<String> apps) {
+		ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
 		Bundle args = new Bundle();
 		args.putStringArrayList("apps", apps);
 		frag.setArguments(args);
@@ -34,31 +35,25 @@ public class SFDialogFragment extends DialogFragment {
 
 	@Override
 	public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
-		AlertDialog.Builder builder =
-				new AlertDialog.Builder(
-						getActivity(),
-						R.style.BriarDialogThemeNoFilter);
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
+				R.style.BriarDialogThemeNoFilter);
 		builder.setTitle(R.string.screen_filter_title);
 		LayoutInflater li = getActivity().getLayoutInflater();
-		//Pass null here because it's an AlertDialog
-		View v =
-				li.inflate(R.layout.alert_dialog_checkbox, null,
-						false);
+		// Pass null here because it's an AlertDialog
+		View v = li.inflate(R.layout.alert_dialog_checkbox, null, false);
 		TextView t = (TextView) v.findViewById(R.id.alert_dialog_text);
 		final ArrayList<String> apps =
 				getArguments().getStringArrayList("apps");
 		t.setText(getString(R.string.screen_filter_body, TextUtils
 				.join("\n", apps)));
-		final CheckBox cb =
-				(CheckBox) v.findViewById(
-						R.id.checkBox_screen_filter_reminder);
+		final CheckBox cb = (CheckBox) v.findViewById(
+				R.id.checkbox_dont_show_again);
 		builder.setNeutralButton(R.string.continue_button,
 				new DialogInterface.OnClickListener() {
 					@Override
-					public void onClick(DialogInterface dialog,
-							int which) {
-						((BaseActivity) getActivity())
-								.rememberShownApps(apps, cb.isChecked());
+					public void onClick(DialogInterface dialog, int which) {
+						if (cb.isChecked())
+						((BaseActivity) getActivity()).rememberShownApps(apps);
 					}
 				});
 		builder.setView(v);
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
index d67a5b6cd7..7d298ef1d6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java
@@ -84,7 +84,8 @@ public class SplashScreenActivity extends BaseActivity {
 	}
 
 	@Override
-	protected void showNewScreenFilterWarning() {
+	protected void showScreenFilterWarning() {
+		// Ignore touches until the next activity is shown
 	}
 
 	private void enableStrictMode() {
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
new file mode 100644
index 0000000000..840e68b085
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/widget/TapSafeFrameLayout.java
@@ -0,0 +1,51 @@
+package org.briarproject.briar.android.widget;
+
+import android.content.Context;
+import android.support.annotation.AttrRes;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import javax.annotation.Nullable;
+
+import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED;
+
+@NotNullByDefault
+public class TapSafeFrameLayout extends FrameLayout {
+
+	@Nullable
+	private OnTapFilteredListener listener;
+
+	public TapSafeFrameLayout(Context context) {
+		super(context);
+		setFilterTouchesWhenObscured(false);
+	}
+
+	public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) {
+		super(context, attrs);
+		setFilterTouchesWhenObscured(false);
+	}
+
+	public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs,
+			@AttrRes int defStyleAttr) {
+		super(context, attrs, defStyleAttr);
+		setFilterTouchesWhenObscured(false);
+	}
+
+	public void setOnTapFilteredListener(OnTapFilteredListener listener) {
+		this.listener = listener;
+	}
+
+	@Override
+	public boolean onFilterTouchEventForSecurity(MotionEvent e) {
+		boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
+		if (filter && listener != null) listener.onTapFiltered();
+		return !filter;
+	}
+
+	public interface OnTapFilteredListener {
+		void onTapFiltered();
+	}
+}
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 b596ecbb2a..56adc43364 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
@@ -11,5 +11,5 @@ public interface ScreenFilterMonitor {
 	Set<String> getApps();
 
 	@UiThread
-	void storeAppsAsShown(Collection<String> s, boolean persistent);
+	void storeAppsAsShown(Collection<String> shown);
 }
diff --git a/briar-android/src/main/res/layout/alert_dialog_checkbox.xml b/briar-android/src/main/res/layout/alert_dialog_checkbox.xml
index 718589da9a..a7a7e1279c 100644
--- a/briar-android/src/main/res/layout/alert_dialog_checkbox.xml
+++ b/briar-android/src/main/res/layout/alert_dialog_checkbox.xml
@@ -1,9 +1,9 @@
 <?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:filterTouchesWhenObscured="false"
-              android:orientation="vertical">
+<LinearLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="match_parent"
+	android:layout_height="match_parent"
+	android:orientation="vertical">
 
 	<ScrollView
 		android:layout_width="match_parent"
@@ -28,13 +28,12 @@
 	</ScrollView>
 
 	<CheckBox
-		android:id="@+id/checkBox_screen_filter_reminder"
+		android:id="@+id/checkbox_dont_show_again"
 		android:layout_width="match_parent"
 		android:layout_height="wrap_content"
 		android:layout_marginLeft="15dp"
 		android:layout_marginStart="15dp"
 		android:layout_weight="0"
-		android:filterTouchesWhenObscured="false"
 		android:text="@string/checkbox_dont_show_again"
 		android:textAppearance="@style/BriarTextBody"/>
 </LinearLayout>
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index 42d6ba9f20..fbbeb9fdbd 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -381,9 +381,8 @@
 	<string name="progress_title_logout">Signing out of Briar…</string>
 
 	<!-- Screen Filters & Tapjacking -->
-	<string name="screen_filter_title">Screen filter detected</string>
-	<string name="screen_filter_body">The following apps have permission to draw over other apps:\n\n%1$s \n\nBriar will not respond to touches when another app is drawing over it.
-If you experience any problems, try turning off these apps when using Briar.\n</string>
+	<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 while another app is drawing on top.\n\nThe following apps have permission to draw on top:\n\n%1$s \n\nTry turning off these apps when using Briar.\n</string>
 	<string name="checkbox_dont_show_again">Don\'t warn me again for these apps</string>
 
 </resources>
diff --git a/briar-android/src/main/res/values/themes.xml b/briar-android/src/main/res/values/themes.xml
index f87a92356e..a9b20148cc 100644
--- a/briar-android/src/main/res/values/themes.xml
+++ b/briar-android/src/main/res/values/themes.xml
@@ -8,7 +8,6 @@
 		<item name="android:textColorLink">@color/briar_text_link</item>
 		<item name="android:windowBackground">@color/window_background</item>
 		<item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
-		<item name="android:filterTouchesWhenObscured">true</item>
 
 		<!-- These fix a long-standing UI bug in the support preference library -->
 		<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
-- 
GitLab