blob: ce5f722a88244c0137dc0d810b3e04cfd48f089e [file] [log] [blame]
/*
* Copyright (C) 2020 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.api
import android.content.Context
import android.os.ParcelUuid
import androidx.annotation.CallSuper
import com.google.android.connecteddevice.api.Connector.QueryCallback
import com.google.android.connecteddevice.model.AssociatedDevice
import com.google.android.connecteddevice.model.ConnectedDevice
import com.google.android.connecteddevice.util.SafeLog.logd
import com.google.android.connecteddevice.util.SafeLog.loge
/**
* Base class for a feature that must bind to `ConnectedDeviceService`. Callbacks are registered
* automatically and events are forwarded to internal methods. Override these to add custom logic
* for callback triggers.
*/
abstract class RemoteFeature
protected constructor(
/** Returns the [Context] registered with the feature. */
val context: Context,
/** Returns the [ParcelUuid] feature id registered for the feature. */
val featureId: ParcelUuid,
val connector: Connector
) {
/**
* Creates a new RemoteFeature.
*
* @param context [Context] of the application process
* @param featureId The id for this feature
* @param forceFgUserBind Force binding to the foreground user service to avoid a cross-user bind
*/
@JvmOverloads
protected constructor(
context: Context,
featureId: ParcelUuid,
forceFgUserBind: Boolean = false
) : this(context, featureId, CompanionConnector(context, forceFgUserBind)) {}
/** Starts setup process and begins connecting to the platform. */
@CallSuper
open fun start() {
connector.connect()
}
/** Stops this feature and disconnects from the platform. */
@CallSuper
open fun stop() {
connector.disconnect()
}
/** Securely sends message to a device. */
open fun sendMessageSecurely(deviceId: String, message: ByteArray) {
connector.sendMessageSecurely(deviceId, message)
}
/** Securely sends message to a device. */
open fun sendMessageSecurely(device: ConnectedDevice, message: ByteArray) {
connector.sendMessageSecurely(device, message)
}
/** Securely sends a query to a device and register a [QueryCallback] for a response. */
open fun sendQuerySecurely(
deviceId: String,
request: ByteArray,
parameters: ByteArray?,
callback: Connector.QueryCallback
) {
connector.sendQuerySecurely(deviceId, request, parameters, callback)
}
/** Securely sends a query to a device and register a [QueryCallback] for a response. */
open fun sendQuerySecurely(
device: ConnectedDevice,
request: ByteArray,
parameters: ByteArray?,
callback: Connector.QueryCallback
) {
connector.sendQuerySecurely(device, request, parameters, callback)
}
/** Sends a secure response to a query with an indication of whether it was successful. */
open fun respondToQuerySecurely(
device: ConnectedDevice,
queryId: Int,
success: Boolean,
response: ByteArray?
) {
connector.respondToQuerySecurely(device, queryId, success, response)
}
/**
* Returns the [ConnectedDevice] with a matching device id for the currently active user. Returns
* `null` if no match found.
*/
open fun getConnectedDeviceById(deviceId: String): ConnectedDevice? {
return connector.getConnectedDeviceById(deviceId)
}
/** Queries the [ConnectedDevice] for its companion application name. */
open fun getCompanionApplicationName(
device: ConnectedDevice,
callback: Connector.AppNameCallback
) {
connector.retrieveCompanionApplicationName(device, callback)
}
/** Returns all the currently connected devices. */
open val connectedDevices: List<ConnectedDevice>
get() = connector.connectedDevices
// These can be overridden to perform custom actions.
/** Called when the platform has connected and is ready for interaction. */
protected open fun onReady() {}
/** Called when the platform has disconnected and is no longer ready for interaction. */
protected open fun onNotReady() {}
/** Called when a new [ConnectedDevice] is connected. */
protected open fun onDeviceConnected(device: ConnectedDevice) {}
/** Called when a [ConnectedDevice] disconnects. */
protected open fun onDeviceDisconnected(device: ConnectedDevice) {}
/** Called when a secure channel has been established with a [ConnectedDevice]. */
protected open fun onSecureChannelEstablished(device: ConnectedDevice) {}
/**
* Called when a message fails to send to a device.
*
* @param deviceId Id of the device the message failed to send to.
* @param message Message to send.
* @param isTransient `true` if cause of failure is transient and can be retried. `false` if
* failure is permanent.
*/
protected open fun onMessageFailedToSend(
deviceId: String,
message: ByteArray,
isTransient: Boolean
) {}
/** Called when a new [byte[]] message is received for this feature. */
protected open fun onMessageReceived(device: ConnectedDevice, message: ByteArray) {}
/** Called when a new query is received for this feature. */
protected open fun onQueryReceived(
device: ConnectedDevice,
queryId: Int,
request: ByteArray,
parameters: ByteArray?
) {}
/** Called when an error has occurred with the connection. */
protected open fun onDeviceError(device: ConnectedDevice, error: Int) {}
/** Called when a new [AssociatedDevice] is added for the given user. */
protected open fun onAssociatedDeviceAdded(device: AssociatedDevice) {}
/** Called when an [AssociatedDevice] is removed for the given user. */
protected open fun onAssociatedDeviceRemoved(device: AssociatedDevice) {}
/** Called when an [AssociatedDevice] is updated for the given user. */
protected open fun onAssociatedDeviceUpdated(device: AssociatedDevice) {}
init {
connector.featureId = featureId
connector.callback =
object : Connector.Callback {
override fun onConnected() {
logd(TAG, "Successfully connected. Initializing feature.")
onReady()
}
override fun onDisconnected() {
logd(TAG, "Disconnected from companion. Stopping feature.")
onNotReady()
}
override fun onFailedToConnect() {
loge(TAG, "Failed to connect. Stopping feature.")
onNotReady()
}
override fun onAssociatedDeviceUpdated(device: AssociatedDevice) {
this@RemoteFeature.onAssociatedDeviceUpdated(device)
}
override fun onAssociatedDeviceRemoved(device: AssociatedDevice) {
this@RemoteFeature.onAssociatedDeviceRemoved(device)
}
override fun onAssociatedDeviceAdded(device: AssociatedDevice) {
this@RemoteFeature.onAssociatedDeviceAdded(device)
}
override fun onDeviceError(device: ConnectedDevice, error: Int) {
this@RemoteFeature.onDeviceError(device, error)
}
override fun onQueryReceived(
device: ConnectedDevice,
queryId: Int,
request: ByteArray,
parameters: ByteArray?
) {
this@RemoteFeature.onQueryReceived(device, queryId, request, parameters)
}
override fun onMessageReceived(device: ConnectedDevice, message: ByteArray) {
this@RemoteFeature.onMessageReceived(device, message)
}
override fun onMessageFailedToSend(
deviceId: String,
message: ByteArray,
isTransient: Boolean
) {
this@RemoteFeature.onMessageFailedToSend(deviceId, message, isTransient)
}
override fun onSecureChannelEstablished(device: ConnectedDevice) {
this@RemoteFeature.onSecureChannelEstablished(device)
}
override fun onDeviceDisconnected(device: ConnectedDevice) {
this@RemoteFeature.onDeviceDisconnected(device)
}
override fun onDeviceConnected(device: ConnectedDevice) {
this@RemoteFeature.onDeviceConnected(device)
}
}
}
companion object {
private const val TAG = "RemoteFeature"
/** Intent action used to request a device be associated. */
const val ACTION_ASSOCIATION_SETTING =
"com.google.android.connecteddevice.api.ASSOCIATION_ACTIVITY"
/** Intent action used to request a device be associated. */
const val ACTION_ASSOCIATION_SETUP_WIZARD =
"com.google.android.connecteddevice.api.SETUP_WIZARD_ASSOCIATION_ACTIVITY"
/** Data name for associated device. */
const val ASSOCIATED_DEVICE_DATA_NAME_EXTRA =
"com.google.android.connecteddevice.api.ASSOCIATED_DEVICE"
}
}