PM-11483: Create AutofillTileService (#3844)

This commit is contained in:
David Perez 2024-08-30 09:50:54 -05:00 committed by GitHub
parent 17c579bfc2
commit 7e5203efa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 176 additions and 5 deletions

View file

@ -3,6 +3,24 @@
xmlns:tools="http://schemas.android.com/tools">
<application tools:ignore="MissingApplicationIcon">
<!--
The AutofillTileService name below refers to the legacy Xamarin app's service name.
This must always match in order for the app to properly query if it is providing autofill
tile services.
-->
<!--suppress AndroidDomInspection -->
<service
android:name="com.x8bit.bitwarden.AutofillTileService"
android:exported="true"
android:icon="@drawable/ic_notification"
android:label="@string/autofill"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<!-- Disable Crashlytics for debug builds -->
<meta-data
android:name="firebase_crashlytics_collection_enabled"

View file

@ -77,6 +77,13 @@
</intent-filter>
</activity>
<activity
android:name=".AccessibilityActivity"
android:exported="false"
android:launchMode="singleTop"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".AutofillTotpCopyActivity"
android:exported="true"

View file

@ -0,0 +1,17 @@
package com.x8bit.bitwarden
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
/**
* An activity to be launched and then immediately closed so that the OS Shade can be collapsed
* after the user clicks on the Autofill Quick Tile.
*/
@OmitFromCoverage
class AccessibilityActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
finish()
}
}

View file

@ -3,16 +3,19 @@ package com.x8bit.bitwarden
import android.app.Service
import android.content.Intent
import android.os.Build
import androidx.annotation.Keep
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.BitwardenAutofillTileService
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_AUTOFILL_TILE_SERVICE_NAME = "com.x8bit.bitwarden.AutofillTileService"
private const val LEGACY_VAULT_TILE_SERVICE_NAME = "com.x8bit.bitwarden.MyVaultTileService"
private const val LEGACY_GENERATOR_TILE_SERVICE_NAME = "com.x8bit.bitwarden.GeneratorTileService"
@ -21,14 +24,20 @@ private const val LEGACY_GENERATOR_TILE_SERVICE_NAME = "com.x8bit.bitwarden.Gene
* and modify various characteristics before initialization.
*/
@Suppress("unused")
@Keep
@OmitFromCoverage
class BitwardenAppComponentFactory : AppComponentFactory() {
/**
* 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.
* Used to intercept when certain legacy services are 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.
*
* Services currently being managed:
* * [BitwardenAutofillService]
* * [BitwardenAutofillTileService]
* * [BitwardenFido2ProviderService]
* * [BitwardenVaultTileService]
* * [BitwardenGeneratorTileService]
*/
override fun instantiateServiceCompat(
cl: ClassLoader,
@ -39,6 +48,14 @@ class BitwardenAppComponentFactory : AppComponentFactory() {
super.instantiateServiceCompat(cl, BitwardenAutofillService::class.java.name, intent)
}
LEGACY_AUTOFILL_TILE_SERVICE_NAME -> {
super.instantiateServiceCompat(
cl,
BitwardenAutofillTileService::class.java.name,
intent,
)
}
LEGACY_CREDENTIAL_SERVICE_NAME -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
super.instantiateServiceCompat(

View file

@ -0,0 +1,21 @@
package com.x8bit.bitwarden.data.accessibility.di
import com.x8bit.bitwarden.data.accessibility.manager.AccessibilityAutofillManager
import com.x8bit.bitwarden.data.accessibility.manager.AccessibilityAutofillManagerImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides dependencies within the accessibility package.
*/
@Module
@InstallIn(SingletonComponent::class)
object AccessibilityModule {
@Singleton
@Provides
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
AccessibilityAutofillManagerImpl()
}

View file

@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.accessibility.manager
/**
* A relay manager used to notify the accessibility service to attempt an autofill.
*/
interface AccessibilityAutofillManager {
/**
* Indicates that the Autofill tile has been clicked and we attempt an accessibility-based
* autofill.
*/
var isAccessibilityTileClicked: Boolean
}

View file

@ -0,0 +1,8 @@
package com.x8bit.bitwarden.data.accessibility.manager
/**
* The default implementation for the [AccessibilityAutofillManager].
*/
class AccessibilityAutofillManagerImpl : AccessibilityAutofillManager {
override var isAccessibilityTileClicked: Boolean = false
}

View file

@ -0,0 +1,52 @@
package com.x8bit.bitwarden.data.tiles
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import android.service.quicksettings.TileService
import androidx.annotation.Keep
import com.x8bit.bitwarden.AccessibilityActivity
import com.x8bit.bitwarden.data.accessibility.manager.AccessibilityAutofillManager
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* A service for handling the Autofill quick settings tile.
*/
@AndroidEntryPoint
@Keep
@OmitFromCoverage
class BitwardenAutofillTileService : TileService() {
@Inject
lateinit var accessibilityAutofillManager: AccessibilityAutofillManager
override fun onClick() {
if (isLocked) {
unlockAndRun { launchAutofill() }
} else {
launchAutofill()
}
}
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun launchAutofill() {
accessibilityAutofillManager.isAccessibilityTileClicked = true
val intent = Intent(applicationContext, AccessibilityActivity::class.java)
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
} else {
startActivityAndCollapse(
PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE,
),
)
}
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.accessibility.manager
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class AccessibilityAutofillManagerTest {
private val accessibilityAutofillManager: AccessibilityAutofillManager =
AccessibilityAutofillManagerImpl()
@Test
fun `isAccessibilityTileClicked should simply hold the state it is provided`() {
assertFalse(accessibilityAutofillManager.isAccessibilityTileClicked)
accessibilityAutofillManager.isAccessibilityTileClicked = true
assertTrue(accessibilityAutofillManager.isAccessibilityTileClicked)
accessibilityAutofillManager.isAccessibilityTileClicked = false
assertFalse(accessibilityAutofillManager.isAccessibilityTileClicked)
}
}