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