Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a644e6a authored by Shivangi Dubey's avatar Shivangi Dubey
Browse files

Update App handle education datastore when specific events occurs

Updates educationViewedTimestampMillis and featureUsedTimestampMillis to app handle education datastore whenever user views the education or expands app handle respectively.

Fixes: 369109780
Test: atest AppHandleEducationControllerTest
Test: atest AppHandleEducationDatastoreRepositoryTest
Flag: com.android.window.flags.enable_desktop_windowing_app_handle_education

Change-Id: Ib504957be030ae13cc9d22700aae3c9023f63064
parent 2112cf82
Loading
Loading
Loading
Loading
+27 −1
Original line number Diff line number Diff line
@@ -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
@@ -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)
            }
      }
    }
  }
@@ -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)
+31 −0
Original line number Diff line number Diff line
@@ -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
+30 −0
Original line number Diff line number Diff line
@@ -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
@@ -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() =
+18 −0
Original line number Diff line number Diff line
@@ -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"
  }