summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson <chiuwinson@google.com> 2020-12-21 13:58:37 -0800
committer Winson <chiuwinson@google.com> 2021-02-04 10:27:00 -0800
commitc89bd75a18c53e7205eb0bbfbb4179edcec465c1 (patch)
treef9ca3c548e99e885d0008489c4c385133666a45e
parent346d2769b071d5156a99acc571700a4af7222a31 (diff)
Combine v1 and v2 DomainVerficationProxies
In case both v1 and v2 are on the device, have a delegate that supports sending requests to both receivers. The verification agent will then decide which API to use. This allows deferred migration to the v2 APIs. Note that the old user state API is still non-functional, even for a v1 verification agent. Exempt-From-Owner-Approval: Already approved by owners on main branch Bug: 170321181 Test: atest DomainVerificationProxyTest Change-Id: I951fec5690c1f0d848d86c8859c3e9a171fca887
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java30
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/DomainVerificationMessageCodes.java5
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java2
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxy.java52
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyCombined.java54
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV1.java4
-rw-r--r--services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV2.java2
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/domain/verify/DomainVerificationProxyTest.kt496
8 files changed, 621 insertions, 24 deletions
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0a7787b659c1..42d900f04060 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7073,25 +7073,21 @@ public class PackageManagerService extends IPackageManager.Stub
mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
mRequiredInstallerPackage = getRequiredInstallerLPr();
mRequiredUninstallerPackage = getRequiredUninstallerLPr();
+ ComponentName intentFilterVerifierComponent =
+ getIntentFilterVerifierComponentNameLPr();
ComponentName domainVerificationAgent =
getDomainVerificationAgentComponentNameLPr();
- if (domainVerificationAgent != null) {
- mDomainVerificationManager.setProxy(
- new DomainVerificationProxyV2(mContext, mDomainVerificationConnection,
- domainVerificationAgent));
- } else {
- // TODO(b/159952358): DomainVerificationProxyV1
- ComponentName intentFilterVerifierComponent =
- getIntentFilterVerifierComponentNameLPr();
- if (intentFilterVerifierComponent != null) {
- mDomainVerificationManager.setProxy(
- new DomainVerificationProxyV1(mContext, mDomainVerificationManager,
- mDomainVerificationManager.getCollector(),
- mDomainVerificationConnection,
- intentFilterVerifierComponent));
- mIntentFilterVerificationManager.setVerifierComponent(
- intentFilterVerifierComponent);
- }
+
+ DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
+ intentFilterVerifierComponent, domainVerificationAgent, mContext,
+ mDomainVerificationManager, mDomainVerificationManager.getCollector(),
+ mDomainVerificationConnection);
+
+ mDomainVerificationManager.setProxy(domainVerificationProxy);
+
+ if (intentFilterVerifierComponent != null) {
+ mIntentFilterVerificationManager.setVerifierComponent(
+ intentFilterVerifierComponent);
}
mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
diff --git a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationMessageCodes.java b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationMessageCodes.java
index feb3be7a2a3f..7fb0067738ad 100644
--- a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationMessageCodes.java
+++ b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationMessageCodes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,5 +31,6 @@ import com.android.server.pm.domain.verify.proxy.DomainVerificationProxy;
public final class DomainVerificationMessageCodes {
public static final int SEND_REQUEST = 1;
- public static final int LEGACY_ON_INTENT_FILTER_VERIFIED = 2;
+ public static final int LEGACY_SEND_REQUEST = 2;
+ public static final int LEGACY_ON_INTENT_FILTER_VERIFIED = 3;
}
diff --git a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java
index 66f0a6d2dd2a..0a28069017d4 100644
--- a/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/domain/verify/DomainVerificationService.java
@@ -1170,8 +1170,6 @@ public class DomainVerificationService extends SystemService
*/
void schedule(int code, @Nullable Object object);
- boolean isCallerPackage(int callingUid, @NonNull String packageName);
-
/**
* This can only be called when the internal {@link #mLock} is held. Otherwise it's possible
* to deadlock with {@link PackageManagerService}.
diff --git a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxy.java b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxy.java
index 2582230984bb..c641caaa8c1b 100644
--- a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxy.java
+++ b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxy.java
@@ -19,15 +19,67 @@ package com.android.server.pm.domain.verify.proxy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
import com.android.server.DeviceIdleInternal;
import com.android.server.pm.domain.verify.DomainVerificationMessageCodes;
+import com.android.server.pm.domain.verify.DomainVerificationCollector;
+import com.android.server.pm.domain.verify.DomainVerificationManagerInternal;
+import java.util.Objects;
import java.util.Set;
// TODO(b/170321181): Combine the proxy versions for supporting v1 and v2 at once
public interface DomainVerificationProxy {
+ String TAG = "DomainVerificationProxy";
+
+ boolean DEBUG_PROXIES = false;
+
+ static <ConnectionType extends DomainVerificationProxyV1.Connection
+ & DomainVerificationProxyV2.Connection> DomainVerificationProxy makeProxy(
+ @Nullable ComponentName componentV1, @Nullable ComponentName componentV2,
+ @NonNull Context context, @NonNull DomainVerificationManagerInternal manager,
+ @NonNull DomainVerificationCollector collector, @NonNull ConnectionType connection) {
+ if (DEBUG_PROXIES) {
+ Slog.d(TAG, "Intent filter verification agent: " + componentV1);
+ Slog.d(TAG, "Domain verification agent: " + componentV2);
+ }
+
+ if (componentV2 != null && componentV1 != null
+ && !Objects.equals(componentV2.getPackageName(), componentV1.getPackageName())) {
+ // Only allow a legacy verifier if it's in the same package as the v2 verifier
+ componentV1 = null;
+ }
+
+ DomainVerificationProxy proxyV1 = null;
+ DomainVerificationProxy proxyV2 = null;
+
+ if (componentV1 != null) {
+ proxyV1 = new DomainVerificationProxyV1(context, manager, collector, connection,
+ componentV1);
+ }
+
+ if (componentV2 != null) {
+ proxyV2 = new DomainVerificationProxyV2(context, connection, componentV2);
+ }
+
+ if (proxyV1 != null && proxyV2 != null) {
+ return new DomainVerificationProxyCombined(proxyV1, proxyV2);
+ }
+
+ if (proxyV1 != null) {
+ return proxyV1;
+ }
+
+ if (proxyV2 != null) {
+ return proxyV2;
+ }
+
+ return new DomainVerificationProxyUnavailable();
+ }
+
default void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
}
diff --git a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyCombined.java b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyCombined.java
new file mode 100644
index 000000000000..eb63d52c0d1f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyCombined.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.domain.verify.proxy;
+
+import android.annotation.NonNull;
+
+import java.util.Set;
+
+class DomainVerificationProxyCombined implements DomainVerificationProxy {
+
+ @NonNull
+ private final DomainVerificationProxy mProxyV1;
+ @NonNull
+ private final DomainVerificationProxy mProxyV2;
+
+ DomainVerificationProxyCombined(@NonNull DomainVerificationProxy proxyV1,
+ @NonNull DomainVerificationProxy proxyV2) {
+ mProxyV1 = proxyV1;
+ mProxyV2 = proxyV2;
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ mProxyV2.sendBroadcastForPackages(packageNames);
+ mProxyV1.sendBroadcastForPackages(packageNames);
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ // Both proxies must run, so cannot use a direct ||, which may skip the right hand side
+ boolean resultV2 = mProxyV2.runMessage(messageCode, object);
+ boolean resultV1 = mProxyV1.runMessage(messageCode, object);
+ return resultV2 || resultV1;
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mProxyV2.isCallerVerifier(callingUid) || mProxyV1.isCallerVerifier(callingUid);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV1.java
index 6229ede45988..c156c3461dae 100644
--- a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV1.java
@@ -115,14 +115,14 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
}
}
}
- mConnection.schedule(DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
+ mConnection.schedule(DomainVerificationMessageCodes.LEGACY_SEND_REQUEST, packageNames);
}
@SuppressWarnings("deprecation")
@Override
public boolean runMessage(int messageCode, Object object) {
switch (messageCode) {
- case DomainVerificationMessageCodes.SEND_REQUEST:
+ case DomainVerificationMessageCodes.LEGACY_SEND_REQUEST:
@SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
if (DEBUG_BROADCASTS) {
Slog.d(TAG, "Requesting domain verification for " + packageNames);
diff --git a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV2.java
index d9d03813bb2b..1595374dd1d3 100644
--- a/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV2.java
+++ b/services/core/java/com/android/server/pm/domain/verify/proxy/DomainVerificationProxyV2.java
@@ -56,7 +56,7 @@ public class DomainVerificationProxyV2 implements DomainVerificationProxy {
@Override
public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
- mConnection.schedule(DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
+ mConnection.schedule(com.android.server.pm.domain.verify.DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
}
@Override
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/domain/verify/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/domain/verify/DomainVerificationProxyTest.kt
new file mode 100644
index 000000000000..7519ff013458
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/domain/verify/DomainVerificationProxyTest.kt
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.domain.verify
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.domain.verify.DomainVerificationManager
+import android.content.pm.domain.verify.DomainVerificationRequest
+import android.content.pm.domain.verify.DomainVerificationSet
+import android.content.pm.domain.verify.DomainVerificationState
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.DeviceIdleInternal
+import com.android.server.pm.domain.verify.DomainVerificationCollector
+import com.android.server.pm.domain.verify.DomainVerificationManagerInternal
+import com.android.server.pm.domain.verify.proxy.DomainVerificationProxy
+import com.android.server.pm.domain.verify.proxy.DomainVerificationProxyV1
+import com.android.server.pm.domain.verify.proxy.DomainVerificationProxyV2
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+
+@Suppress("DEPRECATION")
+class DomainVerificationProxyTest {
+
+ companion object {
+ private const val TEST_PKG_NAME_ONE = "com.test.pkg.one"
+ private const val TEST_PKG_NAME_TWO = "com.test.pkg.two"
+ private const val TEST_PKG_NAME_TARGET_ONE = "com.test.target.one"
+ private const val TEST_PKG_NAME_TARGET_TWO = "com.test.target.two"
+ private const val TEST_CALLING_UID_ACCEPT = 40
+ private const val TEST_CALLING_UID_REJECT = 41
+ private val TEST_UUID_ONE = UUID.fromString("f7fbb7dd-7b5f-4609-a95e-c6c7765fb9cd")
+ private val TEST_UUID_TWO = UUID.fromString("4a09b361-a967-43ac-9d18-07a385dff740")
+ }
+
+ private val componentOne = ComponentName(TEST_PKG_NAME_ONE, ".ReceiverOne")
+ private val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverTwo")
+ private val componentThree = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverThree")
+
+ private lateinit var context: Context
+ private lateinit var manager: DomainVerificationManagerInternal
+ private lateinit var collector: DomainVerificationCollector
+
+ // Must be declared as field to support generics
+ @Captor
+ lateinit var hostCaptor: ArgumentCaptor<Set<String>>
+
+ @Before
+ fun setUpMocks() {
+ MockitoAnnotations.initMocks(this)
+ context = mockThrowOnUnmocked {
+ whenever(sendBroadcastAsUser(any(), any(), any(), any<Bundle>()))
+ whenever(
+ enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT),
+ anyString()
+ )
+ )
+ }
+ manager = mockThrowOnUnmocked {
+ whenever(getDomainVerificationSetId(any())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> TEST_UUID_ONE
+ TEST_PKG_NAME_TARGET_TWO -> TEST_UUID_TWO
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(getDomainVerificationSet(anyString())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> DomainVerificationSet(TEST_UUID_ONE, pkgName, mapOf(
+ "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ ))
+ TEST_PKG_NAME_TARGET_TWO -> DomainVerificationSet(TEST_UUID_TWO, pkgName, mapOf(
+ "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ ))
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
+ }
+ collector = mockThrowOnUnmocked {
+ whenever(collectAutoVerifyDomains(any())) {
+ when (val pkgName = (arguments[0] as AndroidPackage).packageName) {
+ TEST_PKG_NAME_TARGET_ONE -> ArraySet(setOf("example1.com", "example2.com"))
+ TEST_PKG_NAME_TARGET_TWO -> ArraySet(setOf("example3.com", "example4.com"))
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun isCallerVerifierV1() {
+ val connection = mockConnection()
+ val proxyV1 = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, null, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierV2() {
+ val connection = mockConnection()
+ val proxyV2 = DomainVerificationProxy.makeProxy<Connection>(
+ null, componentTwo, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierBoth() {
+ val connection = mockConnection()
+ val proxyBoth = DomainVerificationProxy.makeProxy<Connection>(
+ componentTwo, componentThree,
+ context, manager, collector, connection
+ )
+
+ // The combined proxy should only ever call v2 when it succeeds
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ val callingUidCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ // But will call both when v2 fails
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection, times(2))
+ .isCallerPackage(callingUidCaptor.capture(), eq(TEST_PKG_NAME_TWO))
+ verifyNoMoreInteractions(connection)
+
+ assertThat(callingUidCaptor.allValues.toSet()).containsExactly(TEST_CALLING_UID_REJECT)
+ }
+
+ @Test
+ fun differentPackagesResolvesOnlyV2() {
+ assertThat(DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, componentTwo,
+ context, manager, collector, mockConnection()
+ )).isInstanceOf(DomainVerificationProxyV2::class.java)
+ }
+
+ private fun prepareProxyV1(): ProxyV1Setup {
+ val messages = mutableListOf<Pair<Int, Any?>>()
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne,
+ null,
+ context,
+ manager,
+ collector,
+ connection
+ )
+ return ProxyV1Setup(messages, connection, proxy)
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV1() {
+ val (messages, _, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(2)
+ intents.forEach {
+ assertThat(it.action).isEqualTo(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+ assertThat(it.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME))
+ .isEqualTo(IntentFilter.SCHEME_HTTPS)
+ assertThat(it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1))
+ .isNotEqualTo(-1)
+ }
+
+ intents[0].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_ONE)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example1.com example2.com")
+ }
+
+ intents[1].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_TWO)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example3.com example4.com")
+ }
+ }
+
+ private fun prepareProxyOnIntentFilterVerifiedV1(): Pair<ProxyV1Setup, Pair<Int, Int>> {
+ val (messages, connection, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+ messages.clear()
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+
+ val verificationIds = intentCaptor.allValues.map {
+ it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1)
+ }
+
+ assertThat(verificationIds).doesNotContain(-1)
+
+ return ProxyV1Setup(messages, connection, proxy) to
+ (verificationIds[0] to verificationIds[1])
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFullSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ assertThat(messages).hasSize(2)
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationManager.STATE_SUCCESS)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedPartialSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+ val stateCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(4)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ stateCaptor.capture()
+ )
+
+ assertThat(idCaptor.allValues)
+ .containsExactly(TEST_UUID_ONE, TEST_UUID_ONE, TEST_UUID_TWO, TEST_UUID_TWO)
+
+ val hostToStates: Map<Set<*>, Int> = hostCaptor.allValues.zip(stateCaptor.allValues).toMap()
+ assertThat(hostToStates).isEqualTo(mapOf(
+ setOf("example1.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example2.com") to DomainVerificationState.STATE_SUCCESS,
+ setOf("example3.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example4.com") to DomainVerificationState.STATE_SUCCESS,
+ ))
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFailureV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com", "example2.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com", "example4.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationState.STATE_LEGACY_FAILURE)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV2() {
+ val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverOne")
+ val messages = mutableListOf<Pair<Int, Any?>>()
+
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ null,
+ componentTwo,
+ context,
+ manager,
+ collector,
+ connection
+ )
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(1)
+ intents.single().apply {
+ assertThat(this.action).isEqualTo(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+ val request: DomainVerificationRequest? =
+ getParcelableExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST)
+ assertThat(request?.packageNames).containsExactly(
+ TEST_PKG_NAME_TARGET_ONE,
+ TEST_PKG_NAME_TARGET_TWO
+ )
+ }
+ }
+
+ private fun mockConnection(block: Connection.() -> Unit = {}) =
+ mockThrowOnUnmocked<Connection> {
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)) { false }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)) { false }
+ whenever(getPackage(anyString())) { mockPkg(arguments[0] as String) }
+ whenever(powerSaveTempWhitelistAppDuration) { 1000 }
+ whenever(deviceIdleInternal) {
+ mockThrowOnUnmocked<DeviceIdleInternal> {
+ whenever(
+ addPowerSaveTempWhitelistApp(
+ anyInt(), anyString(), anyLong(), anyInt(),
+ anyBoolean(), anyString()
+ )
+ )
+ }
+ }
+ block()
+ }
+
+ private fun mockPkg(pkgName: String): AndroidPackage {
+ return mockThrowOnUnmocked { whenever(packageName) { pkgName } }
+ }
+
+ private data class ProxyV1Setup(
+ val messages: MutableList<Pair<Int, Any?>>,
+ val connection: Connection,
+ val proxy: DomainVerificationProxy
+ )
+
+ interface Connection : DomainVerificationProxyV1.Connection,
+ DomainVerificationProxyV2.Connection
+}