mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
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:
@@ -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() {
|
||||
@@ -174,8 +180,11 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
LifecycleManager lifecycleManager,
|
||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||
lifecycleManager.registerService(screenFilterMonitor);
|
||||
return screenFilterMonitor;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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