CallAttributesCompat.kt

/*
 * Copyright 2023 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.telecom

import android.net.Uri
import android.telecom.PhoneAccountHandle
import androidx.annotation.IntDef
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.core.telecom.internal.utils.CallAttributesUtils
import androidx.core.telecom.internal.utils.Utils
import java.util.Objects

/**
 * CallAttributes represents a set of properties that define a new Call.  Applications should build
 * an instance of this class and use [CallsManager.addCall] to start a new call with Telecom.
 *
 * @param displayName  Display name of the person on the other end of the call
 * @param address Address of the call. Note, this can be extended to a meeting link
 * @param direction The direction (Outgoing/Incoming) of the new Call
 * @param callType Information related to data being transmitted (voice, video, etc. )
 * @param callCapabilities Allows a package to opt into capabilities on the telecom side,
 *                         on a per-call basis
 */
class CallAttributesCompat constructor(
    val displayName: CharSequence,
    val address: Uri,
    @Direction val direction: Int,
    @CallType val callType: Int = CALL_TYPE_AUDIO_CALL,
    @CallCapability val callCapabilities: Int = SUPPORTS_SET_INACTIVE
) {
    internal var mHandle: PhoneAccountHandle? = null

    override fun toString(): String {
        return "CallAttributes(" +
            "displayName=[$displayName], " +
            "address=[$address], " +
            "direction=[${directionToString()}], " +
            "callType=[${callTypeToString()}], " +
            "capabilities=[${capabilitiesToString()}])"
    }

    override fun equals(other: Any?): Boolean {
        return other is CallAttributesCompat &&
            displayName == other.displayName &&
            address == other.address &&
            direction == other.direction &&
            callType == other.callType &&
            callCapabilities == other.callCapabilities
    }

    override fun hashCode(): Int {
        return Objects.hash(displayName, address, direction, callType, callCapabilities)
    }

    companion object {
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        @Retention(AnnotationRetention.SOURCE)
        @IntDef(DIRECTION_INCOMING, DIRECTION_OUTGOING)
        @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
        annotation class Direction

        /**
         * Indicates that the call is an incoming call.
         */
        const val DIRECTION_INCOMING = 1

        /**
         * Indicates that the call is an outgoing call.
         */
        const val DIRECTION_OUTGOING = 2

        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        @Retention(AnnotationRetention.SOURCE)
        @IntDef(CALL_TYPE_AUDIO_CALL, CALL_TYPE_VIDEO_CALL)
        @Target(AnnotationTarget.TYPE, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY)
        annotation class CallType

        /**
         * Used when answering or dialing a call to indicate that the call does not have a video
         * component
         */
        const val CALL_TYPE_AUDIO_CALL = 1

        /**
         * Indicates video transmission is supported
         */
        const val CALL_TYPE_VIDEO_CALL = 2

        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        @Retention(AnnotationRetention.SOURCE)
        @IntDef(SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER, flag = true)
        @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
        annotation class CallCapability

        /**
         * This call being created can be set to inactive (traditionally referred to as hold).  This
         * means that once a new call goes active, if the active call needs to be held in order to
         * place or receive an incoming call, the active call will be placed on hold.  otherwise,
         * the active call may be disconnected.
         */
        const val SUPPORTS_SET_INACTIVE = 1 shl 1

        /**
         * This call can be streamed from a root device to another device to continue the call
         * without completely transferring it. The call continues to take place on the source
         * device, however media and control are streamed to another device.
         */
        const val SUPPORTS_STREAM = 1 shl 2

        /**
         * This call can be completely transferred from one endpoint to another.
         */
        const val SUPPORTS_TRANSFER = 1 shl 3
    }

    @RequiresApi(34)
    internal fun toCallAttributes(
        phoneAccountHandle: PhoneAccountHandle
    ): android.telecom.CallAttributes {
        return CallAttributesUtils.Api34PlusImpl.toTelecomCallAttributes(
            phoneAccountHandle,
            direction,
            displayName,
            address,
            callType,
            callCapabilities
        )
    }

    private fun directionToString(): String {
        return if (direction == DIRECTION_OUTGOING) {
            "Outgoing"
        } else {
            "Incoming"
        }
    }

    private fun callTypeToString(): String {
        return if (callType == CALL_TYPE_AUDIO_CALL) {
            "Audio"
        } else {
            "Video"
        }
    }

    private fun hasSupportsSetInactiveCapability(): Boolean {
        return Utils.hasCapability(SUPPORTS_SET_INACTIVE, callCapabilities)
    }

    private fun hasStreamCapability(): Boolean {
        return Utils.hasCapability(SUPPORTS_STREAM, callCapabilities)
    }

    private fun hasTransferCapability(): Boolean {
        return Utils.hasCapability(SUPPORTS_TRANSFER, callCapabilities)
    }

    private fun capabilitiesToString(): String {
        val sb = StringBuilder()
        sb.append("[")
        if (hasSupportsSetInactiveCapability()) {
            sb.append("SetInactive")
        }
        if (hasStreamCapability()) {
            sb.append(", Stream")
        }
        if (hasTransferCapability()) {
            sb.append(", Transfer")
        }
        sb.append("])")
        return sb.toString()
    }

    internal fun isOutgoingCall(): Boolean {
        return direction == DIRECTION_OUTGOING
    }
}