Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +27 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map Loading Loading @@ -95,7 +96,25 @@ class AppHandleEducationController( } } .flowOn(backgroundDispatcher) .collectLatest { captionState -> showEducation(captionState) } .collectLatest { captionState -> showEducation(captionState) // After showing first tooltip, mark education as viewed appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true) } } applicationCoroutineScope.launch { if (isFeatureUsed()) return@launch windowDecorCaptionHandleRepository.captionStateFlow .filter { captionState -> captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded } .take(1) .flowOn(backgroundDispatcher) .collect { // If user expands app handle, mark user has used the feature appHandleEducationDatastoreRepository.updateFeatureUsedTimestampMillis(true) } } } } Loading Loading @@ -272,6 +291,13 @@ class AppHandleEducationController( .map { preferences -> preferences.hasEducationViewedTimestampMillis() } .distinctUntilChanged() /** * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in * datastore proto object. */ private suspend fun isFeatureUsed(): Boolean = appHandleEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis() private fun getSize(@DimenRes resourceId: Int): Int { if (resourceId == Resources.ID_NULL) return 0 return context.resources.getDimensionPixelSize(resourceId) Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt +31 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,37 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) { */ suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first() /** * Updates [WindowingEducationProto.educationViewedTimestampMillis_] field in datastore with * current timestamp if [isViewed] is true, if not then clears the field. */ suspend fun updateEducationViewedTimestampMillis(isViewed: Boolean) { dataStore.updateData { preferences -> if (isViewed) { preferences .toBuilder() .setEducationViewedTimestampMillis(System.currentTimeMillis()) .build() } else { preferences.toBuilder().clearEducationViewedTimestampMillis().build() } } } /** * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current * timestamp if [isViewed] is true, if not then clears the field. */ suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) { dataStore.updateData { preferences -> if (isViewed) { preferences.toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build() } else { preferences.toBuilder().clearFeatureUsedTimestampMillis().build() } } } /** * Updates [AppHandleEducation.appUsageStats] and * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +30 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times Loading Loading @@ -188,6 +189,35 @@ class AppHandleEducationControllerTest : ShellTestCase() { verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_appHandleExpanded_shouldMarkFeatureViewed() = testScope.runTest { setShouldShowAppHandleEducation(false) // Simulate app handle visible and expanded. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) // Wait for some time before verifying waitForBufferDelay() verify(mockDataStoreRepository, times(1)).updateFeatureUsedTimestampMillis(eq(true)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_showFirstTooltip_shouldMarkEducationViewed() = testScope.runTest { // App handle is visible. Should show education tooltip. setShouldShowAppHandleEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState() // Wait for first tooltip to showup. waitForBufferDelay() verify(mockDataStoreRepository, times(1)).updateEducationViewedTimestampMillis(eq(true)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt +18 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,24 @@ class AppHandleEducationDatastoreRepositoryTest { assertThat(result).isEqualTo(windowingEducationProto) } @Test fun updateEducationViewedTimestampMillis_updatesDatastoreProto() = runTest(StandardTestDispatcher()) { datastoreRepository.updateEducationViewedTimestampMillis(true) val result = testDatastore.data.first().hasEducationViewedTimestampMillis() assertThat(result).isEqualTo(true) } @Test fun updateFeatureUsedTimestampMillis_updatesDatastoreProto() = runTest(StandardTestDispatcher()) { datastoreRepository.updateFeatureUsedTimestampMillis(true) val result = testDatastore.data.first().hasFeatureUsedTimestampMillis() assertThat(result).isEqualTo(true) } companion object { private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +27 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map Loading Loading @@ -95,7 +96,25 @@ class AppHandleEducationController( } } .flowOn(backgroundDispatcher) .collectLatest { captionState -> showEducation(captionState) } .collectLatest { captionState -> showEducation(captionState) // After showing first tooltip, mark education as viewed appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true) } } applicationCoroutineScope.launch { if (isFeatureUsed()) return@launch windowDecorCaptionHandleRepository.captionStateFlow .filter { captionState -> captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded } .take(1) .flowOn(backgroundDispatcher) .collect { // If user expands app handle, mark user has used the feature appHandleEducationDatastoreRepository.updateFeatureUsedTimestampMillis(true) } } } } Loading Loading @@ -272,6 +291,13 @@ class AppHandleEducationController( .map { preferences -> preferences.hasEducationViewedTimestampMillis() } .distinctUntilChanged() /** * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in * datastore proto object. */ private suspend fun isFeatureUsed(): Boolean = appHandleEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis() private fun getSize(@DimenRes resourceId: Int): Int { if (resourceId == Resources.ID_NULL) return 0 return context.resources.getDimensionPixelSize(resourceId) Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt +31 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,37 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) { */ suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first() /** * Updates [WindowingEducationProto.educationViewedTimestampMillis_] field in datastore with * current timestamp if [isViewed] is true, if not then clears the field. */ suspend fun updateEducationViewedTimestampMillis(isViewed: Boolean) { dataStore.updateData { preferences -> if (isViewed) { preferences .toBuilder() .setEducationViewedTimestampMillis(System.currentTimeMillis()) .build() } else { preferences.toBuilder().clearEducationViewedTimestampMillis().build() } } } /** * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current * timestamp if [isViewed] is true, if not then clears the field. */ suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) { dataStore.updateData { preferences -> if (isViewed) { preferences.toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build() } else { preferences.toBuilder().clearFeatureUsedTimestampMillis().build() } } } /** * Updates [AppHandleEducation.appUsageStats] and * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +30 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times Loading Loading @@ -188,6 +189,35 @@ class AppHandleEducationControllerTest : ShellTestCase() { verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_appHandleExpanded_shouldMarkFeatureViewed() = testScope.runTest { setShouldShowAppHandleEducation(false) // Simulate app handle visible and expanded. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) // Wait for some time before verifying waitForBufferDelay() verify(mockDataStoreRepository, times(1)).updateFeatureUsedTimestampMillis(eq(true)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_showFirstTooltip_shouldMarkEducationViewed() = testScope.runTest { // App handle is visible. Should show education tooltip. setShouldShowAppHandleEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState() // Wait for first tooltip to showup. waitForBufferDelay() verify(mockDataStoreRepository, times(1)).updateEducationViewedTimestampMillis(eq(true)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt +18 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,24 @@ class AppHandleEducationDatastoreRepositoryTest { assertThat(result).isEqualTo(windowingEducationProto) } @Test fun updateEducationViewedTimestampMillis_updatesDatastoreProto() = runTest(StandardTestDispatcher()) { datastoreRepository.updateEducationViewedTimestampMillis(true) val result = testDatastore.data.first().hasEducationViewedTimestampMillis() assertThat(result).isEqualTo(true) } @Test fun updateFeatureUsedTimestampMillis_updatesDatastoreProto() = runTest(StandardTestDispatcher()) { datastoreRepository.updateFeatureUsedTimestampMillis(true) val result = testDatastore.data.first().hasFeatureUsedTimestampMillis() assertThat(result).isEqualTo(true) } companion object { private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" } Loading