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