Cache the list of overlay apps.

This commit is contained in:
akwizgran
2018-01-05 11:58:22 +00:00
parent 7aebf92a6f
commit fcbf6dfb7f
4 changed files with 118 additions and 19 deletions

View File

@@ -180,8 +180,11 @@ public class AppModule {
} }
@Provides @Provides
@Singleton
ScreenFilterMonitor provideScreenFilterMonitor( ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager,
ScreenFilterMonitorImpl screenFilterMonitor) { ScreenFilterMonitorImpl screenFilterMonitor) {
lifecycleManager.registerService(screenFilterMonitor);
return screenFilterMonitor; return screenFilterMonitor;
} }

View File

@@ -2,6 +2,10 @@ package org.briarproject.briar.android;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Application; 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.SharedPreferences;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -9,7 +13,10 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature; import android.content.pm.Signature;
import android.support.annotation.UiThread; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
@@ -24,11 +31,17 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; 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_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
@@ -38,7 +51,7 @@ import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@NotNullByDefault @NotNullByDefault
class ScreenFilterMonitorImpl implements ScreenFilterMonitor { class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ScreenFilterMonitorImpl.class.getName()); Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
@@ -65,17 +78,32 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps"; private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
private final PackageManager pm; private final PackageManager pm;
private final Application app;
private final AndroidExecutor androidExecutor;
private final SharedPreferences prefs; 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 @Inject
ScreenFilterMonitorImpl(Application app, SharedPreferences prefs) { ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
SharedPreferences prefs) {
pm = app.getPackageManager(); pm = app.getPackageManager();
this.app = app;
this.androidExecutor = androidExecutor;
this.prefs = prefs; this.prefs = prefs;
} }
@Override @Override
@UiThread @UiThread
public Collection<AppDetails> getApps() { public Collection<AppDetails> getApps() {
if (cachedApps != null) return cachedApps;
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet()); Collections.emptySet());
List<AppDetails> apps = new ArrayList<>(); List<AppDetails> apps = new ArrayList<>();
@@ -89,11 +117,15 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
} }
} }
Collections.sort(apps, (a, b) -> a.name.compareTo(b.name)); Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
apps = Collections.unmodifiableList(apps);
cachedApps = apps;
return apps; return apps;
} }
@Override @Override
@UiThread
public void allowApps(Collection<String> packageNames) { public void allowApps(Collection<String> packageNames) {
cachedApps = null;
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet()); Collections.emptySet());
Set<String> merged = new HashSet<>(allowed); Set<String> merged = new HashSet<>(allowed);
@@ -121,12 +153,11 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
if (SDK_INT >= 16 && SDK_INT < 23) { if (SDK_INT >= 16 && SDK_INT < 23) {
// Check whether the permission has been requested and granted // Check whether the permission has been requested and granted
int[] flags = packageInfo.requestedPermissionsFlags; int[] flags = packageInfo.requestedPermissionsFlags;
if (flags == null || flags.length != requestedPermissions.length)
throw new AssertionError();
for (int i = 0; i < requestedPermissions.length; i++) { for (int i = 0; i < requestedPermissions.length; i++) {
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW) if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) {
&& (flags[i] & REQUESTED_PERMISSION_GRANTED) != 0) { // 'flags' may be null on Robolectric
return true; return flags == null ||
(flags[i] & REQUESTED_PERMISSION_GRANTED) != 0;
} }
} }
} else { } else {
@@ -163,4 +194,36 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
return false; 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

@@ -49,7 +49,10 @@ public abstract class BaseActivity extends AppCompatActivity
private final List<ActivityLifecycleController> lifecycleControllers = private final List<ActivityLifecycleController> lifecycleControllers =
new ArrayList<>(); new ArrayList<>();
private boolean destroyed = false; private boolean destroyed = false;
private ScreenFilterDialogFragment dialogFrag; private ScreenFilterDialogFragment dialogFrag;
private Toolbar toolbar = null;
private boolean searchedForToolbar = false;
public abstract void injectActivity(ActivityComponent component); public abstract void injectActivity(ActivityComponent component);
@@ -108,6 +111,12 @@ public abstract class BaseActivity extends AppCompatActivity
} }
} }
@Override
protected void onResume() {
super.onResume();
protectToolbar();
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
@@ -137,13 +146,18 @@ public abstract class BaseActivity extends AppCompatActivity
// If the dialog is already visible, filter the tap // If the dialog is already visible, filter the tap
if (dialogFrag != null && dialogFrag.isVisible()) return false; if (dialogFrag != null && dialogFrag.isVisible()) return false;
Collection<AppDetails> apps = screenFilterMonitor.getApps(); Collection<AppDetails> apps = screenFilterMonitor.getApps();
// If all overlay apps are allowed or system apps, allow the tap // If there are no overlay apps that haven't been allowed, allow the tap
if (apps.isEmpty()) return true; if (apps.isEmpty()) return true;
// Create dialog
dialogFrag = ScreenFilterDialogFragment.newInstance(apps); dialogFrag = ScreenFilterDialogFragment.newInstance(apps);
dialogFrag.setCancelable(false); dialogFrag.setCancelable(false);
// Show dialog unless onSaveInstanceState() has been called, see #1112 // Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag()); if (!fm.isStateSaved()) {
// When dialog is dismissed, update protection of toolbar
dialogFrag.setDismissListener(this::protectToolbar);
dialogFrag.show(fm, dialogFrag.getTag());
}
// Filter the tap // Filter the tap
return false; return false;
} }
@@ -199,16 +213,21 @@ public abstract class BaseActivity extends AppCompatActivity
* is outside the wrapper. * is outside the wrapper.
*/ */
private void protectToolbar() { private void protectToolbar() {
View decorView = getWindow().getDecorView(); findToolbar();
if (decorView instanceof ViewGroup) { if (toolbar != null) {
Toolbar toolbar = findToolbar((ViewGroup) decorView); boolean filter = !screenFilterMonitor.getApps().isEmpty();
if (toolbar != null) { toolbar.setFilterTouchesWhenObscured(filter);
boolean filter = !screenFilterMonitor.getApps().isEmpty();
toolbar.setFilterTouchesWhenObscured(filter);
}
} }
} }
private void findToolbar() {
if (searchedForToolbar) return;
View decorView = getWindow().getDecorView();
if (decorView instanceof ViewGroup)
toolbar = findToolbar((ViewGroup) decorView);
searchedForToolbar = true;
}
@Nullable @Nullable
private Toolbar findToolbar(ViewGroup vg) { private Toolbar findToolbar(ViewGroup vg) {
for (int i = 0, len = vg.getChildCount(); i < len; i++) { for (int i = 0, len = vg.getChildCount(); i < len; i++) {
@@ -230,19 +249,16 @@ public abstract class BaseActivity extends AppCompatActivity
@Override @Override
public void setContentView(View v) { public void setContentView(View v) {
super.setContentView(makeTapSafeWrapper(v)); super.setContentView(makeTapSafeWrapper(v));
protectToolbar();
} }
@Override @Override
public void setContentView(View v, LayoutParams layoutParams) { public void setContentView(View v, LayoutParams layoutParams) {
super.setContentView(makeTapSafeWrapper(v), layoutParams); super.setContentView(makeTapSafeWrapper(v), layoutParams);
protectToolbar();
} }
@Override @Override
public void addContentView(View v, LayoutParams layoutParams) { public void addContentView(View v, LayoutParams layoutParams) {
super.addContentView(makeTapSafeWrapper(v), layoutParams); super.addContentView(makeTapSafeWrapper(v), layoutParams);
protectToolbar();
} }
@Override @Override

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.fragment;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
@@ -32,6 +33,8 @@ public class ScreenFilterDialogFragment extends DialogFragment {
@Inject @Inject
ScreenFilterMonitor screenFilterMonitor; ScreenFilterMonitor screenFilterMonitor;
DismissListener dismissListener = null;
public static ScreenFilterDialogFragment newInstance( public static ScreenFilterDialogFragment newInstance(
Collection<AppDetails> apps) { Collection<AppDetails> apps) {
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment(); ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
@@ -46,6 +49,10 @@ public class ScreenFilterDialogFragment extends DialogFragment {
return frag; return frag;
} }
public void setDismissListener(DismissListener dismissListener) {
this.dismissListener = dismissListener;
}
@Override @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@@ -83,4 +90,14 @@ public class ScreenFilterDialogFragment extends DialogFragment {
}); });
return builder.create(); return builder.create();
} }
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (dismissListener != null) dismissListener.onDialogDismissed();
}
public interface DismissListener {
void onDialogDismissed();
}
} }