blob: 564b043fc5f3a8d38e293001af16a05cf021a143 [file] [log] [blame]
/*
* Copyright (C) 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 com.google.android.connecteddevice.transport
import android.os.ParcelUuid
import androidx.annotation.CallSuper
import com.google.android.connecteddevice.util.AidlThreadSafeCallbacks
import com.google.android.connecteddevice.util.DirectExecutor
import com.google.android.connecteddevice.util.SafeLog.logd
import com.google.android.connecteddevice.util.SafeLog.logw
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executor
/**
* Representation of a communication protocol that provides actions and event notifications for
* interacting with devices.
*/
abstract class ConnectionProtocol() : IConnectionProtocol.Stub() {
private val callbackExecutor: Executor = DirectExecutor()
protected val dataReceivedListeners:
MutableMap<String, AidlThreadSafeCallbacks<IDataReceivedListener>> =
ConcurrentHashMap()
protected val deviceDisconnectedListeners:
MutableMap<String, AidlThreadSafeCallbacks<IDeviceDisconnectedListener>> =
ConcurrentHashMap()
protected val maxDataSizeChangedListeners:
MutableMap<String, AidlThreadSafeCallbacks<IDeviceMaxDataSizeChangedListener>> =
ConcurrentHashMap()
private val missedData: MutableMap<String, MutableList<ByteArray>> = ConcurrentHashMap()
/**
* `true` if challenge exchange is required to verify the remote device for establishing a secure
* channel over this [ConnectionProtocol].
*/
abstract override fun isDeviceVerificationRequired(): Boolean
/**
* Begin the discovery process with [name] and [identifier] for a new device to associate with.
*/
abstract override fun startAssociationDiscovery(
name: String,
identifier: ParcelUuid,
callback: IDiscoveryCallback
)
/**
* Begin the discovery process for a device that will respond to the supplied [id] with
* [challenge].
*/
abstract override fun startConnectionDiscovery(
id: ParcelUuid,
challenge: ConnectChallenge,
callback: IDiscoveryCallback
)
/** Stop an ongoing association discovery. */
abstract override fun stopAssociationDiscovery()
/** Stop an ongoing connection discovery for the provided device. */
abstract override fun stopConnectionDiscovery(id: ParcelUuid)
/** Send data to a device. */
abstract override fun sendData(protocolId: String, data: ByteArray, callback: IDataSendCallback?)
/** Disconnect a specific device. */
abstract override fun disconnectDevice(protocolId: String)
/**
* Disconnect all active connections, cancel any discoveries in progress, and clean up to a
* neutral state.
*/
@CallSuper
override fun reset() {
deviceDisconnectedListeners.clear()
dataReceivedListeners.clear()
maxDataSizeChangedListeners.clear()
missedData.clear()
}
/**
* Returns the maximum number of bytes that can be written in a single message for the device
* matching the [protocolId].
*/
abstract override fun getMaxWriteSize(protocolId: String): Int
/** Register a listener to be notified when data has been received on protocol [protocolId]. */
override fun registerDataReceivedListener(protocolId: String, listener: IDataReceivedListener) {
logd(TAG, "Registering a new DataReceivedListener.")
val listeners = dataReceivedListeners.computeIfAbsent(protocolId) { AidlThreadSafeCallbacks() }
listeners.add(listener, callbackExecutor)
missedData.remove(protocolId)?.forEach { data ->
listeners.invoke { it.onDataReceived(protocolId, data) }
}
}
/** Register a listener to be notified when device has disconnected on protocol [protocolId]. */
override fun registerDeviceDisconnectedListener(
protocolId: String,
listener: IDeviceDisconnectedListener
) {
deviceDisconnectedListeners
.computeIfAbsent(protocolId) { AidlThreadSafeCallbacks() }
.add(listener, callbackExecutor)
}
/**
* Register a listener to be notified when the protocol [protocolId] has negotiated a new maximum
* data size.
*/
override fun registerDeviceMaxDataSizeChangedListener(
protocolId: String,
listener: IDeviceMaxDataSizeChangedListener
) {
maxDataSizeChangedListeners
.computeIfAbsent(protocolId) { AidlThreadSafeCallbacks() }
.add(listener, callbackExecutor)
}
/** Unregister a previously registered [listener]. */
override fun unregisterDataReceivedListener(protocolId: String, listener: IDataReceivedListener) {
logd(TAG, "Removing data DataReceivedListener.")
val listeners =
dataReceivedListeners.getOrElse(protocolId) {
logw(
TAG,
"Attempted to delete data received listener for device $protocolId that does not exist."
)
return
}
listeners.remove(listener)
if (listeners.isEmpty) {
dataReceivedListeners.remove(protocolId)
}
}
/** Unregister a previously registered [listener]. */
override fun unregisterDeviceDisconnectListener(
protocolId: String,
listener: IDeviceDisconnectedListener
) {
val listeners =
deviceDisconnectedListeners.getOrElse(protocolId) {
logw(
TAG,
"Attempted to delete disconnect listener for device $protocolId that does not exist."
)
return
}
listeners.remove(listener)
if (listeners.isEmpty) {
deviceDisconnectedListeners.remove(protocolId)
}
}
/** Unregister a previously registered [listener]. */
override fun unregisterDeviceMaxDataSizeChangedListener(
protocolId: String,
listener: IDeviceMaxDataSizeChangedListener
) {
val listeners =
maxDataSizeChangedListeners.getOrElse(protocolId) {
logw(
TAG,
"Attempted to delete maxDataSizeChangedListener for device $protocolId that does not " +
"exist."
)
return
}
listeners.remove(listener)
if (listeners.isEmpty) {
maxDataSizeChangedListeners.remove(protocolId)
}
}
/** Removes registered listeners for connection [protocolId]. */
override fun removeListeners(protocolId: String) {
deviceDisconnectedListeners.remove(protocolId)
dataReceivedListeners.remove(protocolId)
maxDataSizeChangedListeners.remove(protocolId)
}
/**
* Notifies that [data] has been received from connected device.
*
* If there's no registered dataReceivedListener to notify, the data will be cached for the
* listeners registered later.
*/
fun notifyDataReceived(protocolId: String, data: ByteArray) {
dataReceivedListeners[protocolId]
?.takeUnless { it.isEmpty }
?.invoke { it.onDataReceived(protocolId, data) }
?: run {
logd(
TAG,
"No callback has been registered for connection $protocolId, cached received message."
)
missedData.computeIfAbsent(protocolId) { CopyOnWriteArrayList() }.add(data)
}
}
companion object {
private const val TAG = "ConnectionProtocol"
}
}