DevicePerformance.kt

/*
 * Copyright 2021 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.core.performance

import android.content.Context
import android.os.Build

/**
 * Reports the media performance class of the device.
 *
 * Create an instance of DevicePerformance in your [android.app.Application.onCreate] and use
 * the [mediaPerformanceClass] value any time it is needed.
 * @sample androidx.core.performance.samples.usage
 *
 */
interface DevicePerformance {

    /**
     * The media performance class of the device or 0 if none.
     *
     * If this value is not <code>0</code>, the device conforms to the media performance class
     * definition of the SDK version of this value. This value is stable for the duration of
     * the process.
     *
     * Possible non-zero values are defined in
     * [Build.VERSION_CODES][android.os.Build.VERSION_CODES] starting with
     * [VERSION_CODES.R][android.os.Build.VERSION_CODES.R].
     *
     * Defaults to
     * [VERSION.MEDIA_PERFORMANCE_CLASS][android.os.Build.VERSION.MEDIA_PERFORMANCE_CLASS]
     *
     */
    public val mediaPerformanceClass: Int

    companion object {
        /**
         * Create PerformanceClass from the context.
         *
         * This should be done in [android.app.Application.onCreate].
         *
         * @param context The ApplicationContext.
         */
        @JvmStatic
        fun create(
            // Other implementations will require a context
            @Suppress("UNUSED_PARAMETER") context: Context
        ): DevicePerformance = DefaultDevicePerformanceImpl()
    }
}

/**
 * Reports the media performance class of the device.
 */
private class DefaultDevicePerformanceImpl : DevicePerformance {

    public override val mediaPerformanceClass: Int = calculateMediaPerformanceClass()

    companion object {

        // the next code isn't published/finalized until late
        private val PERFCLASS_11: Int = Build.VERSION_CODES.R
        private val PERFCLASS_12: Int = Build.VERSION_CODES.S
        private val PERFCLASS_13: Int = 33

        // we synthesize our own fingerprint:
        //       Brand/Product/Device:api
        // this is currently the prefix in the actual build fingerprints,
        // so it is easy to cut&paste info from there into this list.
        //
        val fingerprints: HashMap<String, Int> = hashMapOf(
            // for unit testing, no actual products with these
            "robolectric-BrandX/ProductX/Device30:11" to PERFCLASS_11,
            "robolectric-BrandX/ProductX/Device31:12" to PERFCLASS_12,

            // actual devices in the field
	    "OPPO/CPH2025EEA/OP4BA2L1:12" to PERFCLASS_11,
	    "OPPO/CPH2207EEA/OP4F0BL1:12" to PERFCLASS_11,
	    "OPPO/PENM00/OP4EC1:11" to PERFCLASS_11,
	    "OnePlus/OnePlus7TTMO/OnePlus7TTMO:11" to PERFCLASS_11,
	    "OnePlus/OnePlus8_BETA/OnePlus8:11" to PERFCLASS_11,
	    "Xiaomi/umi_global/umi:11" to PERFCLASS_11,
	    "realme/RMX2085/RMX2085L1:11" to PERFCLASS_11,
	    "samsung/c1qsqw/c1q:12" to PERFCLASS_11,
	    "samsung/o1quew/o1q:12" to PERFCLASS_11,
	    "samsung/r0quew/r0q:12" to PERFCLASS_11,
	    "samsung/r0sxxx/r0s:12" to PERFCLASS_11,

        )

        fun calculateMediaPerformanceClassFromFingerprint(): Int {

            val brand = Build.BRAND
            val product = Build.PRODUCT
            val device = Build.DEVICE
            val release = Build.VERSION.RELEASE

            val synthesized = brand + "/" + product + "/" + device + ":" + release
            var pc = fingerprints[synthesized]

            if (pc == null) {
                pc = 0
            }
            return pc
        }

        fun calculateMediaPerformanceClassFromProperty(): Int {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                return Build.VERSION.MEDIA_PERFORMANCE_CLASS
            }
            return 0
        }

        // discard illegal performance class numbers
        fun boundPC(pc: Int): Int {
            if (pc < PERFCLASS_11) {
                // performance class 11 is the first legal value for performance class
                return 0
            }
            return pc
        }

        fun calculateMediaPerformanceClass(): Int {

            // device's declared property takes precedence over our in-library table.
            //
            // what this flow doesn't let us express is a device with property PC11
            // to actually upgraded to PC12 via the table.
            //

            val viaProperty: Int = boundPC(calculateMediaPerformanceClassFromProperty())
            if (viaProperty > 0) {
                return viaProperty
            }

            val viaFingerprint: Int = calculateMediaPerformanceClassFromFingerprint()

            return boundPC(viaFingerprint)
        }
    }
}