Allow filtered taps if all overlay apps are whitelisted.

This commit is contained in:
akwizgran
2018-01-04 13:19:49 +00:00
parent 7ec05ac0cd
commit 753068288f
11 changed files with 200 additions and 59 deletions

View File

@@ -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();

View File

@@ -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() {

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.android;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -16,19 +18,23 @@ 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.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
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
@@ -56,54 +62,75 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
"0B145B6AA192858E79020103";
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
private final PackageManager pm;
private final SharedPreferences prefs;
@Inject
ScreenFilterMonitorImpl(Application app) {
ScreenFilterMonitorImpl(Application app, SharedPreferences prefs) {
pm = app.getPackageManager();
this.prefs = prefs;
}
@Override
@UiThread
public Set<String> getApps() {
Set<String> screenFilterApps = new TreeSet<>();
public Collection<AppDetails> getApps() {
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));
return apps;
}
// Fetches the application name for a given package.
@Nullable
private String pkgToString(PackageInfo pkgInfo) {
@Override
public void allowApps(Collection<String> packageNames) {
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;
if (flags == null || flags.length != requestedPermissions.length)
throw new AssertionError();
for (int i = 0; i < requestedPermissions.length; i++) {
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)
&& (flags[i] & REQUESTED_PERMISSION_GRANTED) != 0) {
return true;
}
}
} else {
// Check whether the permission has been requested
for (String requestedPermission : requestedPermissions) {
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
return true;
@@ -113,6 +140,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
return false;
}
@SuppressLint("PackageManagerGetSignatures")
private boolean isPlayServices(String pkg) {
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
try {

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -24,10 +24,11 @@ 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 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;
@@ -132,16 +133,19 @@ 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));
private boolean showScreenFilterWarning() {
// If the dialog is already visible, filter the tap
if (dialogFrag != null && dialogFrag.isVisible()) return false;
Collection<AppDetails> apps = screenFilterMonitor.getApps();
// If all overlay apps are allowed or system apps, allow the tap
if (apps.isEmpty()) return true;
dialogFrag = ScreenFilterDialogFragment.newInstance(apps);
dialogFrag.setCancelable(false);
// Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag());
// Filter the tap
return false;
}
@Override
@@ -198,7 +202,10 @@ public abstract class BaseActivity extends AppCompatActivity
View decorView = getWindow().getDecorView();
if (decorView instanceof ViewGroup) {
Toolbar toolbar = findToolbar((ViewGroup) decorView);
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
if (toolbar != null) {
boolean filter = !screenFilterMonitor.getApps().isEmpty();
toolbar.setFilterTouchesWhenObscured(filter);
}
}
}
@@ -239,7 +246,7 @@ public abstract class BaseActivity extends AppCompatActivity
}
@Override
public void onTapFiltered() {
showScreenFilterWarning();
public boolean shouldAllowTap() {
return showScreenFilterWarning();
}
}

View File

@@ -1,40 +1,86 @@
package org.briarproject.briar.android.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
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 {
@Inject
ScreenFilterMonitor screenFilterMonitor;
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;
}
@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();
});
return builder.create();
}
}

View File

@@ -40,12 +40,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();
}
}

View File

@@ -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
Set<String> getApps();
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
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;
}
}
}

View File

@@ -0,0 +1,26 @@
<?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">
<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_marginTop="@dimen/margin_large"
android:text="@string/screen_filter_allow"/>
</LinearLayout>

View File

@@ -397,6 +397,7 @@
<!-- 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_allow">Allow these apps to draw on top</string>
<!-- Permission Requests -->
<string name="permission_camera_title">Camera permission</string>