blob: 5ebbf14553d9ed8195877371162789fd44497c16 [file] [log] [blame]
/*
* ANT Android Host Stack
*
* Copyright 2018 Dynastream Innovations
*
* 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.dsi.ant.hidl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.NoSuchElementException;
import com.dsi.ant.V1_0.IAnt;
import com.dsi.ant.V1_0.IAntCallbacks;
import com.dsi.ant.V1_0.ImplProps;
import com.dsi.ant.V1_0.OptionFlags;
import com.dsi.ant.server.AntHalDefine;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IHwBinder.DeathRecipient;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
/**
* Class for interacting over the HIDL interface to the hal server.
*/
public final class HidlClient {
/*
* Notes:
*
* The synchronization model here is to do pretty much everything with the state lock held.
* All private functions assume that they are called with the state lock held,
* and the public functions generally grab the lock for the duration.
*
* Exceptions to this are:
* - Code that makes callbacks. Easy to avoid deadlocks if we never call out with locks held.
* - The tx message function. Sending a data message may be blocked but we still need to send control messages.
* - Retrieving the current state. Generally want to be able to do this while power control operations are in progress.
* - Separate lock used for flow controlling.
*/
/**
* Callbacks made by this client into the code in AntServce.java
*/
public interface ICallback {
void ANTRxMessage(byte[] RxMessage);
/** State values are AntHalDefine.ANT_HAL_STATE_ constants. */
void ANTStateChange(int NewState);
}
/** Logcat tag. */
private static final String TAG = "ANT HidlClient";
// For keepalive use the invalid message id 0.
private static final byte[] KEEPALIVE_MESG = new byte[] {(byte)0x01, (byte)0x00, (byte)0x00};
// Response is an invalid message id response.
private static final byte[] KEEPALIVE_RESP = new byte[] {(byte)0x03, (byte)0x40, (byte)0x00, (byte)0x00, (byte)0x28};
// Constants for the keep alive handler
/** Start the keep alive process */
private static final int KEEPALIVE_START = 0;
/** Stop the keep alive process */
private static final int KEEPALIVE_STOP = 1;
/** Reset the keep alive timer */
private static final int KEEPALIVE_RESET = 2;
/** Internal to keep alive handler, indicates the initial timeout. */
private static final int KEEPALIVE_PING = 3;
/** Internal to keep alive handler, indicates that no response was received. */
private static final int KEEPALIVE_TIMEOUT = 4;
/** Value for mDeathCookie indicating we have no valid alive interface right now. */
private static final long INVALID_DEATH_COOKIE = 0L;
/** Timeout for flow control responses. */
private static final long FLOW_CONTROL_TIMEOUT_NS = 10L * 1000L * 1000L * 1000L;
/** Timeout for keepalive */
private static final long KEEPALIVE_TIMEOUT_MS = 5L * 1000L;
// Constants for server bind retries.
private static final int HAL_BIND_RETRY_DELAY_MS = 100;
private static final int HAL_BIND_RETRY_COUNT = 10;
/** Lock object used for all synchronization in this class. */
private final Object mStateLock = new Object();
/** Lock for using flow control on the data channel. */
private final Object mFlowControlLock = new Object();
/** Separate thread for making state change callbacks. */
private final Handler mStateDispatcher;
{
HandlerThread thread = new HandlerThread("State Dispatch");
thread.start();
mStateDispatcher = new Handler(thread.getLooper());
}
/** Handler for dispatching a recovery attempt from failed message transmits. */
private final Handler mRecoveryDispatcher = new Handler();
/** Power state of transport. */
private volatile int mState = AntHalDefine.ANT_HAL_STATE_DISABLED;
/** Callbacks to AntService. */
private volatile ICallback mCallbacks;
/** Handle to HIDL server. */
private IAnt mHidl;
/** Properties of the server implementation. */
private ImplProps mHidlProps;
/** Keep track of the last cookie given to linkToDeath() */
private long mDeathCookie = INVALID_DEATH_COOKIE;
/** Next value to use for the death cookie. */
private long mNextDeathCookie = 1;
/** Whether flow control is used or not. */
private boolean mUseFlowControl = false;
/** Flag to communicate that a flow go was received. */
private boolean mFlowGoReceived = false;
/** Whether keep alive should be used or not. */
private boolean mUseKeepalive = false;
/**
* Initial setup at ANTHALService startup.
*/
public void create(ICallback callback) {
synchronized (mStateLock) {
mCallbacks = callback;
}
}
/**
* Cleanup when ANTHALService is shutting down.
*/
public void destroy() {
synchronized (mStateLock) {
// No need to send state updates since we only destroy when no one is bound.
disable();
cleanupHidl();
}
}
/**
* Enable the transport. Will perform the binding to the HIDL server if required.
* @return true if transport bring up was successful, false otherwise.
*/
public boolean enable() {
synchronized (mStateLock) {
// If already enabled there is nothing to do.
if (mState == AntHalDefine.ANT_HAL_STATE_ENABLED) {
Log.d(TAG, "Ignoring enable(), already enabled.");
return true;
}
updateState(AntHalDefine.ANT_HAL_STATE_ENABLING);
if (doBringup()) {
updateState(AntHalDefine.ANT_HAL_STATE_ENABLED);
return true;
} else {
updateState(AntHalDefine.ANT_HAL_STATE_DISABLED);
return false;
}
}
}
/**
* Disable the transport.
*/
public void disable() {
synchronized (mStateLock) {
// If already disabled there is nothing to do.
if (mState == AntHalDefine.ANT_HAL_STATE_DISABLED) {
Log.d(TAG, "Ignoring disable(), already disabled.");
return;
}
updateState(AntHalDefine.ANT_HAL_STATE_DISABLING);
try {
int status = mHidl.disable();
if (status != 0) {
Log.w(TAG, "Failed to disable ANT: " + mHidl.translateStatus(status));
}
cleanupHidl();
} catch (RemoteException e) {
Log.w(TAG, "Server died in disable", e);
handleServerDeath();
}
updateState(AntHalDefine.ANT_HAL_STATE_DISABLED);
}
}
/**
* Perform a full power cycle of the transport/chip. Usually used to recover from error conditions.
* @return true if the transport/chip where successfully brought back to an operational state.
*/
public boolean hardReset() {
synchronized (mStateLock) {
updateState(AntHalDefine.ANT_HAL_STATE_RESETTING);
if (doRecovery()) {
updateState(AntHalDefine.ANT_HAL_STATE_RESET);
return true;
} else {
updateState(AntHalDefine.ANT_HAL_STATE_DISABLED);
return false;
}
}
}
/**
* Query the current state of the transport.
* @return Ant ANT_HAL_STATE constant representing the current state of the transport.
*/
public int getRadioEnabledStatus() {
// No need to hold lock, since value is marked as volatile.
return mState;
}
/**
* Send a message to the firmware over the transport.
* @param message The ANT message to send.
* @return True if the message was successfully sent, false otherwise.
*/
public boolean ANTTxMessage(byte[] message) {
// Cache a few things that might change during the unsynchronized portion.
IAnt cachedHidl;
long cachedDeathCookie;
boolean succeeded = false;
synchronized (mStateLock) {
// Hidl interface is only valid while enabled.
if (mState != AntHalDefine.ANT_HAL_STATE_ENABLED) {
Log.w(TAG, "Failing message tx because transport was not enabled.");
return false;
}
cachedHidl = mHidl;
cachedDeathCookie = mDeathCookie;
}
// The hidl interface uses ArrayLists, so need to convert.
ArrayList<Byte> hidlMessage = new ArrayList<>(message.length);
for (byte b : message) {
hidlMessage.add(b);
}
// Dispatch to proper channel.
try {
int status;
boolean timeout = false;
switch (message[Mesg.ID_OFFSET]) {
case Mesg.BROADCAST_DATA_ID:
case Mesg.ACKNOWLEDGED_DATA_ID:
case Mesg.BURST_DATA_ID:
case Mesg.EXT_BROADCAST_DATA_ID:
case Mesg.EXT_ACKNOWLEDGED_DATA_ID:
case Mesg.EXT_BURST_DATA_ID:
case Mesg.ADV_BURST_DATA_ID:
// Data messages may be flow controlled at protocol level.
synchronized (mFlowControlLock) {
status = cachedHidl.sendDataMessage(hidlMessage);
if (mUseFlowControl) {
timeout = !waitForFlowGoLocked();
}
}
break;
default:
status = cachedHidl.sendCommandMessage(hidlMessage);
break;
}
if (timeout) {
Log.e(TAG, "Timeout waiting for flow control response.");
} else if (status != 0) {
Log.e(TAG, "Failed to send message: " + mHidl.translateStatus(status));
} else {
succeeded = true;
}
} catch (RemoteException e) {
Log.e(TAG, "Server died while sending message.", e);
// Treat the interface going down during sending of a message the
// same as if an async crash was detected.
mRecoveryDispatcher.post(new Runnable() {
private long deathCookie = cachedDeathCookie;
@Override
public void run() {
mDeathHandler.serviceDied(deathCookie);
}
});
}
return succeeded;
}
/**
* Wait for a flow go to be received. This should only be called with the flow control
* lock held, and the lock should be grabbed before sending the message that prompts the flow go response.
* @return True if a flow go message was received within the timeout.
*/
public boolean waitForFlowGoLocked() {
mFlowGoReceived = false;
long deadline = System.nanoTime() + FLOW_CONTROL_TIMEOUT_NS;
long diff = deadline - System.nanoTime();
while (diff > 0 && !mFlowGoReceived) {
try {
// Wait takes a time in milliseconds
long diff_ms = diff / (1000L * 1000L);
if (diff_ms > 0) {
mFlowControlLock.wait(diff_ms);
} else {
break;
}
} catch (InterruptedException e) {
// Shouldn't be interrupted, but if it is just move the deadline to now.
deadline = System.nanoTime();
}
diff = deadline - System.nanoTime();
}
return mFlowGoReceived;
}
/**
* Make sure that the interface to the hidl server is fully brought up.
* @return true if the interface is fully initialized, false otherwise.
*/
private boolean setupHidl() {
boolean succeeded = false;
try {
// If the interface is already up there is nothing to do.
if (mHidl != null) {
return true;
}
// Use a unique cookie for link to death. This filters out extraneous death notifications.
mDeathCookie = mNextDeathCookie++;
// Shouldn't ever reach wrap around, but just in case...
if (mNextDeathCookie == INVALID_DEATH_COOKIE) {
mNextDeathCookie++;
}
// Initial setup of the interface.
int bindAttempts = 0;
while ((mHidl == null) && (bindAttempts < HAL_BIND_RETRY_COUNT)) {
if (bindAttempts > 0) {
Log.i(TAG, "Could not find hal. Retrying in a little bit.");
try {
Thread.sleep(HAL_BIND_RETRY_DELAY_MS);
} catch (InterruptedException e) {
break;
}
}
bindAttempts++;
try {
mHidl = IAnt.getService();
} catch (NoSuchElementException e) {
// Do nothing, mHidl is null and we will delay then retry.
}
}
if (mHidl == null) {
Log.e(TAG, "Unable to bind to hidl server.");
handleServerDeath();
} else {
mHidl.linkToDeath(mDeathHandler, mDeathCookie);
mHidlProps = mHidl.getProperties();
mUseFlowControl = (OptionFlags.USE_ANT_FLOW_CONTROL & mHidlProps.options) != 0;
mUseKeepalive = (OptionFlags.USE_KEEPALIVE & mHidlProps.options) != 0;
mHidl.setCallbacks(mHidlCallbacks);
Log.i(TAG, "Successfully bound to HAL server.");
Log.d(TAG, "Server properties: " + mHidlProps);
succeeded = true;
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to bind to hidl server.", e);
// Make sure everything is cleaned up.
handleServerDeath();
}
return succeeded;
}
/**
* Cleanup the binding to the HIDL server.
*/
private void cleanupHidl() {
// Nothing to do if we don't currently have an interface binding.
if (mHidl == null) {
return;
}
// This can only be done if the server is still alive.
try {
mHidl.unlinkToDeath(mDeathHandler);
mHidl.setCallbacks(null);
} catch (RemoteException e) {
Log.w(TAG, "Error clearing hidl callbacks", e);
}
// Perform any cleanup for the server being gone.
handleServerDeath();
}
/**
* Cleanup local resources after the server has gone away.
*/
private void handleServerDeath() {
mDeathCookie = INVALID_DEATH_COOKIE;
mHidl = null;
}
/**
* Attempt to power cycle the chip.
* This is the implementation of hardReset, but extracted for clearer state signaling.
*
* @return true if the chip was restored to an operational state, false otherwise.
*/
private boolean doRecovery() {
boolean succeeded = false;
// Can't do anything without the interface being up.
if (!setupHidl()) {
return false;
}
try {
int status;
status = mHidl.disable();
if (status != 0) {
Log.w(TAG, "Reset - Failed to disable: " + mHidl.translateStatus(status));
}
// Even if disable failed we can try to enable again anyways and hope it fixes the issue.
status = mHidl.enable();
if (status == 0) {
succeeded = true;
} else {
Log.e(TAG, "Reset - Failed to enable: " + mHidl.translateStatus(status));
}
} catch (RemoteException e) {
handleServerDeath();
}
return succeeded;
}
/**
* Attempt to bring the chip to an operational state. This is the implementation of enable,
* but extracted for clearer state signaling code.
* @return true if the chip and transport where successfully brought up, false otherwise.
*/
private boolean doBringup() {
boolean succeeded = false;
// Make sure interface is up.
if (!setupHidl()) {
return false;
}
// Now perform actual enable operation.
try {
int status = mHidl.enable();
if (status == 0) {
succeeded = true;
} else {
Log.e(TAG, "Failed to enable ANT: " + mHidl.translateStatus(status));
// Try to clean things up.
status = mHidl.disable();
if (status != 0) {
Log.w(TAG, "Disable failed: " + mHidl.translateStatus(status));
}
}
} catch (RemoteException e) {
Log.e(TAG, "Server died in enable.", e);
handleServerDeath();
}
return succeeded;
}
/**
* Update the internal state, and dispatch the update callback on a separate thread.
* @param newState The new internal state. A callback will only be dispatched if this is different than
* the previous state.
*/
private void updateState(int newState) {
// Ignore non-updates.
if (newState == mState) {
return;
}
mState = newState;
// RESET is not actually a valid state, only used for signaling. In the case where it does occur we are
// in an enabled state.
if (mState == AntHalDefine.ANT_HAL_STATE_RESET) {
mState = AntHalDefine.ANT_HAL_STATE_ENABLED;
}
if (mUseKeepalive) {
// Change keep alive state based on whether we are now enabled or disabled.
int msg = (mState == AntHalDefine.ANT_HAL_STATE_ENABLED) ? KEEPALIVE_START : KEEPALIVE_STOP;
mKeepAliveHandler.sendMessage(mKeepAliveHandler.obtainMessage(msg));
}
// Dispatch state updates with a handler so that they don't block the current operation.
mStateDispatcher.post(new Runnable() {
@Override
public void run() {
// Cache since check+call is not atomic.
ICallback cachedCallbacks = mCallbacks;
if (cachedCallbacks != null) {
cachedCallbacks.ANTStateChange(newState);
}
}
});
}
/**
* Listen for notifications about the server crashing.
*/
private final DeathRecipient mDeathHandler = new DeathRecipient() {
@Override
public void serviceDied(long cookie) {
synchronized (mStateLock) {
// Only do anything if we don't already know about this death.
if (cookie != mDeathCookie) {
return;
}
Log.e(TAG, "Detected asynchronous server death.");
handleServerDeath();
// Attempt automated recovery.
// Result is ignored, errors already logged to logcat and radio service notified by state callbacks.
hardReset();
}
}
};
/**
* Handle callbacks from the HIDL server.
*/
private final IAntCallbacks mHidlCallbacks = new IAntCallbacks.Stub() {
@Override
public void onTransportDown(String cause) throws RemoteException {
synchronized (mStateLock) {
Log.e(TAG, "Transport is down: " + cause);
// Attempt automated recovery.
hardReset();
}
}
@Override
public void onMessageReceived(ArrayList<Byte> data) throws RemoteException {
// Translate to a byte array.
byte[] message = new byte[data.size()];
for (int i = 0; i < data.size(); i++) {
message[i] = data.get(i);
}
if (!keepaliveHandler(message) && !flowControlHandler(message)) {
// No one requested the message be filtered.
// Cache the callback since check+call is non atomic.
ICallback cachedCallbacks = mCallbacks;
if (cachedCallbacks != null) {
cachedCallbacks.ANTRxMessage(message);
}
}
}
/**
* Message handling for keep alive monitoring.
*
* @return true if mesg was for keep alive purposes and should be filtered out.
*/
private boolean keepaliveHandler(byte[] mesg) {
if (mUseKeepalive) {
// All activity resets the timers.
mKeepAliveHandler.sendMessage(mKeepAliveHandler.obtainMessage(KEEPALIVE_RESET));
return Arrays.equals(mesg, KEEPALIVE_RESP);
} else {
return false;
}
}
/**
* Message handling for data channel flow control.
* @return True if mesg was for flow control purposes and should be filtered out.
*/
private boolean flowControlHandler(byte[] mesg) {
boolean filter = false;
if (mUseFlowControl) {
if (mesg[Mesg.ID_OFFSET] == Mesg.FLOW_CONTROL_ID) {
filter = true;
if (mesg[Mesg.DATA_OFFSET] == Mesg.FLOW_CONTROL_GO) {
synchronized (mFlowControlLock) {
mFlowGoReceived = true;
mFlowControlLock.notify();
}
}
}
}
return filter;
}
};
private final Handler mKeepAliveHandler = new Handler() {
private boolean started = false;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case KEEPALIVE_START:
if (!started) {
started = true;
sendMessageDelayed(obtainMessage(KEEPALIVE_PING), KEEPALIVE_TIMEOUT_MS);
}
break;
case KEEPALIVE_STOP:
started = false;
removeMessages(KEEPALIVE_PING);
removeMessages(KEEPALIVE_TIMEOUT);
break;
case KEEPALIVE_RESET:
removeMessages(KEEPALIVE_PING);
removeMessages(KEEPALIVE_TIMEOUT);
if (started) {
sendMessageDelayed(obtainMessage(KEEPALIVE_PING), KEEPALIVE_TIMEOUT_MS);
}
break;
case KEEPALIVE_PING:
// Safe to ignore the result, error will be detected either the next time the radio service sends a message
// or when the keepalive timeout expires.
ANTTxMessage(KEEPALIVE_MESG);
sendMessageDelayed(obtainMessage(KEEPALIVE_TIMEOUT), KEEPALIVE_TIMEOUT_MS);
break;
case KEEPALIVE_TIMEOUT:
Log.e(TAG, "No response to keepalive message, attempting recovery.");
// Return ignored for same reasons as in mDeathHandler.
hardReset();
break;
}
}
};
}