diff --git a/bramble-android/build.gradle b/bramble-android/build.gradle index cc6786f48..5ca4b8a51 100644 --- a/bramble-android/build.gradle +++ b/bramble-android/build.gradle @@ -12,7 +12,7 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 10504 versionName "1.5.4" consumerProguardFiles 'proguard-rules.txt' diff --git a/bramble-android/src/main/java/org/briarproject/bramble/battery/AndroidBatteryManager.java b/bramble-android/src/main/java/org/briarproject/bramble/battery/AndroidBatteryManager.java index 0bd13b0b2..df659e625 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/battery/AndroidBatteryManager.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/battery/AndroidBatteryManager.java @@ -5,6 +5,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.PowerManager; import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.event.BatteryEvent; @@ -16,10 +17,17 @@ import java.util.logging.Logger; import javax.inject.Inject; +import androidx.annotation.RequiresApi; + import static android.content.Intent.ACTION_BATTERY_CHANGED; import static android.content.Intent.ACTION_POWER_CONNECTED; import static android.content.Intent.ACTION_POWER_DISCONNECTED; import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED; +import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; @@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_POWER_CONNECTED); filter.addAction(ACTION_POWER_DISCONNECTED); + filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED); + if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); + if (SDK_INT >= 33) { + filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); + } appContext.registerReceiver(batteryReceiver, filter); } @@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service { eventBus.broadcast(new BatteryEvent(true)); else if (ACTION_POWER_DISCONNECTED.equals(action)) eventBus.broadcast(new BatteryEvent(false)); + else if (SDK_INT >= 23 && + ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) && + LOG.isLoggable(INFO)) { + LOG.info("Device idle mode changed to: " + + getPowerManager(ctx).isDeviceIdleMode()); + } else if (SDK_INT >= 23 && + ACTION_POWER_SAVE_MODE_CHANGED.equals(action) && + LOG.isLoggable(INFO)) { + LOG.info("Power save mode changed to: " + + getPowerManager(ctx).isPowerSaveMode()); + } else if (SDK_INT >= 33 && LOG.isLoggable(INFO) && + ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) { + PowerManager powerManager = + ctx.getSystemService(PowerManager.class); + LOG.info("Low power standby now is: " + + powerManager.isLowPowerStandbyEnabled()); + } else if (SDK_INT >= 33 && LOG.isLoggable(INFO) && + ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) { + PowerManager powerManager = getPowerManager(ctx); + LOG.info("Light idle mode now is: " + + powerManager.isDeviceLightIdleMode()); + } } } + + @RequiresApi(api = 23) + private PowerManager getPowerManager(Context ctx) { + return ctx.getSystemService(PowerManager.class); + } } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java index 8705f6673..e18e4c6ea 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java @@ -63,10 +63,12 @@ public class AndroidUtils { return new Pair<>(address, "adapter"); } // Return the address from settings if it's valid and not fake - address = Settings.Secure.getString(ctx.getContentResolver(), - "bluetooth_address"); - if (isValidBluetoothAddress(address)) { - return new Pair<>(address, "settings"); + if (SDK_INT < 33) { + address = Settings.Secure.getString(ctx.getContentResolver(), + "bluetooth_address"); + if (isValidBluetoothAddress(address)) { + return new Pair<>(address, "settings"); + } } // Try to get the address via reflection address = getBluetoothAddressByReflection(adapter); diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 88dcd1b2b..fa172c88e 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -25,7 +25,7 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 10504 versionName "1.5.4" applicationId "org.briarproject.briar.android" diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index fd765c9f4..04fa06497 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -19,6 +19,10 @@ + @@ -30,8 +34,10 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" tools:ignore="ScopedStorage" /> + - + diff --git a/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java index e6cf8e227..01e406d82 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/DozeWatchdogImpl.java @@ -7,17 +7,26 @@ import android.content.IntentFilter; import android.os.PowerManager; import org.briarproject.bramble.api.lifecycle.Service; -import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.briar.api.android.DozeWatchdog; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import androidx.annotation.RequiresApi; import static android.content.Context.POWER_SERVICE; import static android.os.Build.VERSION.SDK_INT; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; +import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; class DozeWatchdogImpl implements DozeWatchdog, Service { + private static final Logger LOG = + getLogger(DozeWatchdogImpl.class.getName()); + private final Context appContext; private final AtomicBoolean dozed = new AtomicBoolean(false); private final BroadcastReceiver receiver = new DozeBroadcastReceiver(); @@ -32,14 +41,18 @@ class DozeWatchdogImpl implements DozeWatchdog, Service { } @Override - public void startService() throws ServiceException { + public void startService() { if (SDK_INT < 23) return; IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); + if (SDK_INT >= 33) { + filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); + filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + } appContext.registerReceiver(receiver, filter); } @Override - public void stopService() throws ServiceException { + public void stopService() { if (SDK_INT < 23) return; appContext.unregisterReceiver(receiver); } @@ -49,9 +62,33 @@ class DozeWatchdogImpl implements DozeWatchdog, Service { @Override public void onReceive(Context context, Intent intent) { if (SDK_INT < 23) return; + String action = intent.getAction(); PowerManager pm = (PowerManager) appContext.getSystemService(POWER_SERVICE); - if (pm.isDeviceIdleMode()) dozed.set(true); + if (ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) { + if (pm.isDeviceIdleMode()) dozed.set(true); + } else if (SDK_INT >= 33) { + onReceive33(action, pm); + } + } + + @RequiresApi(33) + private void onReceive33(String action, PowerManager pm) { + if (ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) { + if (pm.isLowPowerStandbyEnabled()) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("System is in low power standby mode"); + } + dozed.set(true); + } + } else if (ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) { + if (pm.isDeviceLightIdleMode()) { + if (LOG.isLoggable(WARNING)) { + LOG.warning("System is in light idle mode"); + } + dozed.set(true); + } + } } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java index 1c22e8187..64c313941 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/DozeFragment.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.account; import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -8,6 +9,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ProgressBar; +import android.widget.Toast; import org.briarproject.briar.R; import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener; @@ -18,6 +20,7 @@ import androidx.annotation.Nullable; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; +import static android.widget.Toast.LENGTH_LONG; import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; @@ -113,7 +116,12 @@ public class DozeFragment extends SetupFragment private void askForDozeWhitelisting() { if (getContext() == null) return; Intent i = getDozeWhitelistingIntent(getContext()); - startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + try { + startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + } catch (ActivityNotFoundException e) { + Toast.makeText(requireContext(), + R.string.error_start_activity, LENGTH_LONG).show(); + } } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiProtectedAppsView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiProtectedAppsView.java index ec4b442b8..31a3fc79f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiProtectedAppsView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/HuaweiProtectedAppsView.java @@ -14,6 +14,7 @@ import androidx.annotation.UiThread; import static org.briarproject.android.dontkillmelib.HuaweiUtils.getHuaweiProtectedAppsIntent; import static org.briarproject.android.dontkillmelib.HuaweiUtils.protectedAppsNeedsToBeShown; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @UiThread @NotNullByDefault @@ -49,7 +50,7 @@ class HuaweiProtectedAppsView extends PowerView { @Override protected void onButtonClick() { - getContext().startActivity(getHuaweiProtectedAppsIntent()); + tryToStartActivity(getContext(), getHuaweiProtectedAppsIntent()); setChecked(true); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java index 9d0047570..601cb59ff 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetPasswordFragment.java @@ -19,10 +19,17 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault; import javax.annotation.Nullable; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; + +import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.content.Context.INPUT_METHOD_SERVICE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; +import static androidx.core.content.ContextCompat.checkSelfPermission; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.briar.android.util.UiUtils.setError; @@ -38,6 +45,10 @@ public class SetPasswordFragment extends SetupFragment { private StrengthMeter strengthMeter; private Button nextButton; + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new RequestPermission(), isGranted -> + setPassword()); + public static SetPasswordFragment newInstance() { return new SetPasswordFragment(); } @@ -121,6 +132,18 @@ public class SetPasswordFragment extends SetupFragment { IBinder token = passwordEntry.getWindowToken(); Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0); + if (SDK_INT >= 33 && + checkSelfPermission(requireContext(), POST_NOTIFICATIONS) != + PERMISSION_GRANTED) { + // this calls setPassword() when it returns + requestPermissionLauncher.launch(POST_NOTIFICATIONS); + } else { + setPassword(); + } + } + + private void setPassword() { viewModel.setPassword(passwordEntry.getText().toString()); } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java index 55d04c238..c53bf9352 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/SetupActivity.java @@ -36,7 +36,7 @@ public class SetupActivity extends BaseActivity @Inject ViewModelProvider.Factory viewModelFactory; - SetupViewModel viewModel; + private SetupViewModel viewModel; @Override public void injectActivity(ActivityComponent component) { @@ -71,16 +71,16 @@ public class SetupActivity extends BaseActivity } } - void showPasswordFragment() { + private void showPasswordFragment() { showNextFragment(SetPasswordFragment.newInstance()); } @TargetApi(23) - void showDozeFragment() { + private void showDozeFragment() { showNextFragment(DozeFragment.newInstance()); } - void showApp() { + private void showApp() { Intent i = new Intent(this, ENTRY_ACTIVITY); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME | FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java index 65c357949..297b22469 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/UnlockActivity.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.account; import android.app.KeyguardManager; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; @@ -28,6 +29,7 @@ import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_CANCEL import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED; import static android.os.Build.VERSION.SDK_INT; import static android.view.View.INVISIBLE; +import static android.widget.Toast.LENGTH_LONG; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK; import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock; import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint; @@ -191,7 +193,12 @@ public class UnlockActivity extends BaseActivity { unlock(); } else { keyguardShown = true; - startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK); + try { + startActivityForResult(intent, REQUEST_KEYGUARD_UNLOCK); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.error_start_activity, LENGTH_LONG) + .show(); + } overridePendingTransition(0, 0); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java index d2af11a90..1b268dd89 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/XiaomiLockAppsView.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.account; +import android.content.ActivityNotFoundException; import android.content.Context; import android.util.AttributeSet; import android.widget.Toast; @@ -60,12 +61,12 @@ class XiaomiLockAppsView extends PowerView { getContext().startActivity(getXiaomiLockAppsIntent()); setChecked(true); return; - } catch (SecurityException e) { + } catch (SecurityException | ActivityNotFoundException e) { logException(LOG, WARNING, e); + Toast.makeText(getContext(), + R.string.dnkm_xiaomi_lock_apps_error_toast, + LENGTH_LONG).show(); } - Toast.makeText(getContext(), - R.string.dnkm_xiaomi_lock_apps_error_toast, - LENGTH_LONG).show(); // Let the user continue with setup setChecked(true); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index 1370c5f9c..ed349038b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -1,9 +1,11 @@ package org.briarproject.briar.android.activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.transition.Transition; import android.view.Window; import android.widget.CheckBox; +import android.widget.Toast; import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager; import org.briarproject.bramble.api.system.Wakeful; @@ -34,9 +36,12 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.os.Build.VERSION.SDK_INT; +import static android.widget.Toast.LENGTH_LONG; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.android.dontkillmelib.DozeUtils.getDozeWhitelistingIntent; +import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK; @@ -179,7 +184,13 @@ public abstract class BriarActivity extends BaseActivity { b.setPositiveButton(R.string.fix, (dialog, which) -> { Intent i = getDozeWhitelistingIntent(BriarActivity.this); - startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + try { + startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + } catch (ActivityNotFoundException e) { + logException(LOG, WARNING, e); + Toast.makeText(this, R.string.error_start_activity, + LENGTH_LONG).show(); + } dialog.dismiss(); }); b.setNegativeButton(R.string.cancel, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java index 923f0f775..2e2ed6bf2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ScreenFilterDialogFragment.java @@ -19,6 +19,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails; import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -32,6 +33,7 @@ import androidx.fragment.app.DialogFragment; import static android.os.Build.VERSION.SDK_INT; import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION; import static android.view.View.GONE; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -68,6 +70,7 @@ public class ScreenFilterDialogFragment extends DialogFragment { ((BaseActivity) requireActivity()).getActivityComponent().inject(this); } + @NotNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { Activity activity = getActivity(); @@ -98,7 +101,7 @@ public class ScreenFilterDialogFragment extends DialogFragment { builder.setNeutralButton(R.string.screen_filter_review_apps, (dialog, which) -> { Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION); - startActivity(i); + tryToStartActivity(requireActivity(), i); }); } builder.setPositiveButton(R.string.continue_button, (dialog, which) -> { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java index edfef7303..435a486a8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/AbstractConditionManager.java @@ -1,21 +1,27 @@ package org.briarproject.briar.android.hotspot; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; import android.net.wifi.WifiManager; +import android.widget.Toast; import org.briarproject.briar.R; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.core.util.Consumer; import androidx.fragment.app.FragmentActivity; import static android.content.Context.WIFI_SERVICE; +import static android.widget.Toast.LENGTH_LONG; /** * Abstract base class for the ConditionManagers that ensure that the conditions * to open a hotspot are fulfilled. There are different extensions of this for - * API levels lower than 29 and 29+. + * API levels lower than 29, 29+ and 33+. */ abstract class AbstractConditionManager { @@ -28,6 +34,7 @@ abstract class AbstractConditionManager { final Consumer permissionUpdateCallback; protected FragmentActivity ctx; WifiManager wifiManager; + private ActivityResultLauncher wifiRequest; AbstractConditionManager(Consumer permissionUpdateCallback) { this.permissionUpdateCallback = permissionUpdateCallback; @@ -38,8 +45,12 @@ abstract class AbstractConditionManager { */ void init(FragmentActivity ctx) { this.ctx = ctx; - this.wifiManager = (WifiManager) ctx.getApplicationContext() + wifiManager = (WifiManager) ctx.getApplicationContext() .getSystemService(WIFI_SERVICE); + wifiRequest = ctx.registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> permissionUpdateCallback + .accept(wifiManager.isWifiEnabled())); } /** @@ -57,6 +68,8 @@ abstract class AbstractConditionManager { */ abstract boolean checkAndRequestConditions(); + abstract String getWifiSettingsAction(); + void showRationale(Context ctx, @StringRes int title, @StringRes int body, Runnable onContinueClicked, Runnable onDismiss) { @@ -69,4 +82,13 @@ abstract class AbstractConditionManager { builder.show(); } + void requestEnableWiFi() { + try { + wifiRequest.launch(new Intent(getWifiSettingsAction())); + } catch (ActivityNotFoundException e) { + Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG) + .show(); + } + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager.java index 95c0d57fe..3364251c6 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager.java @@ -1,15 +1,12 @@ package org.briarproject.briar.android.hotspot; -import android.content.Intent; import android.provider.Settings; import org.briarproject.briar.R; +import org.briarproject.nullsafety.NotNullByDefault; import java.util.logging.Logger; -import androidx.activity.result.ActivityResultCaller; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.core.util.Consumer; import static java.util.logging.Level.INFO; @@ -22,20 +19,14 @@ import static java.util.logging.Logger.getLogger; * As soon as {@link #checkAndRequestConditions()} returns true, * all conditions are fulfilled. */ +@NotNullByDefault class ConditionManager extends AbstractConditionManager { private static final Logger LOG = getLogger(ConditionManager.class.getName()); - private final ActivityResultLauncher wifiRequest; - - ConditionManager(ActivityResultCaller arc, - Consumer permissionUpdateCallback) { - super(permissionUpdateCallback); - wifiRequest = arc.registerForActivityResult( - new StartActivityForResult(), - result -> permissionUpdateCallback - .accept(wifiManager.isWifiEnabled())); + ConditionManager(Consumer permissionUpdateCallback) { + super( permissionUpdateCallback); } @Override @@ -76,8 +67,9 @@ class ConditionManager extends AbstractConditionManager { return false; } - private void requestEnableWiFi() { - wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS)); + @Override + String getWifiSettingsAction() { + return Settings.ACTION_WIFI_SETTINGS; } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java index 64e399605..547a44e92 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager29.java @@ -1,18 +1,17 @@ package org.briarproject.briar.android.hotspot; -import android.content.Intent; import android.provider.Settings; import org.briarproject.briar.R; import org.briarproject.briar.android.util.Permission; import org.briarproject.briar.android.util.PermissionUtils; +import org.briarproject.nullsafety.NotNullByDefault; import java.util.logging.Logger; import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.util.Consumer; @@ -28,12 +27,13 @@ import static org.briarproject.briar.android.util.PermissionUtils.showLocationDi /** * This class ensures that the conditions to open a hotspot are fulfilled on - * API levels >= 29. + * API levels >= 29 and < 33. *

