blob: b841822907c1dba36db79e268008090243b55cbd [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.android.server.uwb;
import static android.uwb.UwbManager.MESSAGE_TYPE_COMMAND;
import static com.android.server.uwb.data.UwbUciConstants.FIRA_VERSION_MAJOR_2;
import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_OK;
import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD;
import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import android.uwb.IOnUwbActivityEnergyInfoListener;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbOemExtensionCallback;
import android.uwb.IUwbRangingCallbacks;
import android.uwb.IUwbVendorUciCallback;
import android.uwb.RangingChangeReason;
import android.uwb.SessionHandle;
import android.uwb.StateChangeReason;
import android.uwb.UwbActivityEnergyInfo;
import android.uwb.UwbAddress;
import android.uwb.UwbManager.AdapterStateCallback;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.uwb.data.UwbUciConstants;
import com.android.server.uwb.data.UwbVendorUciResponse;
import com.android.server.uwb.info.UwbPowerStats;
import com.android.server.uwb.jni.INativeUwbManager;
import com.android.server.uwb.jni.NativeUwbManager;
import com.google.uwb.support.base.Params;
import com.google.uwb.support.ccc.CccOpenRangingParams;
import com.google.uwb.support.ccc.CccParams;
import com.google.uwb.support.ccc.CccRangingReconfiguredParams;
import com.google.uwb.support.ccc.CccStartRangingParams;
import com.google.uwb.support.fira.FiraControleeParams;
import com.google.uwb.support.fira.FiraOpenSessionParams;
import com.google.uwb.support.fira.FiraParams;
import com.google.uwb.support.fira.FiraRangingReconfigureParams;
import com.google.uwb.support.generic.GenericParams;
import com.google.uwb.support.generic.GenericSpecificationParams;
import com.google.uwb.support.oemextension.DeviceStatus;
import com.google.uwb.support.profile.UuidBundleWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Core UWB stack.
*/
public class UwbServiceCore implements INativeUwbManager.DeviceNotification,
INativeUwbManager.VendorNotification, UwbCountryCode.CountryCodeChangedListener {
private static final String TAG = "UwbServiceCore";
@VisibleForTesting
public static final int TASK_ENABLE = 1;
@VisibleForTesting
public static final int TASK_DISABLE = 2;
@VisibleForTesting
public static final int TASK_RESTART = 3;
@VisibleForTesting
public static final int TASK_NOTIFY_ADAPTER_STATE = 4;
@VisibleForTesting
public static final int TASK_GET_POWER_STATS = 5;
@VisibleForTesting
public static final int WATCHDOG_MS = 10000;
private static final int SEND_VENDOR_CMD_TIMEOUT_MS = 10000;
private boolean mIsDiagnosticsEnabled = false;
private int mDiagramsFrameReportsFieldsFlags = 0;
private final PowerManager.WakeLock mUwbWakeLock;
private final Context mContext;
private final RemoteCallbackList<IUwbAdapterStateCallbacks>
mAdapterStateCallbacksList = new RemoteCallbackList<>();
private final UwbTask mUwbTask;
private final UwbSessionManager mSessionManager;
private final UwbConfigurationManager mConfigurationManager;
private final NativeUwbManager mNativeUwbManager;
private final UwbMetrics mUwbMetrics;
private final UwbCountryCode mUwbCountryCode;
private final UwbInjector mUwbInjector;
private final Map<String, /* @UwbManager.AdapterStateCallback.State */ Integer>
mChipIdToStateMap;
private @StateChangeReason int mLastStateChangedReason;
private @AdapterStateCallback.State int mLastAdapterStateNotification = -1;
private IUwbVendorUciCallback mCallBack = null;
private IUwbOemExtensionCallback mOemExtensionCallback = null;
private final Handler mHandler;
private GenericSpecificationParams mCachedSpecificationParams;
public UwbServiceCore(Context uwbApplicationContext, NativeUwbManager nativeUwbManager,
UwbMetrics uwbMetrics, UwbCountryCode uwbCountryCode,
UwbSessionManager uwbSessionManager, UwbConfigurationManager uwbConfigurationManager,
UwbInjector uwbInjector, Looper serviceLooper) {
mContext = uwbApplicationContext;
Log.d(TAG, "Starting Uwb");
mUwbWakeLock = mContext.getSystemService(PowerManager.class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "UwbServiceCore:mUwbWakeLock");
mNativeUwbManager = nativeUwbManager;
mNativeUwbManager.setDeviceListener(this);
mNativeUwbManager.setVendorListener(this);
mUwbMetrics = uwbMetrics;
mUwbCountryCode = uwbCountryCode;
mUwbCountryCode.addListener(this);
mSessionManager = uwbSessionManager;
mConfigurationManager = uwbConfigurationManager;
mUwbInjector = uwbInjector;
mChipIdToStateMap = new HashMap<>();
mUwbInjector.getMultichipData().setOnInitializedListener(
() -> {
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
updateState(AdapterStateCallback.STATE_DISABLED,
StateChangeReason.SYSTEM_BOOT,
chipId);
}
});
mUwbTask = new UwbTask(serviceLooper);
mHandler = new Handler(serviceLooper);
}
public Handler getHandler() {
return mHandler;
}
public boolean isOemExtensionCbRegistered() {
return mOemExtensionCallback != null;
}
public IUwbOemExtensionCallback getOemExtensionCallback() {
return mOemExtensionCallback;
}
private void updateState(int state, int reason, String chipId) {
Log.d(TAG, "updateState(): state=" + state + ", reason=" + reason + ", chipId=" + chipId);
synchronized (UwbServiceCore.this) {
mChipIdToStateMap.put(chipId, state);
mLastStateChangedReason = reason;
Log.d(TAG, "chipIdToStateMap = " + mChipIdToStateMap);
}
}
private boolean isUwbEnabled() {
synchronized (UwbServiceCore.this) {
return getInternalAdapterState() != AdapterStateCallback.STATE_DISABLED;
}
}
String getDeviceStateString(int state) {
String ret = "";
switch (state) {
case UwbUciConstants.DEVICE_STATE_OFF:
ret = "OFF";
break;
case UwbUciConstants.DEVICE_STATE_READY:
ret = "READY";
break;
case UwbUciConstants.DEVICE_STATE_ACTIVE:
ret = "ACTIVE";
break;
case UwbUciConstants.DEVICE_STATE_ERROR:
ret = "ERROR";
break;
}
return ret;
}
@Override
public void onVendorUciNotificationReceived(int gid, int oid, byte[] payload) {
Log.i(TAG, "onVendorUciNotificationReceived");
if (mCallBack != null) {
try {
mCallBack.onVendorNotificationReceived(gid, oid, payload);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send vendor notification", e);
}
}
}
@Override
public void onDeviceStatusNotificationReceived(int deviceState, String chipId) {
// If error status is received, toggle UWB off to reset stack state.
// TODO(b/227488208): Should we try to restart (like wifi) instead?
if (!mUwbInjector.getMultichipData().getChipIds().contains(chipId)) {
Log.e(TAG, "onDeviceStatusNotificationReceived with invalid chipId " + chipId
+ ". Ignoring...");
return;
}
if ((byte) deviceState == UwbUciConstants.DEVICE_STATE_ERROR) {
Log.e(TAG, "Error device status received. Restarting...");
mUwbMetrics.incrementDeviceStatusErrorCount();
takBugReportAfterDeviceError("UWB Bugreport: restarting UWB due to device error");
mUwbTask.execute(TASK_RESTART);
oemExtensionDeviceStatusUpdate(deviceState, chipId);
return;
}
updateDeviceState(deviceState, chipId);
mUwbTask.computeAndNotifyAdapterStateChange(
getReasonFromDeviceState(deviceState),
mUwbCountryCode.getCountryCode(),
Optional.empty());
}
void updateDeviceState(int deviceState, String chipId) {
Log.i(TAG, "updateState(): deviceState = " + getDeviceStateString(deviceState)
+ ", current internal adapter state = " + getInternalAdapterState());
oemExtensionDeviceStatusUpdate(deviceState, chipId);
updateState(
getAdapterStateFromDeviceState(deviceState),
getReasonFromDeviceState(deviceState),
chipId);
}
void oemExtensionDeviceStatusUpdate(int deviceState, String chipId) {
if (mOemExtensionCallback != null) {
PersistableBundle deviceStateBundle = new DeviceStatus.Builder()
.setDeviceState(deviceState)
.setChipId(chipId)
.build()
.toBundle();
try {
mOemExtensionCallback.onDeviceStatusNotificationReceived(deviceStateBundle);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send status notification to oem", e);
}
}
}
void notifyAdapterState(int adapterState, int reason) {
// Check if the current adapter state is same as the state in the last adapter state
// notification, to avoid sending extra onAdapterStateChanged() notifications. For example,
// this can happen when UWB is toggled on and a valid country code is already set.
if (mLastAdapterStateNotification == adapterState) {
return;
}
Log.d(TAG, "notifyAdapterState(): adapterState = " + adapterState + ", reason = " + reason);
if (mAdapterStateCallbacksList.getRegisteredCallbackCount() == 0) {
return;
}
final int count = mAdapterStateCallbacksList.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
mAdapterStateCallbacksList.getBroadcastItem(i)
.onAdapterStateChanged(adapterState, reason);
} catch (RemoteException e) {
Log.e(TAG, "onAdapterStateChanged is failed");
}
}
mAdapterStateCallbacksList.finishBroadcast();
mLastAdapterStateNotification = adapterState;
}
int getAdapterStateFromDeviceState(int deviceState) {
int adapterState = AdapterStateCallback.STATE_DISABLED;
if (deviceState == UwbUciConstants.DEVICE_STATE_OFF) {
adapterState = AdapterStateCallback.STATE_DISABLED;
} else if (deviceState == UwbUciConstants.DEVICE_STATE_READY) {
adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE;
} else if (deviceState == UwbUciConstants.DEVICE_STATE_ACTIVE) {
adapterState = AdapterStateCallback.STATE_ENABLED_ACTIVE;
}
return adapterState;
}
int getReasonFromDeviceState(int deviceState) {
int reason = StateChangeReason.UNKNOWN;
if (deviceState == UwbUciConstants.DEVICE_STATE_OFF) {
reason = StateChangeReason.SYSTEM_POLICY;
} else if (deviceState == UwbUciConstants.DEVICE_STATE_READY) {
reason = StateChangeReason.SYSTEM_POLICY;
} else if (deviceState == UwbUciConstants.DEVICE_STATE_ACTIVE) {
reason = StateChangeReason.SESSION_STARTED;
}
return reason;
}
@Override
public void onCoreGenericErrorNotificationReceived(int status, String chipId) {
if (!mUwbInjector.getMultichipData().getChipIds().contains(chipId)) {
Log.e(TAG, "onCoreGenericErrorNotificationReceived with invalid chipId "
+ chipId + ". Ignoring...");
return;
}
Log.e(TAG, "onCoreGenericErrorNotificationReceived status = " + status);
mUwbMetrics.incrementUciGenericErrorCount();
}
@Override
public void onCountryCodeChanged(@Nullable String countryCode) {
Log.i(TAG, "Received onCountryCodeChanged() with countryCode = " + countryCode);
if (mUwbCountryCode.isValid(countryCode)) {
// Notify the current UWB adapter state. For example, if UWB was earlier enabled and at
// that time the country code was not valid, will now notify STATE_ENABLED_INACTIVE.
mUwbTask.computeAndNotifyAdapterStateChange(
mLastStateChangedReason, countryCode, Optional.empty());
}
}
public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
mAdapterStateCallbacksList.register(adapterStateCallbacks);
adapterStateCallbacks.onAdapterStateChanged(getAdapterState(), mLastStateChangedReason);
}
public void unregisterAdapterStateCallbacks(IUwbAdapterStateCallbacks callbacks) {
mAdapterStateCallbacksList.unregister(callbacks);
}
public void registerVendorExtensionCallback(IUwbVendorUciCallback callbacks) {
Log.e(TAG, "Register the callback");
mCallBack = callbacks;
}
public void unregisterVendorExtensionCallback(IUwbVendorUciCallback callbacks) {
Log.e(TAG, "Unregister the callback");
mCallBack = null;
}
public void registerOemExtensionCallback(IUwbOemExtensionCallback callback) {
if (isOemExtensionCbRegistered()) {
Log.w(TAG, "Oem extension callback being re-registered");
}
Log.e(TAG, "Register Oem Extension callback");
mOemExtensionCallback = callback;
}
public void unregisterOemExtensionCallback(IUwbOemExtensionCallback callback) {
Log.e(TAG, "Unregister Oem Extension callback");
mOemExtensionCallback = null;
}
/**
* Get cached specification params
*/
public GenericSpecificationParams getCachedSpecificationParams(String chipId) {
if (mCachedSpecificationParams != null) return mCachedSpecificationParams;
// If nothing in cache, populate it.
getSpecificationInfo(chipId);
return mCachedSpecificationParams;
}
/**
* Get specification info
*/
public PersistableBundle getSpecificationInfo(String chipId) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
// TODO(b/211445008): Consolidate to a single uwb thread.
Pair<Integer, GenericSpecificationParams> specificationParams =
mConfigurationManager.getCapsInfo(
GenericParams.PROTOCOL_NAME, GenericSpecificationParams.class, chipId);
if (specificationParams.first != UwbUciConstants.STATUS_CODE_OK
|| specificationParams.second == null) {
Log.e(TAG, "Failed to retrieve specification params");
return new PersistableBundle();
}
mCachedSpecificationParams = specificationParams.second;
return specificationParams.second.toBundle();
}
public long getTimestampResolutionNanos() {
return mNativeUwbManager.getTimestampResolutionNanos();
}
/** Set whether diagnostics is enabled and set enabled fields */
public void enableDiagnostics(boolean enabled, int flags) {
this.mIsDiagnosticsEnabled = enabled;
this.mDiagramsFrameReportsFieldsFlags = flags;
}
public void openRanging(
AttributionSource attributionSource,
SessionHandle sessionHandle,
IUwbRangingCallbacks rangingCallbacks,
PersistableBundle params,
String chipId) throws RemoteException {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
int sessionId = 0;
int sessionType = 0;
if (UuidBundleWrapper.isUuidBundle(params)) {
UuidBundleWrapper uuidBundleWrapper = UuidBundleWrapper.fromBundle(params);
mUwbInjector.getProfileManager().activateProfile(
attributionSource,
sessionHandle,
uuidBundleWrapper.getServiceInstanceID().get(),
rangingCallbacks,
chipId
);
} else if (FiraParams.isCorrectProtocol(params)) {
FiraOpenSessionParams.Builder builder =
new FiraOpenSessionParams.Builder(FiraOpenSessionParams.fromBundle(params));
if (getCachedSpecificationParams(chipId)
.getFiraSpecificationParams().hasRssiReportingSupport()) {
builder.setIsRssiReportingEnabled(true);
}
if (this.mIsDiagnosticsEnabled && getCachedSpecificationParams(chipId)
.getFiraSpecificationParams().hasDiagnosticsSupport()) {
builder.setIsDiagnosticsEnabled(true);
builder.setDiagramsFrameReportsFieldsFlags(mDiagramsFrameReportsFieldsFlags);
}
FiraOpenSessionParams firaOpenSessionParams = builder.build();
sessionId = firaOpenSessionParams.getSessionId();
sessionType = firaOpenSessionParams.getSessionType();
mSessionManager.initSession(attributionSource, sessionHandle, sessionId,
(byte) sessionType, firaOpenSessionParams.getProtocolName(),
firaOpenSessionParams, rangingCallbacks, chipId);
} else if (CccParams.isCorrectProtocol(params)) {
CccOpenRangingParams cccOpenRangingParams = CccOpenRangingParams.fromBundle(params);
sessionId = cccOpenRangingParams.getSessionId();
sessionType = cccOpenRangingParams.getSessionType();
mSessionManager.initSession(attributionSource, sessionHandle, sessionId,
(byte) sessionType, cccOpenRangingParams.getProtocolName(),
cccOpenRangingParams, rangingCallbacks, chipId);
} else {
Log.e(TAG, "openRanging - Wrong parameters");
try {
rangingCallbacks.onRangingOpenFailed(sessionHandle,
RangingChangeReason.BAD_PARAMETERS, new PersistableBundle());
} catch (RemoteException e) { }
}
}
public void startRanging(SessionHandle sessionHandle, PersistableBundle params)
throws IllegalStateException {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
Params startRangingParams = null;
if (CccParams.isCorrectProtocol(params)) {
startRangingParams = CccStartRangingParams.fromBundle(params);
}
if (mUwbInjector.getProfileManager().hasSession(sessionHandle)) {
mUwbInjector.getProfileManager().startRanging(sessionHandle);
} else {
mSessionManager.startRanging(sessionHandle, startRangingParams);
}
}
public void reconfigureRanging(SessionHandle sessionHandle, PersistableBundle params) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
Params reconfigureRangingParams = null;
if (FiraParams.isCorrectProtocol(params)) {
reconfigureRangingParams = FiraRangingReconfigureParams.fromBundle(params);
} else if (CccParams.isCorrectProtocol(params)) {
reconfigureRangingParams = CccRangingReconfiguredParams.fromBundle(params);
}
mSessionManager.reconfigure(sessionHandle, reconfigureRangingParams);
}
public void stopRanging(SessionHandle sessionHandle) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
if (mUwbInjector.getProfileManager().hasSession(sessionHandle)) {
mUwbInjector.getProfileManager().stopRanging(sessionHandle);
} else {
mSessionManager.stopRanging(sessionHandle);
}
}
public void closeRanging(SessionHandle sessionHandle) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
if (mUwbInjector.getProfileManager().hasSession(sessionHandle)) {
mUwbInjector.getProfileManager().closeRanging(sessionHandle);
} else {
mSessionManager.deInitSession(sessionHandle);
}
}
public void addControlee(SessionHandle sessionHandle, PersistableBundle params) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
Params reconfigureRangingParams = null;
if (FiraParams.isCorrectProtocol(params)) {
FiraControleeParams controleeParams = FiraControleeParams.fromBundle(params);
reconfigureRangingParams = new FiraRangingReconfigureParams.Builder()
.setAction(MULTICAST_LIST_UPDATE_ACTION_ADD)
.setAddressList(controleeParams.getAddressList())
.setSubSessionIdList(controleeParams.getSubSessionIdList())
.build();
}
mSessionManager.reconfigure(sessionHandle, reconfigureRangingParams);
}
public void removeControlee(SessionHandle sessionHandle, PersistableBundle params) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
Params reconfigureRangingParams = null;
if (FiraParams.isCorrectProtocol(params)) {
FiraControleeParams controleeParams = FiraControleeParams.fromBundle(params);
reconfigureRangingParams = new FiraRangingReconfigureParams.Builder()
.setAction(MULTICAST_LIST_UPDATE_ACTION_DELETE)
.setAddressList(controleeParams.getAddressList())
.setSubSessionIdList(controleeParams.getSubSessionIdList())
.build();
}
mSessionManager.reconfigure(sessionHandle, reconfigureRangingParams);
}
/** Send the payload data to a remote device in the UWB session */
public void sendData(SessionHandle sessionHandle, UwbAddress remoteDeviceAddress,
PersistableBundle params, byte[] data) throws RemoteException {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
mSessionManager.sendData(sessionHandle, remoteDeviceAddress, params, data);
}
/**
* Get the UWB Adapter State.
*/
public /* @UwbManager.AdapterStateCallback.State */ int getAdapterState() {
return computeAdapterState(mUwbCountryCode.getCountryCode(), Optional.empty());
}
private int computeAdapterState(String countryCode, Optional<Integer> setCountryCodeStatus) {
// When either the country code is not valid or setting it in UWBS failed with an error,
// notify the UWB stack state as DISABLED (even though internally the UWB device state
// may be stored as READY), so that applications wait for starting a ranging session.
if (!mUwbCountryCode.isValid(countryCode)
|| (setCountryCodeStatus.isPresent()
&& setCountryCodeStatus.get() != STATUS_CODE_OK)) {
return AdapterStateCallback.STATE_DISABLED;
}
return getInternalAdapterState();
}
private /* @UwbManager.AdapterStateCallback.State */ int getInternalAdapterState() {
synchronized (UwbServiceCore.this) {
if (mChipIdToStateMap.isEmpty()) {
return AdapterStateCallback.STATE_DISABLED;
}
boolean isActive = false;
for (int state : mChipIdToStateMap.values()) {
if (state == AdapterStateCallback.STATE_DISABLED) {
return AdapterStateCallback.STATE_DISABLED;
}
if (state == AdapterStateCallback.STATE_ENABLED_ACTIVE) {
isActive = true;
}
}
return isActive ? AdapterStateCallback.STATE_ENABLED_ACTIVE
: AdapterStateCallback.STATE_ENABLED_INACTIVE;
}
}
public synchronized void setEnabled(boolean enabled) {
int task = enabled ? TASK_ENABLE : TASK_DISABLE;
if (enabled && isUwbEnabled()) {
Log.w(TAG, "Uwb is already enabled");
} else if (!enabled && !isUwbEnabled()) {
Log.w(TAG, "Uwb is already disabled");
}
mUwbTask.execute(task);
}
private void sendVendorUciResponse(int gid, int oid, byte[] payload) {
Log.i(TAG, "onVendorUciResponseReceived");
if (mCallBack != null) {
try {
mCallBack.onVendorResponseReceived(gid, oid, payload);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send vendor response", e);
}
}
}
/**
* Send vendor UCI message
*
* @param chipId : Identifier of UWB chip for multi-HAL devices
*/
public synchronized int sendVendorUciMessage(int mt, int gid, int oid, byte[] payload,
String chipId) {
if ((!isUwbEnabled())) {
Log.e(TAG, "sendRawVendor : Uwb is not enabled");
return UwbUciConstants.STATUS_CODE_FAILED;
}
// Testing message type is only allowed in version FiRa 2.0 and above.
if (mt != MESSAGE_TYPE_COMMAND && getCachedSpecificationParams(chipId)
.getFiraSpecificationParams()
.getMaxMacVersionSupported()
.getMajor() < FIRA_VERSION_MAJOR_2) {
Log.e(TAG, "Message Type " + mt + " not supported in this FiRa version");
return UwbUciConstants.STATUS_CODE_FAILED;
}
// TODO(b/211445008): Consolidate to a single uwb thread.
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask<Byte> sendVendorCmdTask = new FutureTask<>(
() -> {
UwbVendorUciResponse response =
mNativeUwbManager.sendRawVendorCmd(mt, gid, oid, payload, chipId);
if (response.status == UwbUciConstants.STATUS_CODE_OK) {
sendVendorUciResponse(response.gid, response.oid, response.payload);
}
return response.status;
});
executor.submit(sendVendorCmdTask);
int status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = sendVendorCmdTask.get(
SEND_VENDOR_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
executor.shutdownNow();
Log.i(TAG, "Failed to send vendor command - status : TIMEOUT");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return status;
}
public void rangingRoundsUpdateDtTag(SessionHandle sessionHandle,
PersistableBundle params) throws RemoteException {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
mSessionManager.rangingRoundsUpdateDtTag(sessionHandle, params);
}
/**
* Query max application data size that can be sent by UWBS in one ranging round.
*/
public int queryMaxDataSizeBytes(SessionHandle sessionHandle) {
if (!isUwbEnabled()) {
throw new IllegalStateException("Uwb is not enabled");
}
return mSessionManager.queryMaxDataSizeBytes(sessionHandle);
}
/**
* Update the pose used by the filter engine to distinguish tag position changes from device
* position changes.
*/
public void updatePose(SessionHandle sessionHandle, PersistableBundle params) {
mSessionManager.updatePose(sessionHandle, params);
}
private class UwbTask extends Handler {
UwbTask(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
int type = msg.what;
switch (type) {
case TASK_ENABLE:
handleEnable();
break;
case TASK_DISABLE:
mSessionManager.deinitAllSession();
handleDisable();
break;
case TASK_RESTART:
mSessionManager.deinitAllSession();
handleDisable();
handleEnable();
break;
case TASK_NOTIFY_ADAPTER_STATE:
handleNotifyAdapterState(msg.arg1, msg.arg2);
break;
case TASK_GET_POWER_STATS:
invokeUwbActivityEnergyInfoListener((IOnUwbActivityEnergyInfoListener) msg.obj);
break;
default:
Log.d(TAG, "UwbTask : Undefined Task");
break;
}
}
public void execute(int task) {
Message msg = mUwbTask.obtainMessage();
msg.what = task;
this.sendMessage(msg);
}
public void execute(int task, int arg1, int arg2) {
Message msg = mUwbTask.obtainMessage();
msg.what = task;
msg.arg1 = arg1;
msg.arg2 = arg2;
this.sendMessage(msg);
}
public void execute(int task, Object obj) {
Message msg = mUwbTask.obtainMessage();
msg.what = task;
msg.obj = obj;
this.sendMessage(msg);
}
private void executeUnique(int task, int arg1, int arg2) {
mUwbTask.removeMessages(task);
Message msg = mUwbTask.obtainMessage();
msg.what = task;
msg.arg1 = arg1;
msg.arg2 = arg2;
this.sendMessage(msg);
}
private void delayedExecute(int task, int arg1, int arg2, int delayMillis) {
Message msg = mUwbTask.obtainMessage();
msg.what = task;
msg.arg1 = arg1;
msg.arg2 = arg2;
this.sendMessageDelayed(msg, delayMillis);
}
private void handleEnable() {
if (isUwbEnabled()) {
Log.i(TAG, "UWB service is already enabled");
return;
}
try {
WatchDogThread watchDog = new WatchDogThread("handleEnable", WATCHDOG_MS);
watchDog.start();
Log.i(TAG, "Initialization start ...");
synchronized (mUwbWakeLock) {
mUwbWakeLock.acquire();
}
try {
if (!mNativeUwbManager.doInitialize()) {
Log.e(TAG, "Error enabling UWB");
mUwbMetrics.incrementDeviceInitFailureCount();
takBugReportAfterDeviceError("UWB Bugreport: error enabling UWB");
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
updateState(AdapterStateCallback.STATE_DISABLED,
StateChangeReason.SYSTEM_POLICY, chipId);
}
} else {
Log.i(TAG, "Initialization success");
/* TODO : keep it until MW, FW fix b/196943897 */
mUwbMetrics.incrementDeviceInitSuccessCount();
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
Log.d(TAG, "enabling chip " + chipId);
updateDeviceState(UwbUciConstants.DEVICE_STATE_READY, chipId);
}
// Set country code on every enable (example: for the scenario when the
// country code was determined/changed while the UWB stack was disabled).
//
// TODO(b/255977441): Handle the case when the countryCode is valid and
// setting the country code returned an error by doing a UWBS reset.
Pair<Integer, String> setCountryCodeResult =
mUwbCountryCode.setCountryCode(true);
Optional<Integer> setCountryCodeStatus =
Optional.of(setCountryCodeResult.first);
String countryCode = setCountryCodeResult.second;
Log.i(TAG, "Current country code = " + countryCode);
computeAndNotifyAdapterStateChange(
getReasonFromDeviceState(UwbUciConstants.DEVICE_STATE_READY),
countryCode,
setCountryCodeStatus);
}
} finally {
synchronized (mUwbWakeLock) {
if (mUwbWakeLock.isHeld()) {
mUwbWakeLock.release();
}
}
watchDog.cancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleDisable() {
if (!isUwbEnabled()) {
Log.i(TAG, "UWB service is already disabled");
return;
}
WatchDogThread watchDog = new WatchDogThread("handleDisable", WATCHDOG_MS);
watchDog.start();
try {
Log.i(TAG, "Deinitialization start ...");
synchronized (mUwbWakeLock) {
mUwbWakeLock.acquire();
}
if (!mNativeUwbManager.doDeinitialize()) {
Log.w(TAG, "Error disabling UWB");
} else {
Log.i(TAG, "Deinitialization success");
}
/* UWBS_STATUS_OFF is not the valid state. so handle device state directly */
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
updateDeviceState(UwbUciConstants.DEVICE_STATE_OFF, chipId);
}
notifyAdapterState(
getAdapterStateFromDeviceState(UwbUciConstants.DEVICE_STATE_OFF),
getReasonFromDeviceState(UwbUciConstants.DEVICE_STATE_OFF));
} finally {
synchronized (mUwbWakeLock) {
if (mUwbWakeLock.isHeld()) {
mUwbWakeLock.release();
}
}
watchDog.cancel();
}
}
private void handleNotifyAdapterState(int adapterState, int reason) {
try {
// TODO(b/255977441): For state ready/active, we should notify only when the
// country code is valid, else do some error handling.
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
notifyAdapterState(adapterState, reason);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void computeAndNotifyAdapterStateChange(int reason,
String countryCode, Optional<Integer> setCountryCodeStatus) {
// When either the country code is not valid or setting it in UWBS failed with the error
// STATUS_CODE_ANDROID_REGULATION_UWB_OFF, notify with the reason SYSTEM_REGULATION.
if (!mUwbCountryCode.isValid(countryCode)
|| (setCountryCodeStatus.isPresent()
&& setCountryCodeStatus.get()
== UwbUciConstants.STATUS_CODE_ANDROID_REGULATION_UWB_OFF)) {
reason = StateChangeReason.SYSTEM_REGULATION;
}
notifyAdapterState(computeAdapterState(countryCode, setCountryCodeStatus), reason);
}
public class WatchDogThread extends Thread {
final Object mCancelWaiter = new Object();
final int mTimeout;
boolean mCanceled = false;
WatchDogThread(String threadName, int timeout) {
super(threadName);
mTimeout = timeout;
}
@Override
public void run() {
try {
synchronized (mCancelWaiter) {
mCancelWaiter.wait(mTimeout);
if (mCanceled) {
return;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
interrupt();
}
synchronized (mUwbWakeLock) {
if (mUwbWakeLock.isHeld()) {
mUwbWakeLock.release();
}
}
}
public synchronized void cancel() {
synchronized (mCancelWaiter) {
mCanceled = true;
mCancelWaiter.notify();
}
}
}
}
private void takBugReportAfterDeviceError(String bugTitle) {
if (mUwbInjector.getDeviceConfigFacade().isDeviceErrorBugreportEnabled()) {
mUwbInjector.getUwbDiagnostics().takeBugReport(bugTitle);
}
}
/**
* Dump the UWB session manager debug info
*/
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- Dump of UwbServiceCore ----");
for (String chipId : mUwbInjector.getMultichipData().getChipIds()) {
pw.println("Device state = " + getDeviceStateString(mChipIdToStateMap.get(chipId))
+ " for chip id = " + chipId);
}
pw.println("mLastStateChangedReason = " + mLastStateChangedReason);
pw.println("mLastAdapterStateNotification = " + mLastAdapterStateNotification);
pw.println("---- Dump of UwbServiceCore ----");
}
/**
* Report the UWB power stats to the listener
*/
public synchronized void reportUwbActivityEnergyInfo(
IOnUwbActivityEnergyInfoListener listener) {
mUwbTask.execute(TASK_GET_POWER_STATS, listener);
}
private void invokeUwbActivityEnergyInfoListener(IOnUwbActivityEnergyInfoListener listener) {
try {
listener.onUwbActivityEnergyInfo(getUwbActivityEnergyInfo());
} catch (RemoteException e) {
Log.e(TAG, "onUwbActivityEnergyInfo: RemoteException -- ", e);
}
}
private UwbActivityEnergyInfo getUwbActivityEnergyInfo() {
try {
String chipId = mUwbInjector.getMultichipData().getDefaultChipId();
PersistableBundle bundle = getSpecificationInfo(chipId);
GenericSpecificationParams params = GenericSpecificationParams.fromBundle(bundle);
if (!isUwbEnabled() || params == null || !params.hasPowerStatsSupport()) {
return null;
}
UwbPowerStats stats = mNativeUwbManager.getPowerStats(chipId);
if (stats == null) {
return null;
}
Log.d(TAG, " getUwbActivityEnergyInfo: "
+ " tx_time_millis=" + stats.getTxTimeMs()
+ " rx_time_millis=" + stats.getRxTimeMs()
+ " rxIdleTimeMillis=" + stats.getIdleTimeMs()
+ " wake_count=" + stats.getTotalWakeCount());
return new UwbActivityEnergyInfo.Builder()
.setTimeSinceBootMillis(SystemClock.elapsedRealtime())
.setStackState(getInternalAdapterState())
.setControllerTxDurationMillis(stats.getTxTimeMs())
.setControllerRxDurationMillis(stats.getRxTimeMs())
.setControllerIdleDurationMillis(stats.getIdleTimeMs())
.setControllerWakeCount(stats.getTotalWakeCount())
.build();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}