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,