Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f8e3f67 authored by William Escande's avatar William Escande Committed by Android (Google) Code Review
Browse files

Merge changes from topics "bt_migration_mainline_tm", "bt_qpr_cherry_pick" into tm-qpr-dev

* changes:
  Load legacy Bluetooth user data at start
  Follow error prone recommendation
  floss: Migration should only occur on Android
  Make the btservices apex updatable.
parents 564cd4a3 c95a8453
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -38,6 +38,6 @@
  <!-- Only run tests in MTS if the Bluetooth Mainline module is installed. -->
  <object type="module_controller"
          class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
      <option name="mainline-module-package-name" value="com.google.android.bluetooth" />
      <option name="mainline-module-package-name" value="com.android.btservices" />
  </object>
</configuration>
+14 −0
Original line number Diff line number Diff line
package {
    // See: http://go/android-license-faq
    default_applicable_licenses: ["Android-Apache-2.0"],
}

android_app {
    name: "BluetoothLegacyMigration",

    srcs: [ "BluetoothLegacyMigration.kt" ],

    // Must match Bluetooth.apk certificate because of sharedUserId
    certificate: ":com.android.bluetooth.certificate",
    platform_apis: true,
}
+20 −0
Original line number Diff line number Diff line
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.bluetooth"
    android:sharedUserId="android.uid.bluetooth">

    <!-- This "legacy" instance is retained on the device to preserve the
        database contents before Bluetooth was migrated into a Mainline module.
        This ensures that we can migrate information to new folder app -->
<application
    android:icon="@mipmap/bt_share"
    android:allowBackup="false"
    android:label="Bluetooth Legacy">
        <provider
            android:name="com.google.android.bluetooth.BluetoothLegacyMigration"
            android:authorities="bluetooth_legacy.provider"
            android:directBootAware="true"
            android:exported="false"
            android:permission="android.permission.BLUETOOTH_PRIVILEGED"
            />
    </application>
</manifest>
+259 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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 com.google.android.bluetooth

import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.UriMatcher
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import android.os.Bundle
import android.util.Log

/**
 * Define an implementation of ContentProvider for the Bluetooth migration
 */
class BluetoothLegacyMigration: ContentProvider() {
    companion object {
        private const val TAG = "BluetoothLegacyMigration"

        private const val AUTHORITY = "bluetooth_legacy.provider"

        private const val START_LEGACY_MIGRATION_CALL = "start_legacy_migration"
        private const val FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration"

        private const val PHONEBOOK_ACCESS_PERMISSION = "phonebook_access_permission"
        private const val MESSAGE_ACCESS_PERMISSION = "message_access_permission"
        private const val SIM_ACCESS_PERMISSION = "sim_access_permission"

        private const val VOLUME_MAP = "bluetooth_volume_map"

        private const val OPP = "OPPMGR"
        private const val BLUETOOTH_OPP_CHANNEL = "btopp_channels"
        private const val BLUETOOTH_OPP_NAME = "btopp_names"

        private const val BLUETOOTH_SIGNED_DEFAULT = "com.google.android.bluetooth_preferences"

        private const val KEY_LIST = "key_list"

        private enum class UriId(
            val fileName: String,
            val handler: (ctx: Context) -> DatabaseHandler
        ) {
            BLUETOOTH(BluetoothDatabase.DATABASE_NAME, ::BluetoothDatabase),
            OPP(OppDatabase.DATABASE_NAME, ::OppDatabase),
        }

        private val URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH).apply {
            UriId.values().map { addURI(AUTHORITY, it.fileName, it.ordinal) }
        }

