LoadSdkCompatException.kt

/*
 * 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 androidx.privacysandbox.sdkruntime.core

import android.app.sdksandbox.LoadSdkException
import android.os.Bundle
import android.os.ext.SdkExtensions.AD_SERVICES
import androidx.annotation.DoNotInline
import androidx.annotation.IntDef
import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP

/**
 * Compat alternative for [LoadSdkException].
 * Thrown from [SandboxedSdkProviderCompat.onLoadSdk].
 *
 * @see [LoadSdkException]
 */
class LoadSdkCompatException : Exception {

    /**
     * Result code this exception was constructed with.
     *
     * @see [LoadSdkException.getLoadSdkErrorCode]
     */
    @field:LoadSdkErrorCode
    @get:LoadSdkErrorCode
    val loadSdkErrorCode: Int

    /**
     * Extra error information this exception was constructed with.
     *
     * @see [LoadSdkException.getExtraInformation]
     */
    val extraInformation: Bundle

    /**
     * Initializes a LoadSdkCompatException with a result code, a message, a cause and extra
     * information.
     *
     * @param loadSdkErrorCode The result code.
     * @param message The detailed message.
     * @param cause The cause of the exception. A null value is permitted, and indicates that the
     *  cause is nonexistent or unknown.
     * @param extraInformation Extra error information. This is empty if there is no such information.
     * @suppress
     */
    @RestrictTo(LIBRARY_GROUP)
    @JvmOverloads
    constructor(
        @LoadSdkErrorCode loadSdkErrorCode: Int,
        message: String?,
        cause: Throwable?,
        extraInformation: Bundle = Bundle()
    ) : super(message, cause) {
        this.loadSdkErrorCode = loadSdkErrorCode
        this.extraInformation = extraInformation
    }

    /**
     * Initializes a LoadSdkCompatException with a result code and a message
     *
     * @param loadSdkErrorCode The result code.
     * @param message The detailed message.
     * @suppress
     */
    @RestrictTo(LIBRARY_GROUP)
    constructor(
        @LoadSdkErrorCode loadSdkErrorCode: Int,
        message: String?
    ) : this(loadSdkErrorCode, message, cause = null)

    /**
     * Initializes a LoadSdkCompatException with a Throwable and a Bundle.
     *
     * @param cause The cause of the exception.
     * @param extraInfo Extra error information. This is empty if there is no such information.
     */
    constructor(
        cause: Throwable,
        extraInfo: Bundle
    ) : this(LOAD_SDK_SDK_DEFINED_ERROR, "", cause, extraInfo)

    /** @suppress */
    @IntDef(
        SDK_SANDBOX_PROCESS_NOT_AVAILABLE,
        LOAD_SDK_NOT_FOUND,
        LOAD_SDK_ALREADY_LOADED,
        LOAD_SDK_SDK_DEFINED_ERROR,
        LOAD_SDK_SDK_SANDBOX_DISABLED,
        LOAD_SDK_INTERNAL_ERROR,
    )
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(AnnotationRetention.SOURCE)
    annotation class LoadSdkErrorCode

    /**
     *  Create platform [LoadSdkException] from compat exception.
     *
     *  @return Platform exception.
     */
    @RequiresExtension(extension = AD_SERVICES, version = 4)
    internal fun toLoadSdkException(): LoadSdkException {
        return ApiAdServicesV4Impl.toLoadSdkException(this)
    }

    @RequiresExtension(extension = AD_SERVICES, version = 4)
    private object ApiAdServicesV4Impl {

        @DoNotInline
        fun toLoadSdkException(ex: LoadSdkCompatException): LoadSdkException {
            return LoadSdkException(
                ex.cause!!,
                ex.extraInformation
            )
        }

        @DoNotInline
        fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
            return LoadSdkCompatException(
                toLoadSdkErrorCodeCompat(ex.loadSdkErrorCode),
                ex.message,
                ex.cause,
                ex.extraInformation
            )
        }

        @LoadSdkErrorCode
        private fun toLoadSdkErrorCodeCompat(
            value: Int
        ): Int {
            return value // TODO(b/249982002): Validate and convert
        }
    }

    companion object {

        /**
         * Sdk sandbox process is not available.
         *
         * This indicates that the sdk sandbox process is not available, either because it has died,
         * disconnected or was not created in the first place.
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE]
         */
        const val SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503

        /**
         * SDK not found.
         *
         * This indicates that client application tried to load a non-existing SDK.
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_NOT_FOUND]
         */
        const val LOAD_SDK_NOT_FOUND = 100

        /**
         * SDK is already loaded.
         *
         * This indicates that client application tried to reload the same SDK after being
         * successfully loaded.
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_ALREADY_LOADED]
         */
        const val LOAD_SDK_ALREADY_LOADED = 101

        /**
         * SDK error after being loaded.
         *
         * This indicates that the SDK encountered an error during post-load initialization. The
         * details of this can be obtained from the Bundle returned in [LoadSdkCompatException].
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_DEFINED_ERROR]
         */
        const val LOAD_SDK_SDK_DEFINED_ERROR = 102

        /**
         * SDK sandbox is disabled.
         *
         * This indicates that the SDK sandbox is disabled. Any subsequent attempts to load SDKs in
         * this boot will also fail.
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED]
         */
        const val LOAD_SDK_SDK_SANDBOX_DISABLED = 103

        /**
         * Internal error while loading SDK.
         *
         * This indicates a generic internal error happened while applying the call from
         * client application.
         *
         * @see [android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR]
         */
        const val LOAD_SDK_INTERNAL_ERROR = 500

        /**
         *  Create compat exception from platform [LoadSdkException].
         *
         *  @param ex Platform exception
         *  @return Compat exception.
         *  @suppress
         */
        @RequiresExtension(extension = AD_SERVICES, version = 4)
        @RestrictTo(LIBRARY_GROUP)
        fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
            return ApiAdServicesV4Impl.toLoadCompatSdkException(ex)
        }
    }
}