1 /* 2 * Copyright 2022 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 package com.google.android.bluetooth 17 18 import android.content.ContentProvider 19 import android.content.ContentValues 20 import android.content.Context 21 import android.content.UriMatcher 22 import android.database.Cursor 23 import android.database.sqlite.SQLiteDatabase 24 import android.net.Uri 25 import android.os.Bundle 26 import android.util.Log 27 28 /** 29 * Define an implementation of ContentProvider for the Bluetooth migration 30 */ 31 class BluetoothLegacyMigration: ContentProvider() { 32 companion object { 33 private const val TAG = "BluetoothLegacyMigration" 34 35 private const val AUTHORITY = "bluetooth_legacy.provider" 36 37 private const val START_LEGACY_MIGRATION_CALL = "start_legacy_migration" 38 private const val FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration" 39 40 private const val PHONEBOOK_ACCESS_PERMISSION = "phonebook_access_permission" 41 private const val MESSAGE_ACCESS_PERMISSION = "message_access_permission" 42 private const val SIM_ACCESS_PERMISSION = "sim_access_permission" 43 44 private const val VOLUME_MAP = "bluetooth_volume_map" 45 46 private const val OPP = "OPPMGR" 47 private const val BLUETOOTH_OPP_CHANNEL = "btopp_channels" 48 private const val BLUETOOTH_OPP_NAME = "btopp_names" 49 50 private const val BLUETOOTH_SIGNED_DEFAULT = "com.google.android.bluetooth_preferences" 51 52 private const val KEY_LIST = "key_list" 53 54 private enum class UriId( 55 val fileName: String, 56 val handler: (ctx: Context) -> DatabaseHandler 57 ) { 58 BLUETOOTH(BluetoothDatabase.DATABASE_NAME, ::BluetoothDatabase), 59 OPP(OppDatabase.DATABASE_NAME, ::OppDatabase), 60 } 61 <lambda>null62 private val URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH).apply { 63 UriId.values().map { addURI(AUTHORITY, it.fileName, it.ordinal) } 64 } 65 putObjectInBundlenull66 private fun putObjectInBundle(bundle: Bundle, key: String, obj: Any?) { 67 when (obj) { 68 is Boolean -> bundle.putBoolean(key, obj) 69 is Int -> bundle.putInt(key, obj) 70 is Long -> bundle.putLong(key, obj) 71 is String -> bundle.putString(key, obj) 72 null -> throw UnsupportedOperationException("null type is not handled") 73 else -> throw UnsupportedOperationException("${obj.javaClass.simpleName}: type is not handled") 74 } 75 } 76 } 77 78 private lateinit var mContext: Context 79 80 /** 81 * Always return true, indicating that the 82 * provider loaded correctly. 83 */ onCreatenull84 override fun onCreate(): Boolean { 85 mContext = context!!.createDeviceProtectedStorageContext() 86 return true 87 } 88 89 /** 90 * Use a content URI to get database name associated 91 * 92 * @param uri Content uri 93 * @return A {@link Cursor} containing the results of the query. 94 */ getTypenull95 override fun getType(uri: Uri): String { 96 val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) } 97 ?: throw UnsupportedOperationException("This Uri is not supported: $uri") 98 return database.fileName 99 } 100 101 /** 102 * Use a content URI to get information about a database 103 * 104 * @param uri Content uri 105 * @param projection unused 106 * @param selection unused 107 * @param selectionArgs unused 108 * @param sortOrder unused 109 * @return A {@link Cursor} containing the results of the query. 110 * 111 */ 112 @Override querynull113 override fun query( 114 uri: Uri, 115 projection: Array<String>?, 116 selection: String?, 117 selectionArgs: Array<String>?, 118 sortOrder: String? 119 ): Cursor? { 120 val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) } 121 ?: throw UnsupportedOperationException("This Uri is not supported: $uri") 122 return database.handler(mContext).toCursor() 123 } 124 125 /** 126 * insert() is not supported 127 */ insertnull128 override fun insert(uri: Uri, values: ContentValues?): Uri? { 129 throw UnsupportedOperationException() 130 } 131 132 /** 133 * delete() is not supported 134 */ deletenull135 override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { 136 throw UnsupportedOperationException() 137 } 138 139 /** 140 * update() is not supported 141 */ updatenull142 override fun update( 143 uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>? 144 ): Int { 145 throw UnsupportedOperationException() 146 } 147 148 abstract class MigrationHandler { toBundlenull149 abstract fun toBundle(): Bundle? 150 abstract fun delete() 151 } 152 153 private class SharedPreferencesHandler(private val ctx: Context, private val key: String) : 154 MigrationHandler() { 155 156 override fun toBundle(): Bundle? { 157 val pref = ctx.getSharedPreferences(key, Context.MODE_PRIVATE) 158 if (pref.all.isEmpty()) { 159 Log.d(TAG, "No migration needed for shared preference: $key") 160 return null 161 } 162 val bundle = Bundle() 163 val keys = arrayListOf<String>() 164 for (e in pref.all) { 165 keys += e.key 166 putObjectInBundle(bundle, e.key, e.value) 167 } 168 bundle.putStringArrayList(KEY_LIST, keys) 169 Log.d(TAG, "SharedPreferences migrating ${keys.size} key(s) from $key") 170 return bundle 171 } 172 173 override fun delete() { 174 ctx.deleteSharedPreferences(key) 175 Log.d(TAG, "$key: SharedPreferences deleted") 176 } 177 } 178 179 abstract class DatabaseHandler(private val ctx: Context, private val dbName: String) : 180 MigrationHandler() { 181 182 abstract val sql: String 183 toCursornull184 fun toCursor(): Cursor? { 185 val databasePath = ctx.getDatabasePath(dbName) 186 if (!databasePath.exists()) { 187 Log.d(TAG, "No migration needed for database: $dbName") 188 return null 189 } 190 val db = SQLiteDatabase.openDatabase( 191 databasePath, 192 SQLiteDatabase.OpenParams.Builder().addOpenFlags(SQLiteDatabase.OPEN_READONLY) 193 .build() 194 ) 195 return db.rawQuery(sql, null) 196 } 197 toBundlenull198 override fun toBundle(): Bundle? { 199 throw UnsupportedOperationException() 200 } 201 deletenull202 override fun delete() { 203 val databasePath = ctx.getDatabasePath(dbName) 204 databasePath.delete() 205 Log.d(TAG, "$dbName: database deleted") 206 } 207 } 208 209 private class BluetoothDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) { 210 companion object { 211 const val DATABASE_NAME = "bluetooth_db" 212 } 213 private val dbTable = "metadata" 214 override val sql = "select * from $dbTable" 215 } 216 217 private class OppDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) { 218 companion object { 219 const val DATABASE_NAME = "btopp.db" 220 } 221 private val dbTable = "btopp" 222 override val sql = "select * from $dbTable" 223 } 224 225 /** 226 * Fetch legacy data describe by {@code arg} and perform {@code method} action on it 227 * 228 * @param method Action to perform. One of START_LEGACY_MIGRATION_CALL|FINISH_LEGACY_MIGRATION_CALL 229 * @param arg item on witch to perform the action specified by {@code method} 230 * @param extras unused 231 * @return A {@link Bundle} containing the results of the query. 232 */ callnull233 override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { 234 val migrationHandler = when (arg) { 235 OPP, 236 VOLUME_MAP, 237 BLUETOOTH_OPP_NAME, 238 BLUETOOTH_OPP_CHANNEL, 239 SIM_ACCESS_PERMISSION, 240 MESSAGE_ACCESS_PERMISSION, 241 PHONEBOOK_ACCESS_PERMISSION -> SharedPreferencesHandler(mContext, arg) 242 BLUETOOTH_SIGNED_DEFAULT -> { 243 val key = mContext.packageName + "_preferences" 244 SharedPreferencesHandler(mContext, key) 245 } 246 BluetoothDatabase.DATABASE_NAME -> BluetoothDatabase(mContext) 247 OppDatabase.DATABASE_NAME -> OppDatabase(mContext) 248 else -> throw UnsupportedOperationException() 249 } 250 return when (method) { 251 START_LEGACY_MIGRATION_CALL -> migrationHandler.toBundle() 252 FINISH_LEGACY_MIGRATION_CALL -> { 253 migrationHandler.delete() 254 return null 255 } 256 else -> throw UnsupportedOperationException() 257 } 258 } 259 } 260