        private fun putObjectInBundle(bundle: Bundle, key: String, obj: Any?) {
            when (obj) {
                is Boolean -> bundle.putBoolean(key, obj)
                is Int -> bundle.putInt(key, obj)
                is Long -> bundle.putLong(key, obj)
                is String -> bundle.putString(key, obj)
                null -> throw UnsupportedOperationException("null type is not handled")
                else -> throw UnsupportedOperationException("${obj.javaClass.simpleName}: type is not handled")
            }
        }
    }

    private lateinit var mContext: Context

    /**
     * Always return true, indicating that the
     * provider loaded correctly.
     */
    override fun onCreate(): Boolean {
        mContext = context!!.createDeviceProtectedStorageContext()
        return true
    }

    /**
     * Use a content URI to get database name associated
     *
     * @param uri Content uri
     * @return A {@link Cursor} containing the results of the query.
     */
    override fun getType(uri: Uri): String {
        val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
            ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
        return database.fileName
    }

    /**
     * Use a content URI to get information about a database
     *
     * @param uri Content uri
     * @param projection unused
     * @param selection unused
     * @param selectionArgs unused
     * @param sortOrder unused
     * @return A {@link Cursor} containing the results of the query.
     *
     */
    @Override
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
            ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
        return database.handler(mContext).toCursor()
    }

    /**
     * insert() is not supported
     */
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        throw UnsupportedOperationException()
    }

    /**
     * delete() is not supported
     */
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        throw UnsupportedOperationException()
    }

    /**
     * update() is not supported
     */
    override fun update(
        uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?
    ): Int {
        throw UnsupportedOperationException()
    }

    abstract class MigrationHandler {
        abstract fun toBundle(): Bundle?
        abstract fun delete()
    }

    private class SharedPreferencesHandler(private val ctx: Context, private val key: String) :
        MigrationHandler() {

        override fun toBundle(): Bundle? {
            val pref = ctx.getSharedPreferences(key, Context.MODE_PRIVATE)
            if (pref.all.isEmpty()) {
                Log.d(TAG, "No migration needed for shared preference: $key")
                return null
            }
            val bundle = Bundle()
            val keys = arrayListOf<String>()
            for (e in pref.all) {
                keys += e.key
                putObjectInBundle(bundle, e.key, e.value)
            }
            bundle.putStringArrayList(KEY_LIST, keys)
            Log.d(TAG, "SharedPreferences migrating ${keys.size} key(s) from $key")
            return bundle
        }

        override fun delete() {
            ctx.deleteSharedPreferences(key)
            Log.d(TAG, "$key: SharedPreferences deleted")
        }
    }

    abstract class DatabaseHandler(private val ctx: Context, private val dbName: String) :
        MigrationHandler() {

        abstract val sql: String

        fun toCursor(): Cursor? {
            val databasePath = ctx.getDatabasePath(dbName)
            if (!databasePath.exists()) {
                Log.d(TAG, "No migration needed for database: $dbName")
                return null
            }
            val db = SQLiteDatabase.openDatabase(
                databasePath,
                SQLiteDatabase.OpenParams.Builder().addOpenFlags(SQLiteDatabase.OPEN_READONLY)
                    .build()
            )
            return db.rawQuery(sql, null)
        }

        override fun toBundle(): Bundle? {
            throw UnsupportedOperationException()
        }

        override fun delete() {
            val databasePath = ctx.getDatabasePath(dbName)
            databasePath.delete()
            Log.d(TAG, "$dbName: database deleted")
        }
    }

    private class BluetoothDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
        companion object {
            const val DATABASE_NAME = "bluetooth_db"
        }
        private val dbTable = "metadata"
        override val sql = "select * from $dbTable"
    }

    private class OppDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
        companion object {
            const val DATABASE_NAME = "btopp.db"
        }
        private val dbTable = "btopp"
        override val sql = "select * from $dbTable"
    }

    /**
     * Fetch legacy data describe by {@code arg} and perform {@code method} action on it
     *
     * @param method Action to perform. One of START_LEGACY_MIGRATION_CALL|FINISH_LEGACY_MIGRATION_CALL
     * @param arg item on witch to perform the action specified by {@code method}
     * @param extras unused
     * @return A {@link Bundle} containing the results of the query.
     */
    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
        val migrationHandler = when (arg) {
            OPP,
            VOLUME_MAP,
            BLUETOOTH_OPP_NAME,
            BLUETOOTH_OPP_CHANNEL,
            SIM_ACCESS_PERMISSION,
            MESSAGE_ACCESS_PERMISSION,
            PHONEBOOK_ACCESS_PERMISSION -> SharedPreferencesHandler(mContext, arg)
            BLUETOOTH_SIGNED_DEFAULT -> {
                val key = mContext.packageName + "_preferences"
                SharedPreferencesHandler(mContext, key)
            }
            BluetoothDatabase.DATABASE_NAME -> BluetoothDatabase(mContext)
            OppDatabase.DATABASE_NAME -> OppDatabase(mContext)
            else -> throw UnsupportedOperationException()
        }
        return when (method) {
            START_LEGACY_MIGRATION_CALL -> migrationHandler.toBundle()
            FINISH_LEGACY_MIGRATION_CALL -> {
                migrationHandler.delete()
                return null
            }
            else -> throw UnsupportedOperationException()
        }
    }
}
+8 −0
Original line number Diff line number Diff line
# Reviewers for /android/BluetoothLegacyMigration

eruffieux@google.com
rahulsabnis@google.com
sattiraju@google.com
siyuanh@google.com
wescande@google.com
zachoverflow@google.com
Loading