mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Allow filtered taps if all overlay apps are whitelisted.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user