1 /*
2  * 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.ComponentName
20 import android.content.ContentProvider
21 import android.content.ContentValues
22 import android.content.Context
23 import android.content.pm.PackageManager
24 import android.database.Cursor
25 import android.database.MatrixCursor
26 import android.net.Uri
27 
28 /**
29  * A content provider that serves update information for system components.
30  *
31  * This class retrieves [UpdateInfo] stored in JSON format and serves it via a content URI. It only
32  * supports the [query] operation; [insert], [delete], and [update] operations are not permitted.
33  *
34  * Typically, OTA or other update clients utilize this provider to expose update information to
35  * other applications or components within the system that need access to the latest security
36  * updates data. The client calls [UpdateInfoManager.registerUpdate] and
37  * [UpdateInfoManager.unregisterUpdate] to add or remove update information to a local store, from
38  * which the content provider serves the data to the applications.
39  *
40  * To setup the content provider add the following snippet to the client's manifest:
41  * ```
42  * <provider
43  * android:name="androidx.security.state.provider.UpdateInfoProvider"
44  * android:authorities="${applicationId}.updateinfoprovider"
45  * android:exported="true" />
46  * ```
47  */
48 public class UpdateInfoProvider : ContentProvider() {
49 
50     private var context: Context? = null
51     private lateinit var authority: String
52     private lateinit var contentUri: Uri
53 
54     /**
55      * Initializes the content provider by constructing [contentUri] using the authority listed in
56      * the manifest.
57      *
58      * @return true if the provider was successfully created, false otherwise.
59      */
onCreatenull60     override fun onCreate(): Boolean {
61         context =
62             getContext() ?: throw IllegalStateException("Cannot find context from the provider.")
63         authority = getAuthority(context!!)
64         contentUri = Uri.parse("content://$authority/updateinfo")
65         return true
66     }
67 
68     /**
69      * Handles queries for the update information.
70      *
71      * This method only responds to queries directed at the specific content URI corresponding to
72      * update data. It returns a [Cursor] containing [UpdateInfo] represented in JSON format.
73      *
74      * @param uri The URI to query. This must match the expected content URI for update data.
75      * @param projection The list of columns to put into the cursor. If null, all columns are
76      *   included.
77      * @param selection The selection criteria to apply.
78      * @param selectionArgs Arguments for the selection criteria.
79      * @param sortOrder The order in which rows are sorted in the returned Cursor.
80      * @return A [Cursor] object containing the update data.
81      * @throws IllegalArgumentException if the provided URI does not match the expected URI for
82      *   update data.
83      */
querynull84     override fun query(
85         uri: Uri,
86         projection: Array<out String>?,
87         selection: String?,
88         selectionArgs: Array<out String>?,
89         sortOrder: String?
90     ): Cursor {
91         // Verify that the caller has requested a correct URI for this provider
92         if (uri == contentUri) {
93             val updateInfoManager = UpdateInfoManager(context!!)
94             val jsonUpdates = updateInfoManager.getAllUpdatesAsJson()
95             val cursor = MatrixCursor(arrayOf("json"))
96             jsonUpdates.forEach { cursor.addRow(arrayOf(it)) }
97             return cursor
98         } else {
99             throw IllegalArgumentException("Unknown URI: $uri")
100         }
101     }
102 
103     /**
104      * Returns the MIME type of the data at the given URI. This method only handles the content URI
105      * for update data.
106      *
107      * @param uri The URI to query for its MIME type.
108      * @return The MIME type of the data at the specified URI, or null if the URI is not handled by
109      *   this provider.
110      */
getTypenull111     override fun getType(uri: Uri): String? {
112         return "vnd.android.cursor.dir/vnd.$authority.updateinfo"
113     }
114 
115     /**
116      * Unsupported operation. This method will throw an exception if called.
117      *
118      * @param uri The URI to query.
119      * @param values The new values to insert.
120      * @return nothing as this operation is not supported.
121      * @throws UnsupportedOperationException always as this operation is not supported.
122      */
insertnull123     override fun insert(uri: Uri, values: ContentValues?): Uri? {
124         throw UnsupportedOperationException("Insert operation is not supported.")
125     }
126 
127     /**
128      * Unsupported operation. This method will throw an exception if called.
129      *
130      * @param uri The URI to delete from.
131      * @param selection The selection criteria to apply.
132      * @param selectionArgs Arguments for the selection criteria.
133      * @return nothing as this operation is not supported.
134      * @throws UnsupportedOperationException always as this operation is not supported.
135      */
deletenull136     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
137         throw UnsupportedOperationException("Delete operation is not supported.")
138     }
139 
140     /**
141      * Unsupported operation. This method will throw an exception if called.
142      *
143      * @param uri The URI to update.
144      * @param values The new values to apply.
145      * @param selection The selection criteria to apply.
146      * @param selectionArgs Arguments for the selection criteria.
147      * @return nothing as this operation is not supported.
148      * @throws UnsupportedOperationException always as this operation is not supported.
149      */
updatenull150     override fun update(
151         uri: Uri,
152         values: ContentValues?,
153         selection: String?,
154         selectionArgs: Array<out String>?
155     ): Int {
156         throw UnsupportedOperationException("Update operation is not supported.")
157     }
158 
159     /**
160      * Returns [android.content.pm.ProviderInfo.authority], the authority of the provider defined in
161      * the manifest.
162      *
163      * For example, "com.example.updateinfoprovider" would be returned for the following provider:
164      * ```
165      * <provider
166      * android:name="androidx.security.state.provider.UpdateInfoProvider"
167      * android:authorities="com.example.updateinfoprovider" />
168      * ```
169      */
getAuthoritynull170     private fun getAuthority(context: Context): String {
171         return context.packageManager
172             .getProviderInfo(
173                 ComponentName(context, UpdateInfoProvider::class.java),
174                 PackageManager.GET_META_DATA
175             )
176             .authority
177     }
178 }
179