Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +11 −1 Original line number Diff line number Diff line Loading @@ -325,10 +325,15 @@ class AppHandleEducationController( /** * Listens to the changes to [WindowingEducationProto#hasEducationViewedTimestampMillis()] in * datastore proto object. * * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That means * it will emit education has not been viewed yet always. */ private fun isEducationViewedFlow(): Flow<Boolean> = appHandleEducationDatastoreRepository.dataStoreFlow .map { preferences -> preferences.hasEducationViewedTimestampMillis() } .map { preferences -> preferences.hasEducationViewedTimestampMillis() && !SHOULD_OVERRIDE_EDUCATION_CONDITIONS } .distinctUntilChanged() /** Loading @@ -352,5 +357,10 @@ class AppHandleEducationController( val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L) val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean get() = SystemProperties.getBoolean( "persist.desktop_windowing_app_handle_education_override_conditions", false) } } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt +10 −1 Original line number Diff line number Diff line Loading @@ -23,10 +23,12 @@ import android.os.SystemClock import android.provider.Settings.Secure import com.android.wm.shell.R import com.android.wm.shell.desktopmode.CaptionState import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.SHOULD_OVERRIDE_EDUCATION_CONDITIONS import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto import java.time.Duration @kotlinx.coroutines.ExperimentalCoroutinesApi /** Filters incoming app handle education triggers based on set conditions. */ class AppHandleEducationFilter( private val context: Context, Loading @@ -35,9 +37,16 @@ class AppHandleEducationFilter( private val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager /** Returns true if conditions to show app handle education are met, returns false otherwise. */ /** * Returns true if conditions to show app handle education are met, returns false otherwise. * * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return * ![captionState.isHandleMenuExpanded]. */ suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean { if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true val focusAppPackageName = captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto() Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +30 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode.education import android.os.SystemProperties import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule Loading Loading @@ -50,6 +51,7 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.any Loading @@ -70,7 +72,10 @@ class AppHandleEducationControllerTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! ExtendedMockitoRule.Builder(this) .mockStatic(DesktopModeStatus::class.java) .mockStatic(SystemProperties::class.java) .build()!! @JvmField @Rule val setFlagsRule = SetFlagsRule() private lateinit var educationController: AppHandleEducationController Loading Loading @@ -187,6 +192,29 @@ class AppHandleEducationControllerTest : ShellTestCase() { verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun overridePrerequisite_educationViewedAlready_shouldCallShowEducationTooltip() = testScope.runTest { // App handle is visible but education has been viewed before. But as we are overriding // prerequisite conditions, we should show education tooltip. // Mark education viewed. testDataStoreFlow.value = createWindowingEducationProto(educationViewedTimestampMillis = 123L) val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions" whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) .thenReturn(true) setShouldShowAppHandleEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) // Wait for first tooltip to showup. waitForBufferDelay() verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_appHandleExpanded_shouldMarkFeatureViewed() = Loading Loading @@ -454,7 +482,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { .thenReturn(shouldShowAppHandleEducation) /** * Class under test waits for some seconds before showing education, simulate advance time before * Class under test waits for some time before showing education, simulate advance time before * verifying or moving forward */ private fun TestScope.waitForBufferDelay() { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt +31 −2 Original line number Diff line number Diff line Loading @@ -19,10 +19,12 @@ package com.android.wm.shell.desktopmode.education import android.app.usage.UsageStats import android.app.usage.UsageStatsManager import android.content.Context import android.os.SystemProperties import android.testing.AndroidTestingRunner import android.testing.TestableContext import android.testing.TestableResources import androidx.test.filters.SmallTest import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.R import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository Loading @@ -35,18 +37,26 @@ import com.google.common.truth.Truth.assertThat import kotlin.Int.Companion.MAX_VALUE import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.whenever /** Tests of [AppHandleEducationFilter] * Usage: atest AppHandleEducationFilterTest */ /** Tests of [AppHandleEducationFilter] Usage: atest AppHandleEducationFilterTest */ @SmallTest @RunWith(AndroidTestingRunner::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class AppHandleEducationFilterTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!! @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository @Mock private lateinit var mockUsageStatsManager: UsageStatsManager private lateinit var educationFilter: AppHandleEducationFilter Loading Loading @@ -209,4 +219,23 @@ class AppHandleEducationFilterTest : ShellTestCase() { // We should not show app handle education if app menu is expanded assertThat(result).isFalse() } @Test fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest { // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite // conditions, #shouldShowAppHandleEducation should return true. testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions" whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())).thenReturn(true) val windowingEducationProto = createWindowingEducationProto( appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isTrue() } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +11 −1 Original line number Diff line number Diff line Loading @@ -325,10 +325,15 @@ class AppHandleEducationController( /** * Listens to the changes to [WindowingEducationProto#hasEducationViewedTimestampMillis()] in * datastore proto object. * * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That means * it will emit education has not been viewed yet always. */ private fun isEducationViewedFlow(): Flow<Boolean> = appHandleEducationDatastoreRepository.dataStoreFlow .map { preferences -> preferences.hasEducationViewedTimestampMillis() } .map { preferences -> preferences.hasEducationViewedTimestampMillis() && !SHOULD_OVERRIDE_EDUCATION_CONDITIONS } .distinctUntilChanged() /** Loading @@ -352,5 +357,10 @@ class AppHandleEducationController( val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L) val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean get() = SystemProperties.getBoolean( "persist.desktop_windowing_app_handle_education_override_conditions", false) } }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt +10 −1 Original line number Diff line number Diff line Loading @@ -23,10 +23,12 @@ import android.os.SystemClock import android.provider.Settings.Secure import com.android.wm.shell.R import com.android.wm.shell.desktopmode.CaptionState import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.SHOULD_OVERRIDE_EDUCATION_CONDITIONS import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto import java.time.Duration @kotlinx.coroutines.ExperimentalCoroutinesApi /** Filters incoming app handle education triggers based on set conditions. */ class AppHandleEducationFilter( private val context: Context, Loading @@ -35,9 +37,16 @@ class AppHandleEducationFilter( private val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager /** Returns true if conditions to show app handle education are met, returns false otherwise. */ /** * Returns true if conditions to show app handle education are met, returns false otherwise. * * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return * ![captionState.isHandleMenuExpanded]. */ suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean { if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true val focusAppPackageName = captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto() Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +30 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode.education import android.os.SystemProperties import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule Loading Loading @@ -50,6 +51,7 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.any Loading @@ -70,7 +72,10 @@ class AppHandleEducationControllerTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! ExtendedMockitoRule.Builder(this) .mockStatic(DesktopModeStatus::class.java) .mockStatic(SystemProperties::class.java) .build()!! @JvmField @Rule val setFlagsRule = SetFlagsRule() private lateinit var educationController: AppHandleEducationController Loading Loading @@ -187,6 +192,29 @@ class AppHandleEducationControllerTest : ShellTestCase() { verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun overridePrerequisite_educationViewedAlready_shouldCallShowEducationTooltip() = testScope.runTest { // App handle is visible but education has been viewed before. But as we are overriding // prerequisite conditions, we should show education tooltip. // Mark education viewed. testDataStoreFlow.value = createWindowingEducationProto(educationViewedTimestampMillis = 123L) val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions" whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) .thenReturn(true) setShouldShowAppHandleEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) // Wait for first tooltip to showup. waitForBufferDelay() verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) fun init_appHandleExpanded_shouldMarkFeatureViewed() = Loading Loading @@ -454,7 +482,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { .thenReturn(shouldShowAppHandleEducation) /** * Class under test waits for some seconds before showing education, simulate advance time before * Class under test waits for some time before showing education, simulate advance time before * verifying or moving forward */ private fun TestScope.waitForBufferDelay() { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt +31 −2 Original line number Diff line number Diff line Loading @@ -19,10 +19,12 @@ package com.android.wm.shell.desktopmode.education import android.app.usage.UsageStats import android.app.usage.UsageStatsManager import android.content.Context import android.os.SystemProperties import android.testing.AndroidTestingRunner import android.testing.TestableContext import android.testing.TestableResources import androidx.test.filters.SmallTest import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.R import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository Loading @@ -35,18 +37,26 @@ import com.google.common.truth.Truth.assertThat import kotlin.Int.Companion.MAX_VALUE import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.eq import org.mockito.kotlin.whenever /** Tests of [AppHandleEducationFilter] * Usage: atest AppHandleEducationFilterTest */ /** Tests of [AppHandleEducationFilter] Usage: atest AppHandleEducationFilterTest */ @SmallTest @RunWith(AndroidTestingRunner::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class AppHandleEducationFilterTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!! @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository @Mock private lateinit var mockUsageStatsManager: UsageStatsManager private lateinit var educationFilter: AppHandleEducationFilter Loading Loading @@ -209,4 +219,23 @@ class AppHandleEducationFilterTest : ShellTestCase() { // We should not show app handle education if app menu is expanded assertThat(result).isFalse() } @Test fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest { // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite // conditions, #shouldShowAppHandleEducation should return true. testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions" whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())).thenReturn(true) val windowingEducationProto = createWindowingEducationProto( appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isTrue() } }