Loading packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/domain/interactor/ScreenCaptureAppContentInteractorTest.kt 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.interactor import android.media.projection.MediaProjectionAppContent import android.os.UserHandle import androidx.core.graphics.createBitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.screencapture.common.data.repository.fakeScreenCaptureAppContentRepository import com.android.systemui.screencapture.common.domain.model.ScreenCaptureAppContent import com.android.systemui.screencapture.common.shared.model.castScreenCaptureUiParameters import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.launch import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureAppContentInteractorTest : SysuiTestCase() { private val kosmos = testKosmosNew() private val fakeUserHandle = UserHandle.of(123) private val fakeMediaProjectionAppContent1 = MediaProjectionAppContent( /* thumbnail= */ createBitmap(100, 100), /* title= */ "FakeTitle1", /* id= */ 456, ) private val fakeMediaProjectionAppContent2 = MediaProjectionAppContent( /* thumbnail= */ createBitmap(200, 200), /* title= */ "FakeTitle2", /* id= */ 789, ) private val fakeThrowable = IllegalStateException("FakeMessage") @Test fun appContentsFor_singlePackage_propagatesSuccess() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: Result<List<ScreenCaptureAppContent>>? = null // Act val appContents = interactor.appContentsFor("FakePackage", 200, 150) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() fakeScreenCaptureAppContentRepository.setAppContentSuccess( packageName = "FakePackage", user = fakeUserHandle, fakeMediaProjectionAppContent1, ) // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(1) with(fakeScreenCaptureAppContentRepository.appContentsForCalls.last()) { assertThat(packageName).isEqualTo("FakePackage") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } assertThat(result?.isSuccess).isTrue() assertThat(result?.getOrNull()) .containsExactly( ScreenCaptureAppContent("FakePackage", fakeMediaProjectionAppContent1) ) // Cleanup job.cancel() } @Test fun appContentsFor_singlePackage_propagatesFailure() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: Result<List<ScreenCaptureAppContent>>? = null // Act val appContents = interactor.appContentsFor("FakePackage", 200, 150) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() fakeScreenCaptureAppContentRepository.setAppContentFailure( packageName = "FakePackage", user = fakeUserHandle, fakeThrowable, ) // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(1) with(fakeScreenCaptureAppContentRepository.appContentsForCalls.last()) { assertThat(packageName).isEqualTo("FakePackage") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } assertThat(result?.isFailure).isTrue() assertThat(result?.exceptionOrNull()).isSameInstanceAs(fakeThrowable) // Cleanup job.cancel() } @Test fun appContentsFor_multiplePackages_propagatesOnlySuccessfulFetches() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: List<ScreenCaptureAppContent>? = null // Act val appContents = interactor.appContentsFor( listOf("FakePackage1", "FakePackage2", "FakePackage3"), 200, 150, ) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() with(fakeScreenCaptureAppContentRepository) { setAppContentSuccess( packageName = "FakePackage1", user = fakeUserHandle, fakeMediaProjectionAppContent1, ) setAppContentFailure( packageName = "FakePackage2", user = fakeUserHandle, throwable = fakeThrowable, ) setAppContentSuccess( packageName = "FakePackage3", user = fakeUserHandle, fakeMediaProjectionAppContent2, ) } // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(3) fakeScreenCaptureAppContentRepository.appContentsForCalls.forEachIndexed { index, call -> with(call) { assertThat(packageName).isEqualTo("FakePackage${index + 1}") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } } assertThat(result) .containsExactly( ScreenCaptureAppContent("FakePackage1", fakeMediaProjectionAppContent1), ScreenCaptureAppContent("FakePackage3", fakeMediaProjectionAppContent2), ) // Cleanup job.cancel() } } packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/domain/model/ScreenCaptureAppContentTest.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.model import android.media.projection.MediaProjectionAppContent import androidx.core.graphics.createBitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureAppContentTest : SysuiTestCase() { @Test fun constructor_usesMediaProjectionAppContentFields() { // Arrange val fakeThumbnail = createBitmap(100, 100) val fakeMediaProjectionAppContent = MediaProjectionAppContent( /* thumbnail= */ fakeThumbnail, /* title= */ "FakeTitle", /* id= */ 123, ) // Act val result = ScreenCaptureAppContent("FakePackage", fakeMediaProjectionAppContent) // Assert with(result) { assertThat(packageName).isEqualTo("FakePackage") assertThat(contentId).isEqualTo(123) assertThat(label).isEqualTo("FakeTitle") assertThat(thumbnail.sameAs(fakeThumbnail)).isTrue() } } } packages/SystemUI/src/com/android/systemui/screencapture/common/domain/interactor/ScreenCaptureAppContentInteractor.kt 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.interactor import com.android.systemui.screencapture.common.ScreenCapture import com.android.systemui.screencapture.common.ScreenCaptureScope import com.android.systemui.screencapture.common.data.repository.ScreenCaptureAppContentRepository import com.android.systemui.screencapture.common.domain.model.ScreenCaptureAppContent import com.android.systemui.screencapture.common.shared.model.ScreenCaptureUiParameters import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map /** Interactor for fetching app content info. */ @ScreenCaptureScope class ScreenCaptureAppContentInteractor @Inject constructor( private val repository: ScreenCaptureAppContentRepository, @ScreenCapture private val parameters: ScreenCaptureUiParameters, ) { /** * Fetch app content info for the given [packageName]. * * Thumbnails will be fetched at the given [thumbnailWidthPx] and [thumbnailHeightPx]. */ fun appContentsFor( packageName: String, thumbnailWidthPx: Int, thumbnailHeightPx: Int, ): Flow<Result<List<ScreenCaptureAppContent>>> = repository .appContentsFor( packageName = packageName, user = parameters.hostAppUserHandle, thumbnailWidthPx = thumbnailWidthPx, thumbnailHeightPx = thumbnailHeightPx, ) .map { appContent -> if (appContent.isFailure) { Result.failure(appContent.exceptionOrNull()!!) } else { Result.success( appContent.getOrNull()!!.map { ScreenCaptureAppContent(packageName, it) } ) } } /** * Fetch app content info for all the given [packageNames]. * * Thumbnails will be fetched at the given [thumbnailWidthPx] and [thumbnailHeightPx]. Only * includes entries for packages that have app content that was successfully fetched. */ fun appContentsFor( packageNames: List<String>, thumbnailWidthPx: Int, thumbnailHeightPx: Int, ): Flow<List<ScreenCaptureAppContent>> = combine( packageNames.distinct().map { appContentsFor( packageName = it, thumbnailWidthPx = thumbnailWidthPx, thumbnailHeightPx = thumbnailHeightPx, ) } ) { appContents -> appContents.mapNotNull { it.getOrNull() }.flatMap { it } } } packages/SystemUI/src/com/android/systemui/screencapture/common/domain/model/ScreenCaptureAppContent.kt 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.model import android.graphics.Bitmap import android.media.projection.MediaProjectionAppContent /** Collection of information about currently available app content. */ data class ScreenCaptureAppContent( val packageName: String, val contentId: Int, val label: CharSequence, val thumbnail: Bitmap, ) { constructor( packageName: String, appContent: MediaProjectionAppContent, ) : this( packageName = packageName, contentId = appContent.id, label = appContent.title, thumbnail = appContent.thumbnail, ) } packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/FakeScreenCaptureAppContentRepository.kt +29 −11 Original line number Diff line number Diff line Loading @@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.receiveAsFlow class FakeScreenCaptureAppContentRepository : ScreenCaptureAppContentRepository { private val appContentChannel = Channel<Result<List<MediaProjectionAppContent>>>(Channel.CONFLATED) private val appContentChannels = mutableMapOf<Pair<String, UserHandle>, Channel<Result<List<MediaProjectionAppContent>>>>() val appContentsForCalls = mutableListOf<AppContentsForCall>() Loading @@ -38,25 +38,43 @@ class FakeScreenCaptureAppContentRepository : ScreenCaptureAppContentRepository appContentsForCalls.add( AppContentsForCall(packageName, user, thumbnailWidthPx, thumbnailHeightPx) ) return appContentChannel.receiveAsFlow() return channelFor(packageName, user).receiveAsFlow() } fun setAppContent(appContent: Result<List<MediaProjectionAppContent>>) { appContentChannel.trySend(appContent) fun setAppContent( packageName: String, user: UserHandle, appContent: Result<List<MediaProjectionAppContent>>, ) { channelFor(packageName, user).trySend(appContent) } fun setAppContentSuccess(appContent: List<MediaProjectionAppContent>) { setAppContent(Result.success(appContent)) fun setAppContentSuccess( packageName: String, user: UserHandle, appContent: List<MediaProjectionAppContent>, ) { setAppContent(packageName, user, Result.success(appContent)) } fun setAppContentSuccess(vararg appContent: MediaProjectionAppContent) { setAppContentSuccess(appContent.toList()) fun setAppContentSuccess( packageName: String, user: UserHandle, vararg appContent: MediaProjectionAppContent, ) { setAppContentSuccess(packageName, user, appContent.toList()) } fun setAppContentFailure(throwable: Throwable) { setAppContent(Result.failure(throwable)) fun setAppContentFailure(packageName: String, user: UserHandle, throwable: Throwable) { setAppContent(packageName, user, Result.failure(throwable)) } private fun channelFor( packageName: String, user: UserHandle, ): Channel<Result<List<MediaProjectionAppContent>>> = appContentChannels.computeIfAbsent(packageName to user) { Channel(Channel.CONFLATED) } data class AppContentsForCall( val packageName: String, val user: UserHandle, Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/domain/interactor/ScreenCaptureAppContentInteractorTest.kt 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.interactor import android.media.projection.MediaProjectionAppContent import android.os.UserHandle import androidx.core.graphics.createBitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.screencapture.common.data.repository.fakeScreenCaptureAppContentRepository import com.android.systemui.screencapture.common.domain.model.ScreenCaptureAppContent import com.android.systemui.screencapture.common.shared.model.castScreenCaptureUiParameters import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.launch import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureAppContentInteractorTest : SysuiTestCase() { private val kosmos = testKosmosNew() private val fakeUserHandle = UserHandle.of(123) private val fakeMediaProjectionAppContent1 = MediaProjectionAppContent( /* thumbnail= */ createBitmap(100, 100), /* title= */ "FakeTitle1", /* id= */ 456, ) private val fakeMediaProjectionAppContent2 = MediaProjectionAppContent( /* thumbnail= */ createBitmap(200, 200), /* title= */ "FakeTitle2", /* id= */ 789, ) private val fakeThrowable = IllegalStateException("FakeMessage") @Test fun appContentsFor_singlePackage_propagatesSuccess() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: Result<List<ScreenCaptureAppContent>>? = null // Act val appContents = interactor.appContentsFor("FakePackage", 200, 150) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() fakeScreenCaptureAppContentRepository.setAppContentSuccess( packageName = "FakePackage", user = fakeUserHandle, fakeMediaProjectionAppContent1, ) // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(1) with(fakeScreenCaptureAppContentRepository.appContentsForCalls.last()) { assertThat(packageName).isEqualTo("FakePackage") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } assertThat(result?.isSuccess).isTrue() assertThat(result?.getOrNull()) .containsExactly( ScreenCaptureAppContent("FakePackage", fakeMediaProjectionAppContent1) ) // Cleanup job.cancel() } @Test fun appContentsFor_singlePackage_propagatesFailure() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: Result<List<ScreenCaptureAppContent>>? = null // Act val appContents = interactor.appContentsFor("FakePackage", 200, 150) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() fakeScreenCaptureAppContentRepository.setAppContentFailure( packageName = "FakePackage", user = fakeUserHandle, fakeThrowable, ) // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(1) with(fakeScreenCaptureAppContentRepository.appContentsForCalls.last()) { assertThat(packageName).isEqualTo("FakePackage") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } assertThat(result?.isFailure).isTrue() assertThat(result?.exceptionOrNull()).isSameInstanceAs(fakeThrowable) // Cleanup job.cancel() } @Test fun appContentsFor_multiplePackages_propagatesOnlySuccessfulFetches() = kosmos.runTest { // Arrange val interactor = ScreenCaptureAppContentInteractor( repository = fakeScreenCaptureAppContentRepository, parameters = castScreenCaptureUiParameters.copy(hostAppUserHandle = fakeUserHandle), ) var result: List<ScreenCaptureAppContent>? = null // Act val appContents = interactor.appContentsFor( listOf("FakePackage1", "FakePackage2", "FakePackage3"), 200, 150, ) val job = testScope.launch { appContents.collect { result = it } } assertThat(result).isNull() with(fakeScreenCaptureAppContentRepository) { setAppContentSuccess( packageName = "FakePackage1", user = fakeUserHandle, fakeMediaProjectionAppContent1, ) setAppContentFailure( packageName = "FakePackage2", user = fakeUserHandle, throwable = fakeThrowable, ) setAppContentSuccess( packageName = "FakePackage3", user = fakeUserHandle, fakeMediaProjectionAppContent2, ) } // Assert assertThat(fakeScreenCaptureAppContentRepository.appContentsForCalls).hasSize(3) fakeScreenCaptureAppContentRepository.appContentsForCalls.forEachIndexed { index, call -> with(call) { assertThat(packageName).isEqualTo("FakePackage${index + 1}") assertThat(user).isEqualTo(fakeUserHandle) assertThat(thumbnailWidthPx).isEqualTo(200) assertThat(thumbnailHeightPx).isEqualTo(150) } } assertThat(result) .containsExactly( ScreenCaptureAppContent("FakePackage1", fakeMediaProjectionAppContent1), ScreenCaptureAppContent("FakePackage3", fakeMediaProjectionAppContent2), ) // Cleanup job.cancel() } }
packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/domain/model/ScreenCaptureAppContentTest.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.model import android.media.projection.MediaProjectionAppContent import androidx.core.graphics.createBitmap import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureAppContentTest : SysuiTestCase() { @Test fun constructor_usesMediaProjectionAppContentFields() { // Arrange val fakeThumbnail = createBitmap(100, 100) val fakeMediaProjectionAppContent = MediaProjectionAppContent( /* thumbnail= */ fakeThumbnail, /* title= */ "FakeTitle", /* id= */ 123, ) // Act val result = ScreenCaptureAppContent("FakePackage", fakeMediaProjectionAppContent) // Assert with(result) { assertThat(packageName).isEqualTo("FakePackage") assertThat(contentId).isEqualTo(123) assertThat(label).isEqualTo("FakeTitle") assertThat(thumbnail.sameAs(fakeThumbnail)).isTrue() } } }
packages/SystemUI/src/com/android/systemui/screencapture/common/domain/interactor/ScreenCaptureAppContentInteractor.kt 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.interactor import com.android.systemui.screencapture.common.ScreenCapture import com.android.systemui.screencapture.common.ScreenCaptureScope import com.android.systemui.screencapture.common.data.repository.ScreenCaptureAppContentRepository import com.android.systemui.screencapture.common.domain.model.ScreenCaptureAppContent import com.android.systemui.screencapture.common.shared.model.ScreenCaptureUiParameters import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map /** Interactor for fetching app content info. */ @ScreenCaptureScope class ScreenCaptureAppContentInteractor @Inject constructor( private val repository: ScreenCaptureAppContentRepository, @ScreenCapture private val parameters: ScreenCaptureUiParameters, ) { /** * Fetch app content info for the given [packageName]. * * Thumbnails will be fetched at the given [thumbnailWidthPx] and [thumbnailHeightPx]. */ fun appContentsFor( packageName: String, thumbnailWidthPx: Int, thumbnailHeightPx: Int, ): Flow<Result<List<ScreenCaptureAppContent>>> = repository .appContentsFor( packageName = packageName, user = parameters.hostAppUserHandle, thumbnailWidthPx = thumbnailWidthPx, thumbnailHeightPx = thumbnailHeightPx, ) .map { appContent -> if (appContent.isFailure) { Result.failure(appContent.exceptionOrNull()!!) } else { Result.success( appContent.getOrNull()!!.map { ScreenCaptureAppContent(packageName, it) } ) } } /** * Fetch app content info for all the given [packageNames]. * * Thumbnails will be fetched at the given [thumbnailWidthPx] and [thumbnailHeightPx]. Only * includes entries for packages that have app content that was successfully fetched. */ fun appContentsFor( packageNames: List<String>, thumbnailWidthPx: Int, thumbnailHeightPx: Int, ): Flow<List<ScreenCaptureAppContent>> = combine( packageNames.distinct().map { appContentsFor( packageName = it, thumbnailWidthPx = thumbnailWidthPx, thumbnailHeightPx = thumbnailHeightPx, ) } ) { appContents -> appContents.mapNotNull { it.getOrNull() }.flatMap { it } } }
packages/SystemUI/src/com/android/systemui/screencapture/common/domain/model/ScreenCaptureAppContent.kt 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.screencapture.common.domain.model import android.graphics.Bitmap import android.media.projection.MediaProjectionAppContent /** Collection of information about currently available app content. */ data class ScreenCaptureAppContent( val packageName: String, val contentId: Int, val label: CharSequence, val thumbnail: Bitmap, ) { constructor( packageName: String, appContent: MediaProjectionAppContent, ) : this( packageName = packageName, contentId = appContent.id, label = appContent.title, thumbnail = appContent.thumbnail, ) }
packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/FakeScreenCaptureAppContentRepository.kt +29 −11 Original line number Diff line number Diff line Loading @@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.receiveAsFlow class FakeScreenCaptureAppContentRepository : ScreenCaptureAppContentRepository { private val appContentChannel = Channel<Result<List<MediaProjectionAppContent>>>(Channel.CONFLATED) private val appContentChannels = mutableMapOf<Pair<String, UserHandle>, Channel<Result<List<MediaProjectionAppContent>>>>() val appContentsForCalls = mutableListOf<AppContentsForCall>() Loading @@ -38,25 +38,43 @@ class FakeScreenCaptureAppContentRepository : ScreenCaptureAppContentRepository appContentsForCalls.add( AppContentsForCall(packageName, user, thumbnailWidthPx, thumbnailHeightPx) ) return appContentChannel.receiveAsFlow() return channelFor(packageName, user).receiveAsFlow() } fun setAppContent(appContent: Result<List<MediaProjectionAppContent>>) { appContentChannel.trySend(appContent) fun setAppContent( packageName: String, user: UserHandle, appContent: Result<List<MediaProjectionAppContent>>, ) { channelFor(packageName, user).trySend(appContent) } fun setAppContentSuccess(appContent: List<MediaProjectionAppContent>) { setAppContent(Result.success(appContent)) fun setAppContentSuccess( packageName: String, user: UserHandle, appContent: List<MediaProjectionAppContent>, ) { setAppContent(packageName, user, Result.success(appContent)) } fun setAppContentSuccess(vararg appContent: MediaProjectionAppContent) { setAppContentSuccess(appContent.toList()) fun setAppContentSuccess( packageName: String, user: UserHandle, vararg appContent: MediaProjectionAppContent, ) { setAppContentSuccess(packageName, user, appContent.toList()) } fun setAppContentFailure(throwable: Throwable) { setAppContent(Result.failure(throwable)) fun setAppContentFailure(packageName: String, user: UserHandle, throwable: Throwable) { setAppContent(packageName, user, Result.failure(throwable)) } private fun channelFor( packageName: String, user: UserHandle, ): Channel<Result<List<MediaProjectionAppContent>>> = appContentChannels.computeIfAbsent(packageName to user) { Channel(Channel.CONFLATED) } data class AppContentsForCall( val packageName: String, val user: UserHandle, Loading