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; package org.briarproject.briar.android;
import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
@@ -89,6 +91,8 @@ public interface AndroidComponent
AndroidNotificationManager androidNotificationManager(); AndroidNotificationManager androidNotificationManager();
SharedPreferences sharedPreferences();
ScreenFilterMonitor screenFilterMonitor(); ScreenFilterMonitor screenFilterMonitor();
ConnectionRegistry connectionRegistry(); ConnectionRegistry connectionRegistry();

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -157,6 +158,11 @@ public class AppModule {
return devConfig; return devConfig;
} }
@Provides
SharedPreferences provideSharedPreferences(Application app) {
return app.getSharedPreferences("db", MODE_PRIVATE);
}
@Provides @Provides
@Singleton @Singleton
ReferenceManager provideReferenceManager() { ReferenceManager provideReferenceManager() {
@@ -174,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

@@ -1,13 +1,22 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
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.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; 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;
@@ -16,23 +25,33 @@ import java.io.InputStream;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; 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.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; 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.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_SIGNATURES; import static android.content.pm.PackageManager.GET_SIGNATURES;
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());
@@ -56,54 +75,93 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" + "82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
"0B145B6AA192858E79020103"; "0B145B6AA192858E79020103";
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 AtomicBoolean used = new AtomicBoolean(false);
// UiThread
@Nullable
private BroadcastReceiver receiver = null;
// UiThread
@Nullable
private Collection<AppDetails> cachedApps = null;
@Inject @Inject
ScreenFilterMonitorImpl(Application app) { ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
SharedPreferences prefs) {
pm = app.getPackageManager(); pm = app.getPackageManager();
this.app = app;
this.androidExecutor = androidExecutor;
this.prefs = prefs;
} }
@Override @Override
@UiThread @UiThread
public Set<String> getApps() { public Collection<AppDetails> getApps() {
Set<String> screenFilterApps = new TreeSet<>(); if (cachedApps != null) return cachedApps;
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet());
List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packageInfos = List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS); pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) { for (PackageInfo packageInfo : packageInfos) {
if (isOverlayApp(packageInfo)) { if (!allowed.contains(packageInfo.packageName)
String name = pkgToString(packageInfo); && isOverlayApp(packageInfo)) {
if (name != null) { String name = getAppName(packageInfo);
screenFilterApps.add(name); 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. @Override
@Nullable @UiThread
private String pkgToString(PackageInfo pkgInfo) { 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); CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
if (seq != null) { return seq == null ? pkgInfo.packageName : seq.toString();
return seq.toString();
}
return null;
} }
// Checks if an installed package is a user app using the permission. // Checks if an installed package is a user app using the permission.
private boolean isOverlayApp(PackageInfo packageInfo) { private boolean isOverlayApp(PackageInfo packageInfo) {
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP; int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
// Ignore system apps // Ignore system apps
if ((packageInfo.applicationInfo.flags & mask) != 0) { if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
return false;
}
// Ignore Play Services, it's effectively a system app // Ignore Play Services, it's effectively a system app
if (isPlayServices(packageInfo.packageName)) { if (isPlayServices(packageInfo.packageName)) return false;
return false;
}
// Get permissions // Get permissions
String[] requestedPermissions = packageInfo.requestedPermissions; 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) { for (String requestedPermission : requestedPermissions) {
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) { if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
return true; return true;
@@ -113,6 +171,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
return false; return false;
} }
@SuppressLint("PackageManagerGetSignatures")
private boolean isPlayServices(String pkg) { private boolean isPlayServices(String pkg) {
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false; if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
try { try {
@@ -135,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

@@ -21,6 +21,7 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment; import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.forum.ForumModule; 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.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment; import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
@@ -152,7 +153,9 @@ public interface ActivityComponent {
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
void inject(PasswordFragment fragment); void inject(PasswordFragment fragment);
void inject(DozeFragment fragment); void inject(DozeFragment fragment);
void inject(ContactListFragment fragment); void inject(ContactListFragment fragment);
@@ -189,4 +192,5 @@ public interface ActivityComponent {
void inject(SettingsFragment fragment); void inject(SettingsFragment fragment);
void inject(ScreenFilterDialogFragment fragment);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.activity; package org.briarproject.briar.android.activity;
import android.app.Activity; import android.app.Activity;
import android.content.SharedPreferences;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
@@ -19,7 +18,6 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static android.content.Context.MODE_PRIVATE;
import static org.briarproject.briar.android.BriarService.BriarServiceConnection; import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
@Module @Module
@@ -57,12 +55,6 @@ public class ActivityModule {
return configController; return configController;
} }
@ActivityScope
@Provides
SharedPreferences provideSharedPreferences(Activity activity) {
return activity.getSharedPreferences("db", MODE_PRIVATE);
}
@ActivityScope @ActivityScope
@Provides @Provides
PasswordController providePasswordController( PasswordController providePasswordController(

View File

@@ -4,6 +4,7 @@ import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; 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.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; 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;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener; import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@@ -48,7 +51,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;
@Nullable
private Toolbar toolbar = null;
private boolean searchedForToolbar = false;
public abstract void injectActivity(ActivityComponent component); public abstract void injectActivity(ActivityComponent component);
@@ -57,8 +63,8 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle state) {
super.onCreate(savedInstanceState); super.onCreate(state);
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
@@ -97,6 +103,16 @@ public abstract class BaseActivity extends AppCompatActivity
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityStart(); 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 @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) { protected void showInitialFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag()) .replace(R.id.fragmentContainer, f, f.getUniqueTag())
@@ -132,16 +139,27 @@ public abstract class BaseActivity extends AppCompatActivity
.commit(); .commit();
} }
private void showScreenFilterWarning() { private boolean showScreenFilterWarning() {
if (dialogFrag != null && dialogFrag.isVisible()) return; // If the dialog is already visible, filter the tap
Set<String> apps = screenFilterMonitor.getApps(); ScreenFilterDialogFragment f = findDialogFragment();
if (apps.isEmpty()) return; if (f != null && f.isVisible()) return false;
dialogFrag = Collection<AppDetails> apps = screenFilterMonitor.getApps();
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps)); // If all overlay apps have been allowed, allow the tap
dialogFrag.setCancelable(false); if (apps.isEmpty()) return true;
// 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()) {
// 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 @Override
@@ -195,15 +213,25 @@ 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(true); 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 @Nullable
private Toolbar findToolbar(ViewGroup vg) { 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++) { for (int i = 0, len = vg.getChildCount(); i < len; i++) {
View child = vg.getChildAt(i); View child = vg.getChildAt(i);
if (child instanceof Toolbar) return (Toolbar) child; if (child instanceof Toolbar) return (Toolbar) child;
@@ -223,23 +251,20 @@ 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
public void onTapFiltered() { public boolean shouldAllowTap() {
showScreenFilterWarning(); return showScreenFilterWarning();
} }
} }

View File

@@ -80,7 +80,7 @@ public abstract class BriarActivity extends BaseActivity {
public void setSceneTransitionAnimation() { public void setSceneTransitionAnimation() {
if (SDK_INT < 21) return; if (SDK_INT < 21) return;
// workaround for #1007 // workaround for #1007
if (isSamsung7(this)) { if (isSamsung7()) {
return; return;
} }
Transition slide = new Slide(Gravity.RIGHT); 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(GROUP_ID, item.getGroupId().getBytes());
i.putExtra(POST_ID, item.getId().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 = ActivityOptionsCompat options =
makeSceneTransitionAnimation((Activity) ctx, layout, makeSceneTransitionAnimation((Activity) ctx, layout,
getTransitionName(item.getId())); getTransitionName(item.getId()));

View File

@@ -1,40 +1,106 @@
package org.briarproject.briar.android.fragment; package org.briarproject.briar.android.fragment;
import android.annotation.SuppressLint;
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;
import android.text.TextUtils; 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.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.ArrayList;
import java.util.Collection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject;
@NotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ScreenFilterDialogFragment extends DialogFragment { public class ScreenFilterDialogFragment extends DialogFragment {
public static final String TAG = ScreenFilterDialogFragment.class.getName();
@Inject
ScreenFilterMonitor screenFilterMonitor;
DismissListener dismissListener = null;
public static ScreenFilterDialogFragment newInstance( public static ScreenFilterDialogFragment newInstance(
ArrayList<String> apps) { Collection<AppDetails> apps) {
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment(); ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
Bundle args = new Bundle(); 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); frag.setArguments(args);
return frag; 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 @Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 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); R.style.BriarDialogThemeNoFilter);
builder.setTitle(R.string.screen_filter_title); builder.setTitle(R.string.screen_filter_title);
ArrayList<String> apps = getArguments().getStringArrayList("apps"); Bundle args = getArguments();
builder.setMessage(getString(R.string.screen_filter_body, if (args == null) throw new IllegalStateException();
TextUtils.join("\n", apps))); ArrayList<String> appNames = args.getStringArrayList("appNames");
builder.setNeutralButton(R.string.continue_button, ArrayList<String> packageNames =
(dialog, which) -> dialog.dismiss()); 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(); 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.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
@@ -162,7 +161,7 @@ public class UiUtils {
} }
public static boolean needsDozeWhitelisting(Context ctx) { 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); PowerManager pm = (PowerManager) ctx.getSystemService(POWER_SERVICE);
String packageName = ctx.getPackageName(); String packageName = ctx.getPackageName();
if (pm == null) throw new AssertionError(); if (pm == null) throw new AssertionError();
@@ -178,8 +177,15 @@ public class UiUtils {
return i; return i;
} }
public static boolean isSamsung7(Context context) { public static boolean isSamsung7() {
return SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung"); 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 android.widget.FrameLayout;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.util.UiUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -20,18 +21,18 @@ public class TapSafeFrameLayout extends FrameLayout {
public TapSafeFrameLayout(Context context) { public TapSafeFrameLayout(Context context) {
super(context); super(context);
setFilterTouchesWhenObscured(false); UiUtils.setFilterTouchesWhenObscured(this, false);
} }
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) { public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setFilterTouchesWhenObscured(false); UiUtils.setFilterTouchesWhenObscured(this, false);
} }
public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs, public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) { @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
setFilterTouchesWhenObscured(false); UiUtils.setFilterTouchesWhenObscured(this, false);
} }
public void setOnTapFilteredListener(OnTapFilteredListener listener) { public void setOnTapFilteredListener(OnTapFilteredListener listener) {
@@ -40,12 +41,12 @@ public class TapSafeFrameLayout extends FrameLayout {
@Override @Override
public boolean onFilterTouchEventForSecurity(MotionEvent e) { public boolean onFilterTouchEventForSecurity(MotionEvent e) {
boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0; boolean obscured = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
if (filter && listener != null) listener.onTapFiltered(); if (obscured && listener != null) return listener.shouldAllowTap();
return !filter; else return !obscured;
} }
public interface OnTapFilteredListener { 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 android.support.annotation.UiThread;
import java.util.Set; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
@NotNullByDefault
public interface ScreenFilterMonitor { 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 @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;
}
}
} }

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/margin_large">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
android:id="@+id/screen_filter_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
<CheckBox
android:id="@+id/screen_filter_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/screen_filter_allow"/>
</LinearLayout>

View File

@@ -396,7 +396,8 @@
<!-- Screen Filters & Tapjacking --> <!-- Screen Filters & Tapjacking -->
<string name="screen_filter_title">Screen overlay detected</string> <string name="screen_filter_title">Screen overlay detected</string>
<string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nTry turning off the following apps when using Briar:\n\n%1$s</string> <string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nThe following apps might be drawing on top:\n\n%1$s</string>
<string name="screen_filter_allow">Allow these apps to draw on top</string>
<!-- Permission Requests --> <!-- Permission Requests -->
<string name="permission_camera_title">Camera permission</string> <string name="permission_camera_title">Camera permission</string>