1 /*
<lambda>null2  * Copyright 2023 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 androidx.core.performance.play.services
18 
19 import android.content.Context
20 import android.util.Log
21 import androidx.core.performance.DefaultDevicePerformance
22 import androidx.core.performance.DevicePerformance
23 import androidx.datastore.core.DataStore
24 import androidx.datastore.preferences.core.PreferenceDataStoreFactory
25 import androidx.datastore.preferences.core.Preferences
26 import androidx.datastore.preferences.core.edit
27 import androidx.datastore.preferences.core.intPreferencesKey
28 import androidx.datastore.preferences.preferencesDataStoreFile
29 import com.google.android.gms.common.api.ApiException
30 import com.google.android.gms.deviceperformance.DevicePerformanceClient
31 import kotlin.math.max
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.first
34 import kotlinx.coroutines.flow.map
35 import kotlinx.coroutines.launch
36 import kotlinx.coroutines.runBlocking
37 
38 private const val MPC_PREFERENCE_KEY = "mpc_value"
39 
40 /**
41  * A DevicePerformance that uses Google Play Services to retrieve media performance class data.
42  *
43  * @param context The application context value to use.
44  */
45 public class PlayServicesDevicePerformance
46 internal constructor(
47     private val context: Context,
48     client: DevicePerformanceClient,
49     private val performanceStore: DataStore<Preferences>
50 ) : DevicePerformance {
51     private val tag = "PlayServicesDevicePerformance"
52 
53     private val defaultMpc = DefaultDevicePerformance()
54     private val mpcKey = intPreferencesKey(MPC_PREFERENCE_KEY)
55 
56     override val mediaPerformanceClass: Int
57         get() = lazyMpc.value
58 
59     private val lazyMpc = lazy {
60         runBlocking {
61             val storedMpc = getPerformanceClass().first()
62             Log.v(tag, "Stored mpc is $storedMpc")
63             Log.v(tag, "Default mpc is ${defaultMpc.mediaPerformanceClass}")
64             val returnedMpc = max(storedMpc ?: 0, defaultMpc.mediaPerformanceClass)
65             Log.v(tag, "Mpc value used $returnedMpc")
66             return@runBlocking returnedMpc
67         }
68     }
69 
70     init {
71         Log.v(
72             tag,
73             "Getting mediaPerformanceClass from " +
74                 "com.google.android.gms.deviceperformance.DevicePerformanceClient"
75         )
76         updatePerformanceStore(client)
77     }
78 
79     /**
80      * A DevicePerformance that uses Google Play Services to retrieve media performance class data.
81      *
82      * @param context The application context value to use.
83      */
84     public constructor(
85         context: Context
86     ) : this(
87         context,
88         com.google.android.gms.deviceperformance.DevicePerformance.getClient(context),
89         PreferenceDataStoreFactory.create(
90             produceFile = { context.preferencesDataStoreFile("media_performance_class") }
91         )
92     )
93 
94     private fun getPerformanceClass(): Flow<Int?> {
95         return performanceStore.data.map { values ->
96             // No type safety.
97             values[mpcKey]
98         }
99     }
100 
101     private suspend fun savePerformanceClass(value: Int) {
102         performanceStore.edit { values -> values[mpcKey] = value }
103     }
104 
105     private fun updatePerformanceStore(client: DevicePerformanceClient) {
106         client
107             .mediaPerformanceClass()
108             .addOnSuccessListener { result ->
109                 runBlocking {
110                     Log.v(tag, "Got mediaPerformanceClass $result")
111                     val storedVal = max(result, defaultMpc.mediaPerformanceClass)
112                     launch {
113                         savePerformanceClass(storedVal)
114                         Log.v(tag, "Saved mediaPerformanceClass $storedVal")
115                     }
116                 }
117             }
118             .addOnFailureListener { e: Exception ->
119                 if (e is ApiException) {
120                     Log.e(tag, "Error saving mediaPerformanceClass", e)
121                 } else if (e is IllegalStateException) {
122                     Log.e(tag, "Error saving mediaPerformanceClass", e)
123                 }
124             }
125     }
126 }
127