1 /*
<lambda>null2  * Copyright 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 androidx.security.state.provider
18 
19 import android.content.Context
20 import androidx.security.state.SecurityPatchState
21 import androidx.security.state.SecurityPatchState.Companion.getComponentSecurityPatchLevel
22 import kotlinx.serialization.json.Json
23 
24 /**
25  * This class interfaces with a [SecurityPatchState] to manage update information for system
26  * components.
27  *
28  * Typically, OTA or other update clients utilize this class to expose update information to other
29  * applications or components within the system that need access to the latest security updates
30  * data. The client calls [registerUpdate] to add [UpdateInfo] to a local store and
31  * [unregisterUpdate] to remove it. [UpdateInfoProvider] then serves this data to the applications.
32  */
33 public class UpdateInfoManager(
34     private val context: Context,
35     customSecurityState: SecurityPatchState? = null
36 ) {
37 
38     private val updateInfoPrefs: String = "UPDATE_INFO_PREFS"
39     private var securityState: SecurityPatchState =
40         customSecurityState ?: SecurityPatchState(context)
41 
42     /**
43      * Registers information about an available update for the specified component.
44      *
45      * @param updateInfo Update information structure.
46      */
47     public fun registerUpdate(updateInfo: UpdateInfo) {
48         cleanupUpdateInfo()
49 
50         val sharedPreferences = context.getSharedPreferences(updateInfoPrefs, Context.MODE_PRIVATE)
51         val editor = sharedPreferences?.edit()
52         val key = getKeyForUpdateInfo(updateInfo)
53         val json =
54             Json.encodeToString(
55                 SerializableUpdateInfo.serializer(),
56                 updateInfo.toSerializableUpdateInfo()
57             )
58         editor?.putString(key, json)
59         editor?.apply()
60     }
61 
62     /**
63      * Unregisters information about an available update for the specified component.
64      *
65      * @param updateInfo Update information structure.
66      */
67     public fun unregisterUpdate(updateInfo: UpdateInfo) {
68         cleanupUpdateInfo()
69 
70         val sharedPreferences = context.getSharedPreferences(updateInfoPrefs, Context.MODE_PRIVATE)
71         val editor = sharedPreferences?.edit()
72         val key = getKeyForUpdateInfo(updateInfo)
73         editor?.remove(key)
74         editor?.apply()
75     }
76 
77     /**
78      * Cleans up outdated or applied updates from the shared preferences. This method checks each
79      * registered update against the current device security patch levels and removes any updates
80      * that are no longer relevant (i.e., the update's patch level is less than or equal to the
81      * current device patch level).
82      */
83     private fun cleanupUpdateInfo() {
84         val allUpdates = getAllUpdates()
85         val sharedPreferences = context.getSharedPreferences(updateInfoPrefs, Context.MODE_PRIVATE)
86         val editor = sharedPreferences?.edit() ?: return
87 
88         allUpdates.forEach { updateInfo ->
89             val component = updateInfo.component
90             val currentSpl: SecurityPatchState.SecurityPatchLevel
91             try {
92                 currentSpl = securityState.getDeviceSecurityPatchLevel(component)
93             } catch (e: IllegalArgumentException) {
94                 // Ignore unknown components.
95                 return@forEach
96             }
97             val updateSpl = getComponentSecurityPatchLevel(component, updateInfo.securityPatchLevel)
98 
99             if (updateSpl <= currentSpl) {
100                 val key = getKeyForUpdateInfo(updateInfo)
101                 editor.remove(key)
102             }
103         }
104 
105         editor.apply()
106     }
107 
108     private fun getKeyForUpdateInfo(updateInfo: UpdateInfo): String {
109         // Create a unique key for each update info.
110         return "${updateInfo.component}-${updateInfo.uri}"
111     }
112 
113     /**
114      * Retrieves a list of all updates currently registered in the system's shared preferences. This
115      * method is primarily used for managing and tracking updates that have been registered but not
116      * yet applied or acknowledged by the system.
117      *
118      * @return A list of [UpdateInfo] objects, each representing a registered update.
119      */
120     private fun getAllUpdates(): List<UpdateInfo> {
121         val allUpdates = mutableListOf<UpdateInfo>()
122         for (json in getAllUpdatesAsJson()) {
123             val serializableUpdateInfo: SerializableUpdateInfo = Json.decodeFromString(json)
124             val updateInfo: UpdateInfo = serializableUpdateInfo.toUpdateInfo()
125             allUpdates.add(updateInfo)
126         }
127         return allUpdates
128     }
129 
130     /**
131      * Retrieves all registered updates in JSON format from the system's shared preferences.
132      *
133      * @return A list of strings, each representing an update in JSON format.
134      */
135     internal fun getAllUpdatesAsJson(): List<String> {
136         val allUpdates = mutableListOf<String>()
137         val sharedPreferences = context.getSharedPreferences(updateInfoPrefs, Context.MODE_PRIVATE)
138         val allEntries = sharedPreferences?.all ?: return emptyList()
139         for ((_, value) in allEntries) {
140             val json = value as? String
141             if (json != null) {
142                 allUpdates.add(json)
143             }
144         }
145         return allUpdates
146     }
147 }
148