Merge branch '1143-screen-overlay-dialog' into 'master'

Don't show screen overlay dialog if all overlay apps have been allowed

Closes #1143

See merge request akwizgran/briar!658
This commit is contained in:
akwizgran
2018-02-01 10:46:41 +00:00
14 changed files with 342 additions and 88 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() {
@@ -174,8 +180,11 @@ public class AppModule {
}
@Provides
@Singleton
ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager,
ScreenFilterMonitorImpl screenFilterMonitor) {
lifecycleManager.registerService(screenFilterMonitor);
return screenFilterMonitor;
}

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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