diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 737b5d821..924d5d37e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,6 +72,7 @@ + @@ -191,6 +192,42 @@ android:value="true" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/x8bit/bitwarden/BitwardenAppComponentFactory.kt b/app/src/main/java/com/x8bit/bitwarden/BitwardenAppComponentFactory.kt index fdd79525a..62ae707b8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/BitwardenAppComponentFactory.kt +++ b/app/src/main/java/com/x8bit/bitwarden/BitwardenAppComponentFactory.kt @@ -7,10 +7,14 @@ import androidx.core.app.AppComponentFactory import com.x8bit.bitwarden.data.autofill.BitwardenAutofillService import com.x8bit.bitwarden.data.autofill.fido2.BitwardenFido2ProviderService import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage +import com.x8bit.bitwarden.data.tiles.BitwardenGeneratorTileService +import com.x8bit.bitwarden.data.tiles.BitwardenVaultTileService private const val LEGACY_AUTOFILL_SERVICE_NAME = "com.x8bit.bitwarden.Autofill.AutofillService" private const val LEGACY_CREDENTIAL_SERVICE_NAME = "com.x8bit.bitwarden.Autofill.CredentialProviderService" +private const val LEGACY_VAULT_TILE_SERVICE_NAME = "com.x8bit.bitwarden.MyVaultTileService" +private const val LEGACY_GENERATOR_TILE_SERVICE_NAME = "com.x8bit.bitwarden.GeneratorTileService" /** * A factory class that allows us to intercept when a manifest element is being instantiated @@ -20,10 +24,11 @@ private const val LEGACY_CREDENTIAL_SERVICE_NAME = @OmitFromCoverage class BitwardenAppComponentFactory : AppComponentFactory() { /** - * Used to intercept when the [BitwardenAutofillService] or [BitwardenFido2ProviderService] is - * being instantiated and modify which service is created. This is required because the - * [className] used in the manifest must match the legacy Xamarin app service name but the - * service name in this app is different. + * Used to intercept when the [BitwardenAutofillService], [BitwardenFido2ProviderService], + * [BitwardenVaultTileService], or [BitwardenGeneratorTileService] is being instantiated and + * modify which service is created. This is required because the [className] used in the + * manifest must match the legacy Xamarin app service name but the service name in this app is + * different. */ override fun instantiateServiceCompat( cl: ClassLoader, @@ -48,6 +53,18 @@ class BitwardenAppComponentFactory : AppComponentFactory() { } } + LEGACY_VAULT_TILE_SERVICE_NAME -> { + super.instantiateServiceCompat(cl, BitwardenVaultTileService::class.java.name, intent) + } + + LEGACY_GENERATOR_TILE_SERVICE_NAME -> { + super.instantiateServiceCompat( + cl, + BitwardenGeneratorTileService::class.java.name, + intent, + ) + } + else -> super.instantiateServiceCompat(cl, className, intent) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenGeneratorTileService.kt b/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenGeneratorTileService.kt new file mode 100644 index 000000000..5bd13239b --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenGeneratorTileService.kt @@ -0,0 +1,43 @@ +package com.x8bit.bitwarden.data.tiles + +import android.annotation.SuppressLint +import android.os.Build +import android.service.quicksettings.TileService +import androidx.annotation.Keep +import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage +import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Runnable +import javax.inject.Inject + +/** + * A service for handling the Password Generator quick settings tile. + */ +@AndroidEntryPoint +@Keep +@OmitFromCoverage +class BitwardenGeneratorTileService : TileService() { + @Inject + lateinit var intentManager: IntentManager + + override fun onClick() { + if (isLocked) { + unlockAndRun(Runnable { launchGenerator() }) + } else { + launchGenerator() + } + } + + @Suppress("DEPRECATION") + @SuppressLint("StartActivityAndCollapseDeprecated") + private fun launchGenerator() { + val intent = intentManager.createTileIntent("bitwarden://password_generator") + + if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) { + startActivityAndCollapse(intent) + } else { + startActivityAndCollapse(intentManager.createTilePendingIntent(0, intent)) + } + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenVaultTileService.kt b/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenVaultTileService.kt new file mode 100644 index 000000000..0c241a30c --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/tiles/BitwardenVaultTileService.kt @@ -0,0 +1,43 @@ +package com.x8bit.bitwarden.data.tiles + +import android.annotation.SuppressLint +import android.os.Build +import android.service.quicksettings.TileService +import androidx.annotation.Keep +import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage +import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Runnable +import javax.inject.Inject + +/** + * A service for handling the My Vault quick settings tile. + */ +@AndroidEntryPoint +@Keep +@OmitFromCoverage +class BitwardenVaultTileService : TileService() { + @Inject + lateinit var intentManager: IntentManager + + override fun onClick() { + if (isLocked) { + unlockAndRun(Runnable { launchVault() }) + } else { + launchVault() + } + } + + @Suppress("DEPRECATION") + @SuppressLint("StartActivityAndCollapseDeprecated") + private fun launchVault() { + val intent = intentManager.createTileIntent("bitwarden://my_vault") + + if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) { + startActivityAndCollapse(intent) + } else { + startActivityAndCollapse(intentManager.createTilePendingIntent(0, intent)) + } + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt index e296b3374..4de21b46b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt @@ -87,6 +87,17 @@ interface IntentManager { */ fun createDocumentIntent(fileName: String): Intent + /** + * Creates an intent using [data] when selecting a quick settings tile. + */ + fun createTileIntent(data: String): Intent + + /** + * Creates a pending intent using [requestCode] and [tileIntent] when selecting a quick + * settings tile on API 34+. + */ + fun createTilePendingIntent(requestCode: Int, tileIntent: Intent): PendingIntent + /** * Creates a pending intent to use when providing [androidx.credentials.provider.CreateEntry] * instances for FIDO 2 credential creation. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt index f64bf972a..c5baa1bc5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt @@ -20,8 +20,10 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.runtime.Composable import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.core.net.toUri import androidx.credentials.CredentialManager import com.x8bit.bitwarden.BuildConfig +import com.x8bit.bitwarden.MainActivity import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage @@ -215,6 +217,24 @@ class IntentManagerImpl( putExtra(Intent.EXTRA_TITLE, fileName) } + override fun createTileIntent(data: String): Intent { + return Intent( + context, + MainActivity::class.java, + ) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .setData(data.toUri()) + } + + override fun createTilePendingIntent(requestCode: Int, tileIntent: Intent): PendingIntent { + return PendingIntent.getActivity( + context, + requestCode, + tileIntent, + PendingIntent.FLAG_IMMUTABLE, + ) + } + override fun createFido2CreationPendingIntent( action: String, userId: String,