blob: 08aef1a286b99917942261fbe4d718c84048624c [file] [log] [blame]
/*
* Copyright (C) 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.connecteddevice.transport.eap
import android.annotation.SuppressLint
import android.os.IBinder
import android.os.ParcelUuid
import android.os.RemoteException
import androidx.annotation.VisibleForTesting
import com.google.android.connecteddevice.transport.ConnectChallenge
import com.google.android.connecteddevice.transport.ConnectionProtocol
import com.google.android.connecteddevice.transport.IDataSendCallback
import com.google.android.connecteddevice.transport.IDiscoveryCallback
import com.google.android.connecteddevice.util.DirectExecutor
import com.google.android.connecteddevice.util.SafeLog.logd
import com.google.android.connecteddevice.util.SafeLog.loge
import com.panasonic.iapx.IDeviceConnection
import com.panasonic.iapx.IDeviceConnectionDelegate
import com.panasonic.iapx.IServiceConnector
import com.panasonic.iapx.IServiceConnectorDelegate
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
/** Defines how connection is established and data is transferred via EAP channel. */
class EapProtocol(
private val eapClientName: String,
private val eapServiceName: String,
private val maxPacketSize: Int,
callbackExecutor: Executor = DirectExecutor()
) : ConnectionProtocol(callbackExecutor) {
// Stores the connected EAP session id to the real connection pair.
private val sessions = ConcurrentHashMap<Long, IDeviceConnection>()
private val ongoingDiscoveries = ConcurrentHashMap<String, IDiscoveryCallback>()
private val serviceDelegate =
object : IServiceConnectorDelegate.Stub() {
override fun OnServiceConnectionChange(status: Int) {
logd(TAG, "Service connection status has changed to $status.")
if (status == IServiceConnector.kServiceConnectionReady) {
logd(TAG, "Connection to EAP service is ready to use.")
}
}
}
init {
bindToEapService()
}
@SuppressLint("PrivateApi")
private fun bindToEapService() {
logd(TAG, "Attempting to connect to EAP service $eapServiceName.")
val serviceBinder =
Class.forName("android.os.ServiceManager")
.getMethod("getService", java.lang.String::class.java)
.invoke(null, eapServiceName) as?
IBinder
if (serviceBinder == null) {
loge(TAG, "Unable to bind to EAP service. Aborting.")
return
}
val connector = IServiceConnector.Stub.asInterface(serviceBinder)
logd(TAG, "Attempting to connect to the EAP client.")
connectClient(connector)
}
@VisibleForTesting
internal fun connectClient(connector: IServiceConnector) {
try {
connector.ConnectClient(
eapClientName,
serviceDelegate.asBinder(),
generateDeviceDelegate().asBinder()
)
} catch (e: RemoteException) {
loge(TAG, "Failed to connect the EAP client.", e)
}
}
override fun startAssociationDiscovery(
name: String,
identifier: ParcelUuid,
callback: IDiscoveryCallback,
) {
ongoingDiscoveries[name] = callback
}
private fun generateDeviceDelegate(): IDeviceConnectionDelegate {
return object : IDeviceConnectionDelegate.Stub() {
override fun OnConnectionReady(connection: IDeviceConnection?, transportType: Int) {
logd(TAG, "Connection is ready on transport $transportType.")
}
override fun OnConnectionClosed(connection: IDeviceConnection?) {
logd(TAG, "Connection has been closed.")
}
override fun OnEAPSessionStart(
connection: IDeviceConnection,
eapSessionId: Long,
eapProtocolName: String?
) {
logd(TAG, "Starting new session $eapSessionId with protocol $eapProtocolName.")
if (ongoingDiscoveries.containsKey(eapProtocolName)) {
sessions[eapSessionId] = connection
ongoingDiscoveries[eapProtocolName]?.onDeviceConnected(eapSessionId.toString())
}
}
override fun OnEAPSessionStop(connection: IDeviceConnection?, eapSessionId: Long) {
logd(TAG, "Device disconnected. id: $eapSessionId.")
if (sessions.containsKey(eapSessionId)) {
sessions.remove(eapSessionId)
deviceDisconnectedListeners[eapSessionId.toString()]?.invoke {
it.onDeviceDisconnected(eapSessionId.toString())
}
}
}
override fun OnEAPData(connection: IDeviceConnection?, eapSessionId: Long, data: ByteArray?) {
logd(TAG, "Received new data from session $eapSessionId.")
if (data != null) {
notifyDataReceived(eapSessionId.toString(), data)
}
}
override fun OnDeviceNameUpdate(connection: IDeviceConnection?, name: String?) {}
override fun OnDeviceTransientUUIDUpdate(connection: IDeviceConnection?, uuid: String?) {}
}
}
override fun stopAssociationDiscovery() {
ongoingDiscoveries.clear()
}
override fun reset() {
super.reset()
sessions.clear()
ongoingDiscoveries.clear()
}
override fun sendData(protocolId: String, data: ByteArray, callback: IDataSendCallback?) {
sessions[protocolId.toLong()]?.SendEAPData(protocolId.toLong(), data)
?: loge(TAG, "Unable to find device with session $protocolId.")
}
override fun getMaxWriteSize(protocolId: String): Int {
return maxPacketSize
}
override fun isDeviceVerificationRequired() = false
override fun startConnectionDiscovery(
id: ParcelUuid,
challenge: ConnectChallenge,
callback: IDiscoveryCallback,
) {}
override fun stopConnectionDiscovery(id: ParcelUuid) {}
override fun disconnectDevice(protocolId: String) {}
companion object {
private const val TAG = "EapProtocol"
}
}