/* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.security.state.provider import android.content.ComponentName import android.content.ContentProvider import android.content.ContentValues import android.content.Context import android.content.pm.PackageManager import android.database.Cursor import android.database.MatrixCursor import android.net.Uri /** * A content provider that serves update information for system components. * * This class retrieves [UpdateInfo] stored in JSON format and serves it via a content URI. It only * supports the [query] operation; [insert], [delete], and [update] operations are not permitted. * * Typically, OTA or other update clients utilize this provider to expose update information to * other applications or components within the system that need access to the latest security * updates data. The client calls [UpdateInfoManager.registerUpdate] and * [UpdateInfoManager.unregisterUpdate] to add or remove update information to a local store, from * which the content provider serves the data to the applications. * * To setup the content provider add the following snippet to the client's manifest: * ``` * * ``` */ public class UpdateInfoProvider : ContentProvider() { private var context: Context? = null private lateinit var authority: String private lateinit var contentUri: Uri /** * Initializes the content provider by constructing [contentUri] using the authority listed in * the manifest. * * @return true if the provider was successfully created, false otherwise. */ override fun onCreate(): Boolean { context = getContext() ?: throw IllegalStateException("Cannot find context from the provider.") authority = getAuthority(context!!) contentUri = Uri.parse("content://$authority/updateinfo") return true } /** * Handles queries for the update information. * * This method only responds to queries directed at the specific content URI corresponding to * update data. It returns a [Cursor] containing [UpdateInfo] represented in JSON format. * * @param uri The URI to query. This must match the expected content URI for update data. * @param projection The list of columns to put into the cursor. If null, all columns are * included. * @param selection The selection criteria to apply. * @param selectionArgs Arguments for the selection criteria. * @param sortOrder The order in which rows are sorted in the returned Cursor. * @return A [Cursor] object containing the update data. * @throws IllegalArgumentException if the provided URI does not match the expected URI for * update data. */ override fun query( uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String? ): Cursor { // Verify that the caller has requested a correct URI for this provider if (uri == contentUri) { val updateInfoManager = UpdateInfoManager(context!!) val jsonUpdates = updateInfoManager.getAllUpdatesAsJson() val cursor = MatrixCursor(arrayOf("json")) jsonUpdates.forEach { cursor.addRow(arrayOf(it)) } return cursor } else { throw IllegalArgumentException("Unknown URI: $uri") } } /** * Returns the MIME type of the data at the given URI. This method only handles the content URI * for update data. * * @param uri The URI to query for its MIME type. * @return The MIME type of the data at the specified URI, or null if the URI is not handled by * this provider. */ override fun getType(uri: Uri): String? { return "vnd.android.cursor.dir/vnd.$authority.updateinfo" } /** * Unsupported operation. This method will throw an exception if called. * * @param uri The URI to query. * @param values The new values to insert. * @return nothing as this operation is not supported. * @throws UnsupportedOperationException always as this operation is not supported. */ override fun insert(uri: Uri, values: ContentValues?): Uri? { throw UnsupportedOperationException("Insert operation is not supported.") } /** * Unsupported operation. This method will throw an exception if called. * * @param uri The URI to delete from. * @param selection The selection criteria to apply. * @param selectionArgs Arguments for the selection criteria. * @return nothing as this operation is not supported. * @throws UnsupportedOperationException always as this operation is not supported. */ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { throw UnsupportedOperationException("Delete operation is not supported.") } /** * Unsupported operation. This method will throw an exception if called. * * @param uri The URI to update. * @param values The new values to apply. * @param selection The selection criteria to apply. * @param selectionArgs Arguments for the selection criteria. * @return nothing as this operation is not supported. * @throws UnsupportedOperationException always as this operation is not supported. */ override fun update( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array? ): Int { throw UnsupportedOperationException("Update operation is not supported.") } /** * Returns [android.content.pm.ProviderInfo.authority], the authority of the provider defined in * the manifest. * * For example, "com.example.updateinfoprovider" would be returned for the following provider: * ``` * * ``` */ private fun getAuthority(context: Context): String { return context.packageManager .getProviderInfo( ComponentName(context, UpdateInfoProvider::class.java), PackageManager.GET_META_DATA ) .authority } }