* As soon as {@link #checkAndRequestConditions()} returns true, * all conditions are fulfilled. */ @RequiresApi(29) +@NotNullByDefault class ConditionManager29 extends AbstractConditionManager { private static final Logger LOG = @@ -42,7 +42,6 @@ class ConditionManager29 extends AbstractConditionManager { private Permission locationPermission = Permission.UNKNOWN; private final ActivityResultLauncher locationRequest; - private final ActivityResultLauncher wifiRequest; ConditionManager29(ActivityResultCaller arc, Consumer permissionUpdateCallback) { @@ -53,11 +52,6 @@ class ConditionManager29 extends AbstractConditionManager { onRequestPermissionResult(granted); permissionUpdateCallback.accept(TRUE.equals(granted)); }); - wifiRequest = arc.registerForActivityResult( - new StartActivityForResult(), - result -> permissionUpdateCallback - .accept(wifiManager.isWifiEnabled()) - ); } @Override @@ -131,6 +125,11 @@ class ConditionManager29 extends AbstractConditionManager { return false; } + @Override + String getWifiSettingsAction() { + return Settings.Panel.ACTION_WIFI; + } + private void onRequestPermissionResult(@Nullable Boolean granted) { if (granted != null && granted) { locationPermission = Permission.GRANTED; @@ -146,8 +145,4 @@ class ConditionManager29 extends AbstractConditionManager { locationRequest.launch(ACCESS_FINE_LOCATION); } - private void requestEnableWiFi() { - wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI)); - } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager33.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager33.java new file mode 100644 index 000000000..89cf62e2b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/ConditionManager33.java @@ -0,0 +1,134 @@ +package org.briarproject.briar.android.hotspot; + +import android.provider.Settings; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.util.Permission; +import org.briarproject.briar.android.util.PermissionUtils; +import org.briarproject.nullsafety.NotNullByDefault; + +import java.util.logging.Logger; + +import androidx.activity.result.ActivityResultCaller; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.util.Consumer; + +import static android.Manifest.permission.NEARBY_WIFI_DEVICES; +import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; +import static java.lang.Boolean.TRUE; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; + +/** + * This class ensures that the conditions to open a hotspot are fulfilled on + * API levels >= 33. + *

+ * As soon as {@link #checkAndRequestConditions()} returns true, + * all conditions are fulfilled. + */ +@RequiresApi(33) +@NotNullByDefault +class ConditionManager33 extends AbstractConditionManager { + + private static final Logger LOG = + getLogger(ConditionManager33.class.getName()); + + private Permission nearbyWifiPermission = Permission.UNKNOWN; + + private final ActivityResultLauncher nearbyWifiRequest; + + ConditionManager33(ActivityResultCaller arc, + Consumer permissionUpdateCallback) { + super(permissionUpdateCallback); + // permissionUpdateCallback receives false if permissions were denied + nearbyWifiRequest = arc.registerForActivityResult( + new RequestPermission(), granted -> { + onRequestPermissionResult(granted); + permissionUpdateCallback.accept(TRUE.equals(granted)); + }); + } + + @Override + void onStart() { + nearbyWifiPermission = Permission.UNKNOWN; + } + + private boolean areEssentialPermissionsGranted() { + boolean isWifiEnabled = wifiManager.isWifiEnabled(); + if (LOG.isLoggable(INFO)) { + LOG.info(String.format("areEssentialPermissionsGranted(): " + + "nearbyWifiPermission? %s, " + + "wifiManager.isWifiEnabled()? %b", + nearbyWifiPermission, isWifiEnabled)); + } + return nearbyWifiPermission == Permission.GRANTED && isWifiEnabled; + } + + @Override + boolean checkAndRequestConditions() { + if (areEssentialPermissionsGranted()) return true; + + if (nearbyWifiPermission == Permission.UNKNOWN) { + requestPermissions(); + return false; + } + + // If the location permission has been permanently denied, ask the + // user to change the setting + if (nearbyWifiPermission == Permission.PERMANENTLY_DENIED) { + PermissionUtils.showDenialDialog(ctx, + R.string.permission_nearby_devices_title, + R.string.permission_hotspot_nearby_wifi_denied_body, + () -> permissionUpdateCallback.accept(false)); + return false; + } + + // Should we show the rationale for location permission? + if (nearbyWifiPermission == Permission.SHOW_RATIONALE) { + showRationale(ctx, + R.string.permission_location_title, + R.string.permission_hotspot_nearby_wifi_request_body, + this::requestPermissions, + () -> permissionUpdateCallback.accept(false)); + return false; + } + + // If Wifi is not enabled, we show the rationale for enabling Wifi? + if (!wifiManager.isWifiEnabled()) { + showRationale(ctx, R.string.wifi_settings_title, + R.string.wifi_settings_request_enable_body, + this::requestEnableWiFi, + () -> permissionUpdateCallback.accept(false)); + return false; + } + + // we shouldn't usually reach this point, but if we do, return false + // anyway to force a recheck. Maybe some condition changed in the + // meantime. + return false; + } + + @Override + String getWifiSettingsAction() { + return Settings.Panel.ACTION_WIFI; + } + + private void onRequestPermissionResult(@Nullable Boolean granted) { + if (granted != null && granted) { + nearbyWifiPermission = Permission.GRANTED; + } else if (shouldShowRequestPermissionRationale(ctx, + NEARBY_WIFI_DEVICES)) { + nearbyWifiPermission = Permission.SHOW_RATIONALE; + } else { + nearbyWifiPermission = Permission.PERMANENTLY_DENIED; + } + } + + private void requestPermissions() { + nearbyWifiRequest.launch(NEARBY_WIFI_DEVICES); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java index efc81a9f6..394012f8c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java @@ -31,6 +31,7 @@ import static android.view.View.VISIBLE; import static androidx.transition.TransitionManager.beginDelayedTransition; import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -102,7 +103,7 @@ public class FallbackFragment extends BaseFragment { i.putExtra(EXTRA_STREAM, uri); i.setType("*/*"); // gives us all sharing options i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); - startActivity(Intent.createChooser(i, null)); + tryToStartActivity(requireActivity(), Intent.createChooser(i, null)); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java index c7e9bcd30..db8e1cf22 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotIntroFragment.java @@ -49,8 +49,10 @@ public class HotspotIntroFragment extends Fragment { private TextView progressTextView; private final AbstractConditionManager conditionManager = SDK_INT < 29 ? - new ConditionManager(this, this::onPermissionUpdate) : - new ConditionManager29(this, this::onPermissionUpdate); + new ConditionManager(this::onPermissionUpdate) : + SDK_INT >= 33 ? + new ConditionManager33(this, this::onPermissionUpdate) : + new ConditionManager29(this, this::onPermissionUpdate); @Override public void onAttach(Context context) { @@ -87,7 +89,6 @@ public class HotspotIntroFragment extends Fragment { } private void onButtonClick(View view) { - startButton.setEnabled(false); startHotspotIfConditionsFulfilled(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java index 30d224c0a..7f5640444 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java @@ -22,13 +22,19 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault; import javax.annotation.Nullable; import javax.inject.Inject; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; +import static androidx.core.content.ContextCompat.checkSelfPermission; import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR; import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS; import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog; @@ -52,6 +58,10 @@ public class PasswordFragment extends BaseFragment implements TextWatcher { private TextInputLayout input; private TextInputEditText password; + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new RequestPermission(), isGranted -> + validatePassword()); + @Override public void injectFragment(ActivityComponent component) { component.inject(this); @@ -109,6 +119,17 @@ public class PasswordFragment extends BaseFragment implements TextWatcher { hideSoftKeyboard(password); signInButton.setVisibility(INVISIBLE); progress.setVisibility(VISIBLE); + if (SDK_INT >= 33 && + checkSelfPermission(requireContext(), POST_NOTIFICATIONS) != + PERMISSION_GRANTED) { + // this calls validatePassword() when it returns + requestPermissionLauncher.launch(POST_NOTIFICATIONS); + } else { + validatePassword(); + } + } + + private void validatePassword() { viewModel.validatePassword(password.getText().toString()); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportFormFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportFormFragment.java index 8eff6d704..b915b815a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportFormFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportFormFragment.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.reporting; -import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -33,13 +32,11 @@ import androidx.recyclerview.widget.RecyclerView; import static android.view.View.GONE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_SHORT; import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -180,13 +177,7 @@ public class ReportFormFragment extends BaseFragment { private void triggerPrivacyPolicy() { Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\")); - try { - startActivity(i); - } catch (ActivityNotFoundException e) { - logException(LOG, WARNING, e); - Toast.makeText(requireContext(), - R.string.error_start_activity, LENGTH_LONG).show(); - } + tryToStartActivity(requireActivity(), i); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/AboutFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/AboutFragment.java index 664e7f286..fff46763e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/AboutFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/AboutFragment.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.settings; -import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -8,7 +7,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import android.widget.Toast; import org.briarproject.briar.BuildConfig; import org.briarproject.briar.R; @@ -21,10 +19,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import static android.widget.Toast.LENGTH_LONG; -import static java.util.logging.Level.WARNING; +import static android.content.Intent.ACTION_VIEW; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -85,16 +82,9 @@ public class AboutFragment extends Fragment { } private void goToUrl(String url) { - Intent i = new Intent(Intent.ACTION_VIEW); + Intent i = new Intent(ACTION_VIEW); i.setData(Uri.parse(url)); - try { - startActivity(i); - } catch (ActivityNotFoundException e) { - logException(LOG, WARNING, e); - Toast.makeText(requireContext(), - R.string.error_start_activity, LENGTH_LONG).show(); - } - + tryToStartActivity(requireActivity(), i); } } \ No newline at end of file diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index 2d5ec5fc1..314c283cb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -1,12 +1,10 @@ package org.briarproject.briar.android.settings; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; -import android.widget.Toast; import org.briarproject.briar.R; import org.briarproject.briar.android.mailbox.MailboxActivity; @@ -28,12 +26,12 @@ import androidx.preference.PreferenceGroup; import static android.content.Intent.ACTION_SEND; import static android.content.Intent.EXTRA_TEXT; -import static android.widget.Toast.LENGTH_LONG; import static java.util.Objects.requireNonNull; import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -101,12 +99,8 @@ public class SettingsFragment extends PreferenceFragmentCompat { Intent sendIntent = new Intent(ACTION_SEND); sendIntent.putExtra(EXTRA_TEXT, text); sendIntent.setType("text/plain"); - try { - startActivity(Intent.createChooser(sendIntent, null)); - } catch (ActivityNotFoundException e) { - Toast.makeText(requireContext(), - R.string.error_start_activity, LENGTH_LONG).show(); - } + tryToStartActivity(requireActivity(), + Intent.createChooser(sendIntent, null)); return true; }); Preference prefFeedback = diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java index fdad47615..d1c6432a8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/PermissionUtils.java @@ -1,12 +1,10 @@ package org.briarproject.briar.android.util; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.location.LocationManager; import android.net.Uri; -import android.widget.Toast; import org.briarproject.briar.R; import org.briarproject.nullsafety.MethodsNotNullByDefault; @@ -29,10 +27,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS; -import static android.widget.Toast.LENGTH_LONG; import static androidx.core.content.ContextCompat.checkSelfPermission; import static java.lang.Boolean.TRUE; import static org.briarproject.briar.BuildConfig.APPLICATION_ID; +import static org.briarproject.briar.android.util.UiUtils.tryToStartActivity; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -47,7 +45,7 @@ public class PermissionUtils { } } - public static boolean isPermissionGranted(Context ctx, String permission) { + private static boolean isPermissionGranted(Context ctx, String permission) { return checkSelfPermission(ctx, permission) == PERMISSION_GRANTED; } @@ -68,7 +66,7 @@ public class PermissionUtils { gotPermission(ctx, grantedMap, BLUETOOTH_SCAN); } - public static DialogInterface.OnClickListener getGoToSettingsListener( + private static DialogInterface.OnClickListener getGoToSettingsListener( Context context) { return (dialog, which) -> { Intent i = new Intent(); @@ -76,7 +74,7 @@ public class PermissionUtils { i.addCategory(CATEGORY_DEFAULT); i.setData(Uri.parse("package:" + APPLICATION_ID)); i.addFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); + tryToStartActivity(context, i); }; } @@ -123,12 +121,7 @@ public class PermissionUtils { builder.setPositiveButton(R.string.permission_location_setting_button, (dialog, which) -> { Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS); - try { - ctx.startActivity(i); - } catch (ActivityNotFoundException e) { - Toast.makeText(ctx, R.string.error_start_activity, - LENGTH_LONG).show(); - } + tryToStartActivity(ctx, i); }); builder.show(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index 78a0e3ad2..c92d5829e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -157,6 +157,15 @@ public class UiUtils { ta.commit(); } + public static void tryToStartActivity(Context ctx, Intent intent) { + try { + ctx.startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(ctx, R.string.error_start_activity, LENGTH_LONG) + .show(); + } + } + public static String getContactDisplayName(Author author, @Nullable String alias) { String name = author.getName(); diff --git a/briar-android/src/main/res/layout/info_view.xml b/briar-android/src/main/res/layout/info_view.xml index c7cf40f7b..8a884a4d8 100644 --- a/briar-android/src/main/res/layout/info_view.xml +++ b/briar-android/src/main/res/layout/info_view.xml @@ -13,10 +13,10 @@ android:layout_margin="@dimen/margin_medium" android:contentDescription="@string/info" android:drawablePadding="@dimen/margin_medium" - android:drawableTint="?attr/colorControlNormal" android:gravity="center_vertical" app:drawableLeftCompat="@drawable/ic_info_dark" app:drawableStartCompat="@drawable/ic_info_dark" + app:drawableTint="?attr/colorControlNormal" tools:text="Did you know that if you took all the veins out of your body and laid them out end to end, you would die?" /> \ No newline at end of file diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index c108f88d5..4719ed5f2 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -789,6 +789,7 @@ Camera permission To scan the QR code, Briar needs access to the camera. Location permission + Nearby devices permission To discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone. Camera and location To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone. @@ -833,6 +834,8 @@ To create a Wi-Fi hotspot, Briar needs permission to access your precise location.\n\nBriar does not store your location or share it with anyone. You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access. You have denied access to your precise location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access. + To create a Wi-Fi hotspot, Briar needs permission to access nearby devices. + You have denied access to nearby devices, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access. Wi-Fi setting To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.