mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Merge branch '1143-screen-overlay-dialog' into 'maintenance-0.16'
Backport: Don't show screen overlay dialog if all overlay apps have been allowed See merge request akwizgran/briar!683
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
briar-android/src/main/res/layout/dialog_screen_filter.xml
Normal file
28
briar-android/src/main/res/layout/dialog_screen_filter.xml
Normal 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>
|
||||
@@ -395,7 +395,8 @@
|
||||
|
||||
<!-- Screen Filters & Tapjacking -->
|
||||
<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 -->
|
||||
<string name="permission_camera_title">Camera permission</string>
|
||||
|
||||
Reference in New Issue
Block a user