• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode.education.data
18 
19 import android.content.Context
20 import android.util.Slog
21 import androidx.datastore.core.CorruptionException
22 import androidx.datastore.core.DataStore
23 import androidx.datastore.core.DataStoreFactory
24 import androidx.datastore.core.Serializer
25 import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
26 import androidx.datastore.dataStoreFile
27 import com.android.framework.protobuf.InvalidProtocolBufferException
28 import com.android.internal.annotations.VisibleForTesting
29 import java.io.IOException
30 import java.io.InputStream
31 import java.io.OutputStream
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.catch
34 import kotlinx.coroutines.flow.first
35 
36 /** Updates data in App-to-Web's education datastore. */
37 class AppToWebEducationDatastoreRepository
38 @VisibleForTesting
39 constructor(private val dataStore: DataStore<WindowingEducationProto>) {
40     constructor(
41         context: Context
42     ) : this(
43         DataStoreFactory.create(
44             serializer = WindowingEducationProtoSerializer,
45             produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) },
46             corruptionHandler =
47                 ReplaceFileCorruptionHandler(
48                     produceNewData = { WindowingEducationProto.getDefaultInstance() }
49                 ),
50         )
51     )
52 
53     /** Provides dataStore.data flow and handles exceptions thrown during collection */
54     val dataStoreFlow: Flow<WindowingEducationProto> =
55         dataStore.data.catch { exception ->
56             // dataStore.data throws an IOException when an error is encountered when reading data
57             if (exception is IOException) {
58                 Slog.e(
59                     TAG,
60                     "Error in reading App-to-Web education related data from datastore," +
61                         "data is stored in a file named" +
62                         "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH",
63                     exception,
64                 )
65             } else {
66                 throw exception
67             }
68         }
69 
70     /**
71      * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
72      * DataStore is empty or there's an error reading, it returns the default value of Proto.
73      */
74     suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
75 
76     /**
77      * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current
78      * timestamp if [isViewed] is true, if not then clears the field.
79      */
80     suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) {
81         dataStore.updateData { preferences ->
82             if (isViewed) {
83                 preferences
84                     .toBuilder()
85                     .setFeatureUsedTimestampMillis(System.currentTimeMillis())
86                     .build()
87             } else {
88                 preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
89             }
90         }
91     }
92 
93     /** Increases [AppToWebEducation.educationShownCount] field by one. */
94     suspend fun updateEducationShownCount() {
95         val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
96         currentAppHandleProto.setEducationShownCount(
97             currentAppHandleProto.getEducationShownCount() + 1
98         )
99         dataStore.updateData { preferences ->
100             preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
101         }
102     }
103 
104     companion object {
105         private const val TAG = "AppToWebEducationDatastoreRepository"
106         private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
107 
108         object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
109 
110             override val defaultValue: WindowingEducationProto =
111                 WindowingEducationProto.getDefaultInstance()
112 
113             override suspend fun readFrom(input: InputStream): WindowingEducationProto =
114                 try {
115                     WindowingEducationProto.parseFrom(input)
116                 } catch (exception: InvalidProtocolBufferException) {
117                     throw CorruptionException("Cannot read proto.", exception)
118                 }
119 
120             override suspend fun writeTo(
121                 windowingProto: WindowingEducationProto,
122                 output: OutputStream,
123             ) = windowingProto.writeTo(output)
124         }
125     }
126 }
127