summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt553
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt210
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt32
6 files changed, 560 insertions, 504 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index de9c79ab34fd..c5fca028a1a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -36,8 +36,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.Theme
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
-import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainCoroutineDispatcher
@@ -74,293 +74,330 @@ class AppHandleEducationController(
@ShellMainThread private val applicationCoroutineScope: CoroutineScope,
@ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
) {
- private val decorThemeUtil = DecorThemeUtil(context)
- private lateinit var openHandleMenuCallback: (Int) -> Unit
- private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private lateinit var openHandleMenuCallback: (Int) -> Unit
+ private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+
+ init {
+ runIfEducationFeatureEnabled {
+ applicationCoroutineScope.launch {
+ // Central block handling the app handle's educational flow end-to-end.
+ isAppHandleHintViewedFlow()
+ .flatMapLatest { isAppHandleHintViewed ->
+ if (isAppHandleHintViewed) {
+ // If the education is viewed then return emptyFlow() that completes
+ // immediately.
+ // This will help us to not listen to [captionHandleStateFlow] after the
+ // education
+ // has been viewed already.
+ emptyFlow()
+ } else {
+ // Listen for changes to window decor's caption handle.
+ windowDecorCaptionHandleRepository.captionStateFlow
+ // Wait for few seconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ captionState is CaptionState.AppHandle &&
+ appHandleEducationFilter.shouldShowAppHandleEducation(
+ captionState
+ )
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .collectLatest { captionState ->
+ val tooltipColorScheme = tooltipColorScheme(captionState)
+
+ showEducation(captionState, tooltipColorScheme)
+ // After showing first tooltip, mark education as viewed
+ appHandleEducationDatastoreRepository
+ .updateAppHandleHintViewedTimestampMillis(true)
+ }
+ }
- init {
- runIfEducationFeatureEnabled {
- applicationCoroutineScope.launch {
- // Central block handling the app handle's educational flow end-to-end.
- isAppHandleHintViewedFlow()
- .flatMapLatest { isAppHandleHintViewed ->
- if (isAppHandleHintViewed) {
- // If the education is viewed then return emptyFlow() that completes immediately.
- // This will help us to not listen to [captionHandleStateFlow] after the education
- // has been viewed already.
- emptyFlow()
- } else {
- // Listen for changes to window decor's caption handle.
+ applicationCoroutineScope.launch {
+ if (isAppHandleHintUsed()) return@launch
windowDecorCaptionHandleRepository.captionStateFlow
- // Wait for few seconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
- captionState is CaptionState.AppHandle &&
- appHandleEducationFilter.shouldShowAppHandleEducation(captionState)
+ captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
}
- }
+ .take(1)
+ .flowOn(backgroundDispatcher)
+ .collect {
+ // If user expands app handle, mark user has used the app handle hint
+ appHandleEducationDatastoreRepository
+ .updateAppHandleHintUsedTimestampMillis(true)
+ }
+ }
+ }
+ }
+
+ private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+ if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation())
+ block()
+ }
+
+ private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
+ val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
+ val tooltipGlobalCoordinates =
+ Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
+ // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the
+ // cue.
+ // Populate information important to inflate app handle education tooltip.
+ val appHandleTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
+ onEducationClickAction = {
+ launchWithExceptionHandling {
+ showWindowingImageButtonTooltip(tooltipColorScheme)
+ }
+ openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ },
+ onDismissAction = {
+ launchWithExceptionHandling {
+ showWindowingImageButtonTooltip(tooltipColorScheme)
+ }
+ },
+ )
+
+ windowingEducationViewController.showEducationTooltip(
+ tooltipViewConfig = appHandleTooltipConfig,
+ taskId = captionState.runningTaskInfo.taskId,
+ )
+ }
+
+ /** Show tooltip that points to windowing image button in app handle menu */
+ private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
+ val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
+ val windowingOptionPillHeight =
+ getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ val appHandleMenuWidth =
+ getSize(R.dimen.desktop_mode_handle_menu_width) +
+ getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ val appHandleMenuMargins =
+ getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
+ getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+
+ windowDecorCaptionHandleRepository.captionStateFlow
+ // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
+ // has been expanded.
+ .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+ .catchTimeoutAndLog {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ }
+ // Wait for few milliseconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ // Filter out states when app handle is not visible or not expanded.
+ captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
}
+ // Before showing this tooltip, stop listening to further emissions to avoid
+ // accidentally
+ // showing the same tooltip on future emissions.
+ .take(1)
.flowOn(backgroundDispatcher)
.collectLatest { captionState ->
- val tooltipColorScheme = tooltipColorScheme(captionState)
+ captionState as CaptionState.AppHandle
+ val appHandleBounds = captionState.globalAppHandleBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
+ appHandleBounds.top +
+ appHandleMenuMargins +
+ appInfoPillHeight +
+ windowingOptionPillHeight / 2,
+ )
+ // Populate information important to inflate windowing image button education
+ // tooltip.
+ val windowingImageButtonTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText =
+ getString(
+ R.string.windowing_desktop_mode_image_button_education_tooltip
+ ),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onEducationClickAction = {
+ launchWithExceptionHandling {
+ showExitWindowingTooltip(tooltipColorScheme)
+ }
+ toDesktopModeCallback(
+ captionState.runningTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
+ )
+ },
+ onDismissAction = {
+ launchWithExceptionHandling {
+ showExitWindowingTooltip(tooltipColorScheme)
+ }
+ },
+ )
- showEducation(captionState, tooltipColorScheme)
- // After showing first tooltip, mark education as viewed
- appHandleEducationDatastoreRepository.updateAppHandleHintViewedTimestampMillis(true)
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = windowingImageButtonTooltipConfig,
+ )
}
- }
+ }
- applicationCoroutineScope.launch {
- if (isAppHandleHintUsed()) return@launch
+ /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
+ private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
windowDecorCaptionHandleRepository.captionStateFlow
+ // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
+ // desktop mode.
+ .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+ .catchTimeoutAndLog {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ }
+ // Wait for few milliseconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+ // Filter out states when app header is not visible or expanded.
+ captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
}
+ // Before showing this tooltip, stop listening to further emissions to avoid
+ // accidentally
+ // showing the same tooltip on future emissions.
.take(1)
.flowOn(backgroundDispatcher)
- .collect {
- // If user expands app handle, mark user has used the app handle hint
- appHandleEducationDatastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
+ .collectLatest { captionState ->
+ captionState as CaptionState.AppHeader
+ val globalAppChipBounds = captionState.globalAppChipBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ globalAppChipBounds.right,
+ globalAppChipBounds.top + globalAppChipBounds.height() / 2,
+ )
+ // Populate information important to inflate exit desktop mode education tooltip.
+ val exitWindowingTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText =
+ getString(R.string.windowing_desktop_mode_exit_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onDismissAction = {},
+ onEducationClickAction = {
+ openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ },
+ )
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = exitWindowingTooltipConfig,
+ )
}
- }
}
- }
-
- private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
- if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation()) block()
- }
-
- private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
- val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
- val tooltipGlobalCoordinates =
- Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
- // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue.
- // Populate information important to inflate app handle education tooltip.
- val appHandleTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
- arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
- onEducationClickAction = {
- launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
- },
- onDismissAction = {
- launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
- },
- )
-
- windowingEducationViewController.showEducationTooltip(
- tooltipViewConfig = appHandleTooltipConfig, taskId = captionState.runningTaskInfo.taskId)
- }
-
- /** Show tooltip that points to windowing image button in app handle menu */
- private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
- val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
- val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
- val appHandleMenuWidth =
- getSize(R.dimen.desktop_mode_handle_menu_width) +
- getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
- val appHandleMenuMargins =
- getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
- getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
-
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
- // has been expanded.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app handle is not visible or not expanded.
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHandle
- val appHandleBounds = captionState.globalAppHandleBounds
- val tooltipGlobalCoordinates =
- Point(
- appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
- appHandleBounds.top +
- appHandleMenuMargins +
- appInfoPillHeight +
- windowingOptionPillHeight / 2)
- // Populate information important to inflate windowing image button education tooltip.
- val windowingImageButtonTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText =
- getString(R.string.windowing_desktop_mode_image_button_education_tooltip),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onEducationClickAction = {
- launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
- toDesktopModeCallback(
- captionState.runningTaskInfo.taskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
- },
- onDismissAction = {
- launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
- },
- )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = windowingImageButtonTooltipConfig)
- }
- }
+ private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
+ context.withStyledAttributes(
+ set = null,
+ attrs =
+ intArrayOf(
+ com.android.internal.R.attr.materialColorOnTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixedDim,
+ ),
+ defStyleAttr = 0,
+ defStyleRes = 0,
+ ) {
+ val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
+ val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
+ val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
+ val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
- /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
- private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
- // desktop mode.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app header is not visible or expanded.
- captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHeader
- val globalAppChipBounds = captionState.globalAppChipBounds
- val tooltipGlobalCoordinates =
- Point(
- globalAppChipBounds.right,
- globalAppChipBounds.top + globalAppChipBounds.height() / 2)
- // Populate information important to inflate exit desktop mode education tooltip.
- val exitWindowingTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onDismissAction = {},
- onEducationClickAction = {
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
- },
- )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = exitWindowingTooltipConfig,
- )
+ val tooltipContainerColor =
+ if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
+ tertiaryFixed
+ } else {
+ tertiaryFixedDim
+ }
+ return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
}
- }
+ return TooltipColorScheme(0, 0, 0)
+ }
- private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
- context.withStyledAttributes(
- set = null,
- attrs =
- intArrayOf(
- com.android.internal.R.attr.materialColorOnTertiaryFixed,
- com.android.internal.R.attr.materialColorTertiaryFixed,
- com.android.internal.R.attr.materialColorTertiaryFixedDim),
- defStyleAttr = 0,
- defStyleRes = 0) {
- val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
- val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
- val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
- val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
+ /**
+ * Setup callbacks for app handle education tooltips.
+ *
+ * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
+ * @param toDesktopModeCallback callback invoked to move task into desktop mode.
+ */
+ fun setAppHandleEducationTooltipCallbacks(
+ openHandleMenuCallback: (taskId: Int) -> Unit,
+ toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit,
+ ) {
+ this.openHandleMenuCallback = openHandleMenuCallback
+ this.toDesktopModeCallback = toDesktopModeCallback
+ }
- val tooltipContainerColor =
- if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
- tertiaryFixed
- } else {
- tertiaryFixedDim
- }
- return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
+ private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
+ catch { exception ->
+ if (exception is TimeoutCancellationException) block() else throw exception
}
- return TooltipColorScheme(0, 0, 0)
- }
- /**
- * Setup callbacks for app handle education tooltips.
- *
- * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
- * @param toDesktopModeCallback callback invoked to move task into desktop mode.
- */
- fun setAppHandleEducationTooltipCallbacks(
- openHandleMenuCallback: (taskId: Int) -> Unit,
- toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit
- ) {
- this.openHandleMenuCallback = openHandleMenuCallback
- this.toDesktopModeCallback = toDesktopModeCallback
- }
-
- private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
- catch { exception ->
- if (exception is TimeoutCancellationException) block() else throw exception
- }
-
- private fun launchWithExceptionHandling(block: suspend () -> Unit) =
- applicationCoroutineScope.launch {
- try {
- block()
- } catch (e: Throwable) {
- Slog.e(TAG, "Error: ", e)
+ private fun launchWithExceptionHandling(block: suspend () -> Unit) =
+ applicationCoroutineScope.launch {
+ try {
+ block()
+ } catch (e: Throwable) {
+ Slog.e(TAG, "Error: ", e)
+ }
}
- }
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()] in
- * datastore proto object.
- *
- * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That means
- * it will always emit app handle hint has not been viewed yet.
- */
- private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
- appHandleEducationDatastoreRepository.dataStoreFlow
- .map { preferences ->
- preferences.hasAppHandleHintViewedTimestampMillis() && !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
- }
- .distinctUntilChanged()
+ /**
+ * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()]
+ * in datastore proto object.
+ *
+ * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That
+ * means it will always emit app handle hint has not been viewed yet.
+ */
+ private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .map { preferences ->
+ preferences.hasAppHandleHintViewedTimestampMillis() &&
+ !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
+ }
+ .distinctUntilChanged()
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
- * datastore proto object.
- */
- private suspend fun isAppHandleHintUsed(): Boolean =
- appHandleEducationDatastoreRepository.dataStoreFlow.first().hasAppHandleHintUsedTimestampMillis()
+ /**
+ * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
+ * datastore proto object.
+ */
+ private suspend fun isAppHandleHintUsed(): Boolean =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .first()
+ .hasAppHandleHintUsedTimestampMillis()
- private fun getSize(@DimenRes resourceId: Int): Int {
- if (resourceId == Resources.ID_NULL) return 0
- return context.resources.getDimensionPixelSize(resourceId)
- }
+ private fun getSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) return 0
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
- private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+ private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
- companion object {
- const val TAG = "AppHandleEducationController"
- val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
- get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
+ companion object {
+ const val TAG = "AppHandleEducationController"
+ val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
- val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
- get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
+ 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)
- }
+ val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
+ get() =
+ SystemProperties.getBoolean(
+ "persist.desktop_windowing_app_handle_education_override_conditions",
+ false,
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
index 7a7829334fb6..9990846fc92e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
@@ -32,106 +32,114 @@ import java.time.Duration
/** Filters incoming app handle education triggers based on set conditions. */
class AppHandleEducationFilter(
private val context: Context,
- private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository
+ private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository,
) {
- 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.
- *
- * 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()
-
- return isFocusAppInAllowlist(focusAppPackageName) &&
- !isOtherEducationShowing() &&
- hasSufficientTimeSinceSetup() &&
- !isAppHandleHintViewedBefore(windowingEducationProto) &&
- !isAppHandleHintUsedBefore(windowingEducationProto) &&
- hasMinAppUsage(windowingEducationProto, focusAppPackageName)
- }
-
- private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
- focusAppPackageName in
- context.resources.getStringArray(
- R.array.desktop_windowing_app_handle_education_allowlist_apps)
-
- // TODO: b/350953004 - Add checks based on App compat
- // TODO: b/350951797 - Add checks based on PKT tips education
- private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
-
- private fun isTaskbarEducationShowing(): Boolean =
- Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
-
- private fun hasSufficientTimeSinceSetup(): Boolean =
- Duration.ofMillis(SystemClock.elapsedRealtime()) >
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_required_time_since_setup_seconds)
-
- private fun isAppHandleHintViewedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
- windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
-
- private fun isAppHandleHintUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
- windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
-
- private suspend fun hasMinAppUsage(
- windowingEducationProto: WindowingEducationProto,
- focusAppPackageName: String
- ): Boolean =
- (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
- context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
-
- private suspend fun launchCountByPackageName(
- windowingEducationProto: WindowingEducationProto
- ): Map<String, Int> =
- if (isAppUsageCacheStale(windowingEducationProto)) {
- // Query and return user stats, update cache in datastore
- getAndCacheAppUsageStats()
- } else {
- // Return cached usage stats
- windowingEducationProto.appHandleEducation.appUsageStatsMap
- }
-
- private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
- val currentTime = currentTimeInDuration()
- val lastUpdateTime =
- Duration.ofMillis(
- windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis)
- val appUsageStatsCachingInterval =
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_app_usage_cache_interval_seconds)
- return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
- }
-
- private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
- val currentTime = currentTimeInDuration()
- val appUsageStats = queryAppUsageStats()
- appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
- return appUsageStats
- }
-
- private fun queryAppUsageStats(): Map<String, Int> {
- val endTime = currentTimeInDuration()
- val appLaunchInterval =
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_app_launch_interval_seconds)
- val startTime = endTime - appLaunchInterval
-
- return usageStatsManager
- .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
- .mapValues { it.value.appLaunchCount }
- }
-
- private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
- Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
-
- private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
+ 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.
+ *
+ * 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()
+
+ return isFocusAppInAllowlist(focusAppPackageName) &&
+ !isOtherEducationShowing() &&
+ hasSufficientTimeSinceSetup() &&
+ !isAppHandleHintViewedBefore(windowingEducationProto) &&
+ !isAppHandleHintUsedBefore(windowingEducationProto) &&
+ hasMinAppUsage(windowingEducationProto, focusAppPackageName)
+ }
+
+ private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
+ focusAppPackageName in
+ context.resources.getStringArray(
+ R.array.desktop_windowing_app_handle_education_allowlist_apps
+ )
+
+ // TODO: b/350953004 - Add checks based on App compat
+ // TODO: b/350951797 - Add checks based on PKT tips education
+ private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
+
+ private fun isTaskbarEducationShowing(): Boolean =
+ Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
+
+ private fun hasSufficientTimeSinceSetup(): Boolean =
+ Duration.ofMillis(SystemClock.elapsedRealtime()) >
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds
+ )
+
+ private fun isAppHandleHintViewedBefore(
+ windowingEducationProto: WindowingEducationProto
+ ): Boolean = windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
+
+ private fun isAppHandleHintUsedBefore(
+ windowingEducationProto: WindowingEducationProto
+ ): Boolean = windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
+
+ private suspend fun hasMinAppUsage(
+ windowingEducationProto: WindowingEducationProto,
+ focusAppPackageName: String,
+ ): Boolean =
+ (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
+ context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
+
+ private suspend fun launchCountByPackageName(
+ windowingEducationProto: WindowingEducationProto
+ ): Map<String, Int> =
+ if (isAppUsageCacheStale(windowingEducationProto)) {
+ // Query and return user stats, update cache in datastore
+ getAndCacheAppUsageStats()
+ } else {
+ // Return cached usage stats
+ windowingEducationProto.appHandleEducation.appUsageStatsMap
+ }
+
+ private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
+ val currentTime = currentTimeInDuration()
+ val lastUpdateTime =
+ Duration.ofMillis(
+ windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis
+ )
+ val appUsageStatsCachingInterval =
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_app_usage_cache_interval_seconds
+ )
+ return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
+ }
+
+ private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
+ val currentTime = currentTimeInDuration()
+ val appUsageStats = queryAppUsageStats()
+ appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
+ return appUsageStats
+ }
+
+ private fun queryAppUsageStats(): Map<String, Int> {
+ val endTime = currentTimeInDuration()
+ val appLaunchInterval =
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_app_launch_interval_seconds
+ )
+ val startTime = endTime - appLaunchInterval
+
+ return usageStatsManager
+ .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
+ .mapValues { it.value.appLaunchCount }
+ }
+
+ private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
+ Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
+
+ private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
index 693da81ec4a0..bfe1b12c9605 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -53,8 +53,8 @@ import kotlinx.coroutines.launch
/**
* Controls App-to-Web education end to end.
*
- * Listen to usages of App-to-Web, calls an api to check if the education
- * should be shown and controls education UI.
+ * Listen to usages of App-to-Web, calls an api to check if the education should be shown and
+ * controls education UI.
*/
@OptIn(kotlinx.coroutines.FlowPreview::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -88,8 +88,9 @@ class AppToWebEducationController(
.debounce(APP_TO_WEB_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
captionState !is CaptionState.NoCaption &&
- appToWebEducationFilter
- .shouldShowAppToWebEducation(captionState)
+ appToWebEducationFilter.shouldShowAppToWebEducation(
+ captionState
+ )
}
}
}
@@ -104,12 +105,12 @@ class AppToWebEducationController(
applicationCoroutineScope.launch {
if (isFeatureUsed()) return@launch
- windowDecorCaptionHandleRepository.appToWebUsageFlow
- .collect {
- // If user utilizes App-to-Web, mark user has used the feature
- appToWebEducationDatastoreRepository
- .updateFeatureUsedTimestampMillis(isViewed = true)
- }
+ windowDecorCaptionHandleRepository.appToWebUsageFlow.collect {
+ // If user utilizes App-to-Web, mark user has used the feature
+ appToWebEducationDatastoreRepository.updateFeatureUsedTimestampMillis(
+ isViewed = true
+ )
+ }
}
}
}
@@ -126,10 +127,8 @@ class AppToWebEducationController(
val appHandleBounds = captionState.globalAppHandleBounds
val educationWidth =
loadDimensionPixelSize(R.dimen.desktop_windowing_education_promo_width)
- educationGlobalCoordinates = Point(
- appHandleBounds.centerX() - educationWidth / 2,
- appHandleBounds.bottom
- )
+ educationGlobalCoordinates =
+ Point(appHandleBounds.centerX() - educationWidth / 2, appHandleBounds.bottom)
taskId = captionState.runningTaskInfo.taskId
}
@@ -152,19 +151,22 @@ class AppToWebEducationController(
viewGlobalCoordinates = educationGlobalCoordinates,
educationText = getString(R.string.desktop_windowing_app_to_web_education_text),
widthId = R.dimen.desktop_windowing_education_promo_width,
- heightId = R.dimen.desktop_windowing_education_promo_height
+ heightId = R.dimen.desktop_windowing_education_promo_height,
)
windowingEducationViewController.showEducation(
- viewConfig = educationConfig, taskId = taskId)
+ viewConfig = educationConfig,
+ taskId = taskId,
+ )
}
private fun educationColorScheme(captionState: CaptionState): EducationColorScheme? {
- val taskInfo: RunningTaskInfo = when (captionState) {
- is CaptionState.AppHandle -> captionState.runningTaskInfo
- is CaptionState.AppHeader -> captionState.runningTaskInfo
- else -> return null
- }
+ val taskInfo: RunningTaskInfo =
+ when (captionState) {
+ is CaptionState.AppHandle -> captionState.runningTaskInfo
+ is CaptionState.AppHeader -> captionState.runningTaskInfo
+ else -> return null
+ }
val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
val tooltipContainerColor = colorScheme.surfaceBright.toArgb()
@@ -178,8 +180,7 @@ class AppToWebEducationController(
*/
private fun isEducationViewLimitReachedFlow(): Flow<Boolean> =
appToWebEducationDatastoreRepository.dataStoreFlow
- .map { preferences ->
- appToWebEducationFilter.isEducationViewLimitReached(preferences)}
+ .map { preferences -> appToWebEducationFilter.isEducationViewLimitReached(preferences) }
.distinctUntilChanged()
/**
@@ -199,9 +200,6 @@ class AppToWebEducationController(
companion object {
const val TAG = "AppToWebEducationController"
val APP_TO_WEB_EDUCATION_DELAY_MILLIS: Long
- get() = SystemProperties.getLong(
- "persist.windowing_app_handle_education_delay",
- 3000L
- )
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
index feee6ed86da1..e272b54dd907 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
@@ -30,39 +30,41 @@ import java.time.Duration
/** Filters incoming App-to-Web education triggers based on set conditions. */
class AppToWebEducationFilter(
private val context: Context,
- private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository
+ private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository,
) {
/** Returns true if conditions to show App-to-web education are met, returns false otherwise. */
suspend fun shouldShowAppToWebEducation(captionState: CaptionState): Boolean {
- val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) = when (captionState) {
- is CaptionState.AppHandle ->
- Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
- is CaptionState.AppHeader ->
- Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
- else -> return false
- }
+ val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) =
+ when (captionState) {
+ is CaptionState.AppHandle ->
+ Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+ is CaptionState.AppHeader ->
+ Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+ else -> return false
+ }
val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
val windowingEducationProto = appToWebEducationDatastoreRepository.windowingEducationProto()
return !isOtherEducationShowing() &&
- !isEducationViewLimitReached(windowingEducationProto) &&
- hasSufficientTimeSinceSetup() &&
- !isFeatureUsedBefore(windowingEducationProto) &&
- isCapturedLinkAvailable &&
- isFocusAppInAllowlist(focusAppPackageName)
+ !isEducationViewLimitReached(windowingEducationProto) &&
+ hasSufficientTimeSinceSetup() &&
+ !isFeatureUsedBefore(windowingEducationProto) &&
+ isCapturedLinkAvailable &&
+ isFocusAppInAllowlist(focusAppPackageName)
}
private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
focusAppPackageName in
- context.resources.getStringArray(
- R.array.desktop_windowing_app_to_web_education_allowlist_apps)
+ context.resources.getStringArray(
+ R.array.desktop_windowing_app_to_web_education_allowlist_apps
+ )
// TODO: b/350953004 - Add checks based on App compat
// TODO: b/350951797 - Add checks based on PKT tips education
- private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing() ||
- isCompatUiEducationShowing()
+ private fun isOtherEducationShowing(): Boolean =
+ isTaskbarEducationShowing() || isCompatUiEducationShowing()
private fun isTaskbarEducationShowing(): Boolean =
Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
@@ -72,13 +74,14 @@ class AppToWebEducationFilter(
private fun hasSufficientTimeSinceSetup(): Boolean =
Duration.ofMillis(SystemClock.elapsedRealtime()) >
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds
+ )
/** Returns true if education is viewed maximum amount of times it should be shown. */
fun isEducationViewLimitReached(windowingEducationProto: WindowingEducationProto): Boolean =
windowingEducationProto.getAppToWebEducation().getEducationShownCount() >=
- MAXIMUM_TIMES_EDUCATION_SHOWN
+ MAXIMUM_TIMES_EDUCATION_SHOWN
private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
windowingEducationProto.hasFeatureUsedTimestampMillis()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index 5e0c0007e2eb..3e120b09a0b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -42,103 +42,109 @@ import kotlinx.coroutines.flow.first
class AppHandleEducationDatastoreRepository
@VisibleForTesting
constructor(private val dataStore: DataStore<WindowingEducationProto>) {
- constructor(
- context: Context
- ) : this(
- DataStoreFactory.create(
- serializer = WindowingEducationProtoSerializer,
- produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
+ constructor(
+ context: Context
+ ) : this(
+ DataStoreFactory.create(
+ serializer = WindowingEducationProtoSerializer,
+ produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) },
+ )
+ )
- /** Provides dataStore.data flow and handles exceptions thrown during collection */
- val dataStoreFlow: Flow<WindowingEducationProto> =
- dataStore.data.catch { exception ->
- // dataStore.data throws an IOException when an error is encountered when reading data
- if (exception is IOException) {
- Log.e(
- TAG,
- "Error in reading app handle education related data from datastore, data is " +
- "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
- exception)
- } else {
- throw exception
+ /** Provides dataStore.data flow and handles exceptions thrown during collection */
+ val dataStoreFlow: Flow<WindowingEducationProto> =
+ dataStore.data.catch { exception ->
+ // dataStore.data throws an IOException when an error is encountered when reading data
+ if (exception is IOException) {
+ Log.e(
+ TAG,
+ "Error in reading app handle education related data from datastore, data is " +
+ "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
+ exception,
+ )
+ } else {
+ throw exception
+ }
}
- }
- /**
- * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
- * DataStore is empty or there's an error reading, it returns the default value of Proto.
- */
- suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
+ /**
+ * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
+ * DataStore is empty or there's an error reading, it returns the default value of Proto.
+ */
+ suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
- /**
- * Updates [WindowingEducationProto.appHandleHintViewedTimestampMillis_] field
- * in datastore with current timestamp if [isViewed] is true, if not then
- * clears the field.
- */
- suspend fun updateAppHandleHintViewedTimestampMillis(isViewed: Boolean) {
- dataStore.updateData { preferences ->
- if (isViewed) {
- preferences
- .toBuilder()
- .setAppHandleHintViewedTimestampMillis(System.currentTimeMillis())
- .build()
- } else {
- preferences.toBuilder().clearAppHandleHintViewedTimestampMillis().build()
- }
+ /**
+ * Updates [WindowingEducationProto.appHandleHintViewedTimestampMillis_] field in datastore with
+ * current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateAppHandleHintViewedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setAppHandleHintViewedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearAppHandleHintViewedTimestampMillis().build()
+ }
+ }
}
- }
- /**
- * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field
- * in datastore with current timestamp if [isViewed] is true, if not then
- * clears the field.
- */
- suspend fun updateAppHandleHintUsedTimestampMillis(isViewed: Boolean) {
- dataStore.updateData { preferences ->
- if (isViewed) {
- preferences.toBuilder().setAppHandleHintUsedTimestampMillis(System.currentTimeMillis()).build()
- } else {
- preferences.toBuilder().clearAppHandleHintUsedTimestampMillis().build()
- }
+ /**
+ * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field in datastore with
+ * current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateAppHandleHintUsedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setAppHandleHintUsedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearAppHandleHintUsedTimestampMillis().build()
+ }
+ }
}
- }
- /**
- * Updates [AppHandleEducation.appUsageStats] and
- * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
- * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
- */
- suspend fun updateAppUsageStats(
- appUsageStats: Map<String, Int>,
- appUsageStatsLastUpdateTimestamp: Duration
- ) {
- val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
- currentAppHandleProto
- .putAllAppUsageStats(appUsageStats)
- .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
- dataStore.updateData { preferences: WindowingEducationProto ->
- preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+ /**
+ * Updates [AppHandleEducation.appUsageStats] and
+ * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
+ * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
+ */
+ suspend fun updateAppUsageStats(
+ appUsageStats: Map<String, Int>,
+ appUsageStatsLastUpdateTimestamp: Duration,
+ ) {
+ val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
+ currentAppHandleProto
+ .putAllAppUsageStats(appUsageStats)
+ .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
+ dataStore.updateData { preferences: WindowingEducationProto ->
+ preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+ }
}
- }
- companion object {
- private const val TAG = "AppHandleEducationDatastoreRepository"
- private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
+ companion object {
+ private const val TAG = "AppHandleEducationDatastoreRepository"
+ private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
- object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+ object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
- override val defaultValue: WindowingEducationProto =
- WindowingEducationProto.getDefaultInstance()
+ override val defaultValue: WindowingEducationProto =
+ WindowingEducationProto.getDefaultInstance()
- override suspend fun readFrom(input: InputStream): WindowingEducationProto =
- try {
- WindowingEducationProto.parseFrom(input)
- } catch (exception: InvalidProtocolBufferException) {
- throw CorruptionException("Cannot read proto.", exception)
- }
+ override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+ try {
+ WindowingEducationProto.parseFrom(input)
+ } catch (exception: InvalidProtocolBufferException) {
+ throw CorruptionException("Cannot read proto.", exception)
+ }
- override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) =
- windowingProto.writeTo(output)
+ override suspend fun writeTo(
+ windowingProto: WindowingEducationProto,
+ output: OutputStream,
+ ) = windowingProto.writeTo(output)
+ }
}
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
index 8be6e6dff6fe..e5ad901d1435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
@@ -25,12 +25,12 @@ import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import com.android.framework.protobuf.InvalidProtocolBufferException
import com.android.internal.annotations.VisibleForTesting
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.first
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
/** Updates data in App-to-Web's education datastore. */
class AppToWebEducationDatastoreRepository
@@ -41,7 +41,9 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
) : this(
DataStoreFactory.create(
serializer = WindowingEducationProtoSerializer,
- produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) }))
+ produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) },
+ )
+ )
/** Provides dataStore.data flow and handles exceptions thrown during collection */
val dataStoreFlow: Flow<WindowingEducationProto> =
@@ -51,8 +53,10 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
Slog.e(
TAG,
"Error in reading App-to-Web education related data from datastore," +
- "data is stored in a file named" +
- "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH", exception)
+ "data is stored in a file named" +
+ "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH",
+ exception,
+ )
} else {
throw exception
}
@@ -72,26 +76,26 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
dataStore.updateData { preferences ->
if (isViewed) {
preferences
- .toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+ .toBuilder()
+ .setFeatureUsedTimestampMillis(System.currentTimeMillis())
+ .build()
} else {
preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
}
}
}
- /**
- * Increases [AppToWebEducation.educationShownCount] field by one.
- */
+ /** Increases [AppToWebEducation.educationShownCount] field by one. */
suspend fun updateEducationShownCount() {
val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
- currentAppHandleProto
- .setEducationShownCount(currentAppHandleProto.getEducationShownCount() + 1)
+ currentAppHandleProto.setEducationShownCount(
+ currentAppHandleProto.getEducationShownCount() + 1
+ )
dataStore.updateData { preferences ->
preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
}
}
-
companion object {
private const val TAG = "AppToWebEducationDatastoreRepository"
private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
@@ -110,7 +114,7 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
override suspend fun writeTo(
windowingProto: WindowingEducationProto,
- output: OutputStream
+ output: OutputStream,
) = windowingProto.writeTo(output)
}
}