diff options
3 files changed, 161 insertions, 4 deletions
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index 9843cf8d..2a8fcfa4 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -840,7 +840,7 @@ public class ResolverListAdapter extends BaseAdapter { userHandle); } - public final List<Intent> getIntents() { + public List<Intent> getIntents() { // TODO: immutable copy? return mIntents; } diff --git a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java index e6d5d1c4..2b4a7ada 100644 --- a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java @@ -34,8 +34,8 @@ import com.android.intentresolver.shared.model.User; import java.util.List; /** - * Empty state provider that does not allow cross profile sharing, it will return a blocker - * in case if the profile of the current tab is not the same as the profile of the calling app. + * Empty state provider that informs about a lack of cross profile sharing. It will return + * an empty state in case there are no intents which can be forwarded to another profile. */ public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider { @@ -79,7 +79,8 @@ public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider { // Allow access to the tab when launched by the same user as the tab owner // or when there is at least one target which is permitted for cross-profile. - if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, tabOwnerHandle)) { + if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, + /* source = */ launchedAs.getHandle())) { return null; } diff --git a/tests/unit/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProviderTest.kt b/tests/unit/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProviderTest.kt new file mode 100644 index 00000000..fe3e844b --- /dev/null +++ b/tests/unit/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProviderTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 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.intentresolver.emptystate + +import android.content.Intent +import com.android.intentresolver.ProfileHelper +import com.android.intentresolver.ResolverListAdapter +import com.android.intentresolver.annotation.JavaInterop +import com.android.intentresolver.data.repository.FakeUserRepository +import com.android.intentresolver.domain.interactor.UserInteractor +import com.android.intentresolver.inject.FakeIntentResolverFlags +import com.android.intentresolver.shared.model.User +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.junit.Test +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyList +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.same +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +@OptIn(JavaInterop::class) +class NoCrossProfileEmptyStateProviderTest { + + private val personalUser = User(0, User.Role.PERSONAL) + private val workUser = User(10, User.Role.WORK) + private val flags = FakeIntentResolverFlags() + private val personalBlocker = mock<EmptyState>() + private val workBlocker = mock<EmptyState>() + + private val userRepository = FakeUserRepository(listOf(personalUser, workUser)) + + private val personalIntents = listOf(Intent("PERSONAL")) + private val personalListAdapter = + mock<ResolverListAdapter> { + on { userHandle } doReturn personalUser.handle + on { intents } doReturn personalIntents + } + private val workIntents = listOf(Intent("WORK")) + private val workListAdapter = + mock<ResolverListAdapter> { + on { userHandle } doReturn workUser.handle + on { intents } doReturn workIntents + } + + // Pretend that no intent can ever be forwarded + val crossProfileIntentsChecker = + mock<CrossProfileIntentsChecker> { + on { + hasCrossProfileIntents( + /* intents = */ anyList(), + /* source = */ anyInt(), + /* target = */ anyInt() + ) + } doReturn false + } + private val sourceUserId = argumentCaptor<Int>() + private val targetUserId = argumentCaptor<Int>() + + @Test + fun testPersonalToWork() { + val userInteractor = UserInteractor(userRepository, launchedAs = personalUser.handle) + + val profileHelper = + ProfileHelper( + userInteractor, + CoroutineScope(Dispatchers.Unconfined), + Dispatchers.Unconfined, + flags + ) + + val provider = + NoCrossProfileEmptyStateProvider( + /* profileHelper = */ profileHelper, + /* noWorkToPersonalEmptyState = */ personalBlocker, + /* noPersonalToWorkEmptyState = */ workBlocker, + /* crossProfileIntentsChecker = */ crossProfileIntentsChecker + ) + + // Personal to personal, not blocked + assertThat(provider.getEmptyState(personalListAdapter)).isNull() + // Not called because sourceUser == targetUser + verify(crossProfileIntentsChecker, never()) + .hasCrossProfileIntents(anyList(), anyInt(), anyInt()) + + // Personal to work, blocked + assertThat(provider.getEmptyState(workListAdapter)).isSameInstanceAs(workBlocker) + + verify(crossProfileIntentsChecker, times(1)) + .hasCrossProfileIntents( + same(workIntents), + sourceUserId.capture(), + targetUserId.capture() + ) + assertThat(sourceUserId.firstValue).isEqualTo(personalUser.id) + assertThat(targetUserId.firstValue).isEqualTo(workUser.id) + } + + @Test + fun testWorkToPersonal() { + val userInteractor = UserInteractor(userRepository, launchedAs = workUser.handle) + + val profileHelper = + ProfileHelper( + userInteractor, + CoroutineScope(Dispatchers.Unconfined), + Dispatchers.Unconfined, + flags + ) + + val provider = + NoCrossProfileEmptyStateProvider( + /* profileHelper = */ profileHelper, + /* noWorkToPersonalEmptyState = */ personalBlocker, + /* noPersonalToWorkEmptyState = */ workBlocker, + /* crossProfileIntentsChecker = */ crossProfileIntentsChecker + ) + + // Work to work, not blocked + assertThat(provider.getEmptyState(workListAdapter)).isNull() + // Not called because sourceUser == targetUser + verify(crossProfileIntentsChecker, never()) + .hasCrossProfileIntents(anyList(), anyInt(), anyInt()) + + // Work to personal, blocked + assertThat(provider.getEmptyState(personalListAdapter)).isSameInstanceAs(personalBlocker) + + verify(crossProfileIntentsChecker, times(1)) + .hasCrossProfileIntents( + same(personalIntents), + sourceUserId.capture(), + targetUserId.capture() + ) + assertThat(sourceUserId.firstValue).isEqualTo(workUser.id) + assertThat(targetUserId.firstValue).isEqualTo(personalUser.id) + } +} |