Request permission to show notifications

This is needed since API 33, because we show a "Vault unlocked"
notification if the user has enabled encryption and has unlocked the
vault.
This commit is contained in:
Alexander Bakker 2022-09-14 19:29:36 +02:00
parent 642864fca1
commit 1e3ceefeec
7 changed files with 53 additions and 2 deletions

View file

@ -1,11 +1,13 @@
package com.beemdevelopment.aegis; package com.beemdevelopment.aegis;
import android.Manifest;
import android.view.View; import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.espresso.UiController; import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewAction;
import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.SCryptParameters; import com.beemdevelopment.aegis.crypto.SCryptParameters;
@ -42,6 +44,9 @@ public abstract class AegisTest {
@Rule @Rule
public HiltAndroidRule hiltRule = new HiltAndroidRule(this); public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
@Rule
public final GrantPermissionRule permRule = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
@Inject @Inject
protected VaultManager _vaultManager; protected VaultManager _vaultManager;

View file

@ -82,6 +82,7 @@ public class IntroTest extends AegisTest {
@Test @Test
public void doIntro_None() { public void doIntro_None() {
assertFalse(_prefs.isIntroDone());
ViewInteraction next = onView(withId(R.id.btnNext)); ViewInteraction next = onView(withId(R.id.btnNext));
ViewInteraction prev = onView(withId(R.id.btnPrevious)); ViewInteraction prev = onView(withId(R.id.btnPrevious));
@ -98,10 +99,12 @@ public class IntroTest extends AegisTest {
VaultRepository vault = _vaultManager.getVault(); VaultRepository vault = _vaultManager.getVault();
assertFalse(vault.isEncryptionEnabled()); assertFalse(vault.isEncryptionEnabled());
assertNull(vault.getCredentials()); assertNull(vault.getCredentials());
assertTrue(_prefs.isIntroDone());
} }
@Test @Test
public void doIntro_Password() { public void doIntro_Password() {
assertFalse(_prefs.isIntroDone());
ViewInteraction next = onView(withId(R.id.btnNext)); ViewInteraction next = onView(withId(R.id.btnNext));
ViewInteraction prev = onView(withId(R.id.btnPrevious)); ViewInteraction prev = onView(withId(R.id.btnPrevious));
@ -129,10 +132,12 @@ public class IntroTest extends AegisTest {
assertTrue(vault.isEncryptionEnabled()); assertTrue(vault.isEncryptionEnabled());
assertTrue(slots.has(PasswordSlot.class)); assertTrue(slots.has(PasswordSlot.class));
assertFalse(slots.has(BiometricSlot.class)); assertFalse(slots.has(BiometricSlot.class));
assertTrue(_prefs.isIntroDone());
} }
@Test @Test
public void doIntro_Import_Plain() { public void doIntro_Import_Plain() {
assertFalse(_prefs.isIntroDone());
Uri uri = getResourceUri("aegis_plain.json"); Uri uri = getResourceUri("aegis_plain.json");
Intent resultData = new Intent(); Intent resultData = new Intent();
resultData.setData(uri); resultData.setData(uri);
@ -147,10 +152,12 @@ public class IntroTest extends AegisTest {
VaultRepository vault = _vaultManager.getVault(); VaultRepository vault = _vaultManager.getVault();
assertFalse(vault.isEncryptionEnabled()); assertFalse(vault.isEncryptionEnabled());
assertNull(vault.getCredentials()); assertNull(vault.getCredentials());
assertTrue(_prefs.isIntroDone());
} }
@Test @Test
public void doIntro_Import_Encrypted() { public void doIntro_Import_Encrypted() {
assertFalse(_prefs.isIntroDone());
Uri uri = getResourceUri("aegis_encrypted.json"); Uri uri = getResourceUri("aegis_encrypted.json");
Intent resultData = new Intent(); Intent resultData = new Intent();
resultData.setData(uri); resultData.setData(uri);
@ -169,6 +176,7 @@ public class IntroTest extends AegisTest {
assertTrue(vault.isEncryptionEnabled()); assertTrue(vault.isEncryptionEnabled());
assertTrue(slots.has(PasswordSlot.class)); assertTrue(slots.has(PasswordSlot.class));
assertFalse(slots.has(BiometricSlot.class)); assertFalse(slots.has(BiometricSlot.class));
assertTrue(_prefs.isIntroDone());
} }
private Uri getResourceUri(String resourceName) { private Uri getResourceUri(String resourceName) {

View file

@ -75,6 +75,7 @@ public class OverallTest extends AegisTest {
VaultRepository vault = _vaultManager.getVault(); VaultRepository vault = _vaultManager.getVault();
assertTrue(vault.isEncryptionEnabled()); assertTrue(vault.isEncryptionEnabled());
assertTrue(vault.getCredentials().getSlots().has(PasswordSlot.class)); assertTrue(vault.getCredentials().getSlots().has(PasswordSlot.class));
assertTrue(_prefs.isIntroDone());
List<VaultEntry> entries = Arrays.asList( List<VaultEntry> entries = Arrays.asList(
generateEntry(TotpInfo.class, "Frank", "Google"), generateEntry(TotpInfo.class, "Frank", "Google"),

View file

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"

View file

@ -1,5 +1,6 @@
package com.beemdevelopment.aegis.ui; package com.beemdevelopment.aegis.ui;
import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
@ -30,6 +31,7 @@ import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.helpers.BiometricsHelper; import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.EditTextHelper; import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.MetricsHelper; import com.beemdevelopment.aegis.helpers.MetricsHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.helpers.UiThreadExecutor; import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
@ -49,6 +51,9 @@ import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
public class AuthActivity extends AegisActivity { public class AuthActivity extends AegisActivity {
// Permission request codes
private static final int CODE_PERM_NOTIFICATIONS = 0;
private EditText _textPassword; private EditText _textPassword;
private SlotList _slots; private SlotList _slots;
@ -85,6 +90,13 @@ public class AuthActivity extends AegisActivity {
Intent intent = getIntent(); Intent intent = getIntent();
if (savedInstanceState == null) { if (savedInstanceState == null) {
_inhibitBioPrompt = intent.getBooleanExtra("inhibitBioPrompt", false); _inhibitBioPrompt = intent.getBooleanExtra("inhibitBioPrompt", false);
// A persistent notification is shown to let the user know that the vault is unlocked. Permission
// to do so is required since API 33, so for existing users, we have to request permission here
// in order to be able to show the notification after unlock.
if (Build.VERSION.SDK_INT >= 33) {
PermissionHelper.request(this, CODE_PERM_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
}
} else { } else {
_inhibitBioPrompt = savedInstanceState.getBoolean("inhibitBioPrompt", false); _inhibitBioPrompt = savedInstanceState.getBoolean("inhibitBioPrompt", false);
} }

View file

@ -5,12 +5,18 @@ import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE; import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS; import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
import android.Manifest;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ThemeMap; import com.beemdevelopment.aegis.ThemeMap;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.intro.IntroBaseActivity; import com.beemdevelopment.aegis.ui.intro.IntroBaseActivity;
import com.beemdevelopment.aegis.ui.intro.SlideFragment; import com.beemdevelopment.aegis.ui.intro.SlideFragment;
@ -24,6 +30,9 @@ import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
public class IntroActivity extends IntroBaseActivity { public class IntroActivity extends IntroBaseActivity {
// Permission request codes
private static final int CODE_PERM_NOTIFICATIONS = 0;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -40,7 +49,7 @@ public class IntroActivity extends IntroBaseActivity {
} }
@Override @Override
protected boolean onBeforeSlideChanged(Class<? extends SlideFragment> oldSlide, Class<? extends SlideFragment> newSlide) { protected boolean onBeforeSlideChanged(Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
// hide the keyboard before every slide change // hide the keyboard before every slide change
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(findViewById(android.R.id.content).getWindowToken(), 0); imm.hideSoftInputFromWindow(findViewById(android.R.id.content).getWindowToken(), 0);
@ -67,6 +76,17 @@ public class IntroActivity extends IntroBaseActivity {
return false; return false;
} }
@Override
protected void onAfterSlideChanged(@Nullable Class<? extends SlideFragment> oldSlide, @NonNull Class<? extends SlideFragment> newSlide) {
// If the user has enabled encryption, we need to request permission to show notifications
// in order to be able to show the "Vault unlocked" notification.
if (newSlide == DoneSlide.class && getState().getSerializable("creds") != null) {
if (Build.VERSION.SDK_INT >= 33) {
PermissionHelper.request(this, CODE_PERM_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
}
}
}
@Override @Override
protected void onDonePressed() { protected void onDonePressed() {
Bundle state = getState(); Bundle state = getState();

View file

@ -1,5 +1,6 @@
package com.beemdevelopment.aegis.vault; package com.beemdevelopment.aegis.vault;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.backup.BackupManager; import android.app.backup.BackupManager;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
@ -14,6 +15,7 @@ import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle; import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.services.NotificationService; import com.beemdevelopment.aegis.services.NotificationService;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
@ -350,8 +352,10 @@ public class VaultManager {
} }
private void startNotificationService() { private void startNotificationService() {
if (PermissionHelper.granted(_context, Manifest.permission.POST_NOTIFICATIONS)) {
_context.startService(getNotificationServiceIntent()); _context.startService(getNotificationServiceIntent());
} }
}
private void stopNotificationService() { private void stopNotificationService() {
_context.stopService(getNotificationServiceIntent()); _context.stopService(getNotificationServiceIntent());