blob: 6349e23f5769ceb8c9909299df0b11dbc64dd7be [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.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_EXTENDED;
import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT;
import static com.android.server.uwb.data.UwbUciConstants.RANGING_DEVICE_ROLE_OBSERVER;
import static com.android.server.uwb.data.UwbUciConstants.REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS;
import static com.android.server.uwb.data.UwbUciConstants.ROUND_USAGE_OWR_AOA_MEASUREMENT;
import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_OK;
import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN;
import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_SHORT_MAC_ADDRESS_LEN;
import static com.android.server.uwb.data.UwbUciConstants.UWB_SESSION_STATE_ACTIVE;
import static com.android.server.uwb.util.DataTypeConversionUtil.macAddressByteArrayToLong;
import static com.google.uwb.support.fira.FiraParams.FILTER_TYPE_APPLICATION;
import static com.google.uwb.support.fira.FiraParams.FILTER_TYPE_DEFAULT;
import static com.google.uwb.support.fira.FiraParams.FILTER_TYPE_NONE;
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 static com.google.uwb.support.fira.FiraParams.PROTOCOL_NAME;
import static com.google.uwb.support.fira.FiraParams.P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_16_BYTE;
import static com.google.uwb.support.fira.FiraParams.P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_32_BYTE;
import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_DISABLE;
import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_ENABLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.AttributionSource;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
import android.util.Pair;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbRangingCallbacks;
import android.uwb.RangingChangeReason;
import android.uwb.SessionHandle;
import android.uwb.UwbAddress;
import androidx.annotation.VisibleForTesting;
import com.android.server.uwb.advertisement.UwbAdvertiseManager;
import com.android.server.uwb.correction.UwbFilterEngine;
import com.android.server.uwb.correction.pose.ApplicationPoseSource;
import com.android.server.uwb.correction.pose.IPoseSource;
import com.android.server.uwb.data.DtTagUpdateRangingRoundsStatus;
import com.android.server.uwb.data.UwbMulticastListUpdateStatus;
import com.android.server.uwb.data.UwbOwrAoaMeasurement;
import com.android.server.uwb.data.UwbRangingData;
import com.android.server.uwb.data.UwbTwoWayMeasurement;
import com.android.server.uwb.data.UwbUciConstants;
import com.android.server.uwb.jni.INativeUwbManager;
import com.android.server.uwb.jni.NativeUwbManager;
import com.android.server.uwb.proto.UwbStatsLog;
import com.android.server.uwb.util.ArrayUtils;
import com.android.server.uwb.util.DataTypeConversionUtil;
import com.android.server.uwb.util.LruList;
import com.android.server.uwb.util.UwbUtil;
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.CccRangingStartedParams;
import com.google.uwb.support.ccc.CccSpecificationParams;
import com.google.uwb.support.ccc.CccStartRangingParams;
import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate;
import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdateStatus;
import com.google.uwb.support.fira.FiraOpenSessionParams;
import com.google.uwb.support.fira.FiraParams;
import com.google.uwb.support.fira.FiraPoseUpdateParams;
import com.google.uwb.support.fira.FiraRangingReconfigureParams;
import com.google.uwb.support.fira.FiraSpecificationParams;
import com.google.uwb.support.generic.GenericSpecificationParams;
import com.google.uwb.support.oemextension.AdvertisePointedTarget;
import com.google.uwb.support.oemextension.SessionStatus;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
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;
import java.util.stream.Collectors;
public class UwbSessionManager implements INativeUwbManager.SessionNotification,
ActivityManager.OnUidImportanceListener {
private static final String TAG = "UwbSessionManager";
private static final byte OPERATION_TYPE_INIT_SESSION = 0;
@VisibleForTesting
public static final int SESSION_OPEN_RANGING = 1;
@VisibleForTesting
public static final int SESSION_START_RANGING = 2;
@VisibleForTesting
public static final int SESSION_STOP_RANGING = 3;
@VisibleForTesting
public static final int SESSION_RECONFIG_RANGING = 4;
@VisibleForTesting
public static final int SESSION_DEINIT = 5;
@VisibleForTesting
public static final int SESSION_ON_DEINIT = 6;
@VisibleForTesting
public static final int SESSION_SEND_DATA = 7;
@VisibleForTesting
public static final int SESSION_UPDATE_DT_TAG_RANGING_ROUNDS = 8;
// TODO: don't expose the internal field for testing.
@VisibleForTesting
final ConcurrentHashMap<SessionHandle, UwbSession> mSessionTable = new ConcurrentHashMap();
// Used for storing recently closed sessions for debugging purposes.
final LruList<UwbSession> mDbgRecentlyClosedSessions = new LruList<>(5);
final ConcurrentHashMap<Integer, List<UwbSession>> mNonPrivilegedUidToFiraSessionsTable =
new ConcurrentHashMap();
final ConcurrentHashMap<Integer, Integer> mSessionTokenMap = new ConcurrentHashMap<>();
private final ActivityManager mActivityManager;
private final NativeUwbManager mNativeUwbManager;
private final UwbMetrics mUwbMetrics;
private final UwbConfigurationManager mConfigurationManager;
private final UwbSessionNotificationManager mSessionNotificationManager;
private final UwbAdvertiseManager mAdvertiseManager;
private final UwbInjector mUwbInjector;
private final AlarmManager mAlarmManager;
private final Looper mLooper;
private final EventTask mEventTask;
private Boolean mIsRangeDataNtfConfigEnableDisableSupported;
public UwbSessionManager(
UwbConfigurationManager uwbConfigurationManager,
NativeUwbManager nativeUwbManager, UwbMetrics uwbMetrics,
UwbAdvertiseManager uwbAdvertiseManager,
UwbSessionNotificationManager uwbSessionNotificationManager,
UwbInjector uwbInjector, AlarmManager alarmManager, ActivityManager activityManager,
Looper serviceLooper) {
mNativeUwbManager = nativeUwbManager;
mNativeUwbManager.setSessionListener(this);
mUwbMetrics = uwbMetrics;
mAdvertiseManager = uwbAdvertiseManager;
mConfigurationManager = uwbConfigurationManager;
mSessionNotificationManager = uwbSessionNotificationManager;
mUwbInjector = uwbInjector;
mAlarmManager = alarmManager;
mActivityManager = activityManager;
mLooper = serviceLooper;
mEventTask = new EventTask(serviceLooper);
registerUidImportanceTransitions();
}
private boolean isRangeDataNtfConfigEnableDisableSupported() {
if (mIsRangeDataNtfConfigEnableDisableSupported == null) {
String defaultChipId = mUwbInjector.getMultichipData().getDefaultChipId();
GenericSpecificationParams specificationParams =
mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(defaultChipId);
if (specificationParams == null) return false;
EnumSet<FiraParams.RangeDataNtfConfigCapabilityFlag> supportedRangeDataNtfConfigs =
specificationParams.getFiraSpecificationParams()
.getRangeDataNtfConfigCapabilities();
mIsRangeDataNtfConfigEnableDisableSupported =
supportedRangeDataNtfConfigs.containsAll(EnumSet.of(
HAS_RANGE_DATA_NTF_CONFIG_DISABLE,
HAS_RANGE_DATA_NTF_CONFIG_ENABLE));
}
return mIsRangeDataNtfConfigEnableDisableSupported;
}
@Override
public void onUidImportance(final int uid, final int importance) {
Handler handler = new Handler(mLooper);
handler.post(() -> {
List<UwbSession> uwbSessions = mNonPrivilegedUidToFiraSessionsTable.get(uid);
// Not a uid in the watch list
if (uwbSessions == null) return;
boolean newModeHasNonPrivilegedFgApp =
UwbInjector.isForegroundAppOrServiceImportance(importance);
for (UwbSession uwbSession : uwbSessions) {
// already at correct state.
if (newModeHasNonPrivilegedFgApp == uwbSession.hasNonPrivilegedFgApp()) {
continue;
}
uwbSession.setHasNonPrivilegedFgApp(newModeHasNonPrivilegedFgApp);
int sessionId = uwbSession.getSessionId();
Log.i(TAG, "App state change for session " + sessionId + ". IsFg: "
+ newModeHasNonPrivilegedFgApp);
// Reconfigure the session based on the new fg/bg state if
// NtfConfigEnableDisable is supported.
if (isRangeDataNtfConfigEnableDisableSupported()) {
Log.i(TAG, "Session " + sessionId
+ " reconfiguring ntf control due to app state change");
uwbSession.reconfigureFiraSessionOnFgStateChange();
}
// Recalculate session priority based on the new fg/bg state.
if (!uwbSession.mSessionPriorityOverride) {
int newSessionPriority = uwbSession.calculateSessionPriority();
Log.i(TAG, "Session " + sessionId
+ " recalculating session priority, new priority: "
+ newSessionPriority);
uwbSession.setStackSessionPriority(newSessionPriority);
}
}
});
}
// Detect UIDs going foreground/background
private void registerUidImportanceTransitions() {
mActivityManager.addOnUidImportanceListener(
UwbSessionManager.this, IMPORTANCE_FOREGROUND_SERVICE);
}
private static boolean hasAllRangingResultError(@NonNull UwbRangingData rangingData) {
if (rangingData.getRangingMeasuresType()
== UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY) {
for (UwbTwoWayMeasurement measure : rangingData.getRangingTwoWayMeasures()) {
if (measure.isStatusCodeOk()) {
return false;
}
}
} else if (rangingData.getRangingMeasuresType()
== UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA) {
UwbOwrAoaMeasurement measure = rangingData.getRangingOwrAoaMeasure();
if (measure.getRangingStatus() == UwbUciConstants.STATUS_CODE_OK) {
return false;
}
}
return true;
}
@Override
public void onRangeDataNotificationReceived(UwbRangingData rangingData) {
Trace.beginSection("UWB#onRangeDataNotificationReceived");
long sessionId = rangingData.getSessionId();
UwbSession uwbSession = getUwbSession((int) sessionId);
if (uwbSession != null) {
// TODO: b/268065070 Include UWB logs for both filtered and unfiltered data.
mSessionNotificationManager.onRangingResult(uwbSession, rangingData);
processRangeData(rangingData, uwbSession);
if (mUwbInjector.getDeviceConfigFacade().isRangingErrorStreakTimerEnabled()
&& uwbSession.mRangingErrorStreakTimeoutMs
!= UwbSession.RANGING_RESULT_ERROR_NO_TIMEOUT) {
if (hasAllRangingResultError(rangingData)) {
uwbSession.startRangingResultErrorStreakTimerIfNotSet();
} else {
uwbSession.stopRangingResultErrorStreakTimerIfSet();
}
}
} else {
Log.i(TAG, "Session is not initialized or Ranging Data is Null");
}
Trace.endSection();
}
/* Notification of received data over UWB to Application*/
@Override
public void onDataReceived(
long sessionId, int status, long sequenceNum,
byte[] address, int sourceEndPoint, int destEndPoint, byte[] data) {
Log.d(TAG, "onDataReceived(): Received data packet - "
+ "Address: " + UwbUtil.toHexString(address)
+ ", Data: " + UwbUtil.toHexString(data)
+ ", sessionId: " + sessionId
+ ", status: " + status
+ ", sequenceNum: " + sequenceNum);
UwbSession uwbSession = getUwbSession((int) sessionId);
if (uwbSession == null) {
Log.e(TAG, "onDataReceived(): Received data for unknown sessionId = " + sessionId);
return;
}
// Size of address in the UCI Packet for DATA_MESSAGE_RCV is always expected to be 8
// (EXTENDED_ADDRESS_BYTE_LENGTH). It can contain the MacAddress in short format however
// (2 LSB with MacAddress, 6 MSB zeroed out).
if (address.length != UWB_DEVICE_EXT_MAC_ADDRESS_LEN) {
Log.e(TAG, "onDataReceived(): Received data for sessionId = " + sessionId
+ ", with unexpected MacAddress length = " + address.length);
return;
}
Long longAddress = macAddressByteArrayToLong(address);
ReceivedDataInfo info = new ReceivedDataInfo();
info.sessionId = sessionId;
info.status = status;
info.sequenceNum = sequenceNum;
info.address = longAddress;
info.sourceEndPoint = sourceEndPoint;
info.destEndPoint = destEndPoint;
info.payload = data;
uwbSession.addReceivedDataInfo(info);
mUwbMetrics.logDataRx(uwbSession, status);
}
/* Notification of data send status */
@Override
public void onDataSendStatus(
long sessionId, int dataTransferStatus, long sequenceNum) {
Log.d(TAG, "onDataSendStatus(): Received data send status - "
+ ", sessionId: " + sessionId
+ ", status: " + dataTransferStatus
+ ", sequenceNum: " + sequenceNum);
UwbSession uwbSession = getUwbSession((int) sessionId);
if (uwbSession == null) {
Log.e(TAG, "onDataSendStatus(): Received data send status for unknown sessionId = "
+ sessionId);
return;
}
SendDataInfo sendDataInfo = uwbSession.getSendDataInfo(sequenceNum);
if (sendDataInfo == null) {
Log.e(TAG, "onDataSendStatus(): No SendDataInfo found for data packet (sessionId = "
+ sessionId + ", sequenceNum = " + sequenceNum + ")");
return;
}
// A note on status - earlier spec versions had the same status value (0x1) as an error,
// the code is written as per recent spec versions (v2.0.0_0.0.9r0).
if (dataTransferStatus == UwbUciConstants.STATUS_CODE_DATA_TRANSFER_REPETITION_OK
|| dataTransferStatus == UwbUciConstants.STATUS_CODE_DATA_TRANSFER_OK) {
mSessionNotificationManager.onDataSent(
uwbSession, sendDataInfo.remoteDeviceAddress, sendDataInfo.params);
} else {
mSessionNotificationManager.onDataSendFailed(
uwbSession, sendDataInfo.remoteDeviceAddress, dataTransferStatus,
sendDataInfo.params);
}
// TODO(b/274711916): When Data Repetition during Data Packet Tx flow is implemented
// change here to remove the sendDataInfo only after all the copies of the packet have
// been sent.
uwbSession.removeSendDataInfo(sequenceNum);
}
/** Updates pose information if the session is using an ApplicationPoseSource */
public void updatePose(SessionHandle sessionHandle, PersistableBundle params) {
int sessionId = getSessionId(sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession == null) {
// Session doesn't exist yet/anymore.
return;
}
uwbSession.updatePose(FiraPoseUpdateParams.fromBundle(params));
}
@VisibleForTesting
static final class ReceivedDataInfo {
public long sessionId;
public int status;
public long sequenceNum;
public long address;
public int sourceEndPoint;
public int destEndPoint;
public byte[] payload;
}
@Override
public void onMulticastListUpdateNotificationReceived(
UwbMulticastListUpdateStatus multicastListUpdateStatus) {
Log.d(TAG, "onMulticastListUpdateNotificationReceived");
UwbSession uwbSession = getUwbSession((int) multicastListUpdateStatus.getSessionId());
if (uwbSession == null) {
Log.d(TAG, "onMulticastListUpdateNotificationReceived - invalid session");
return;
}
uwbSession.setMulticastListUpdateStatus(multicastListUpdateStatus);
synchronized (uwbSession.getWaitObj()) {
uwbSession.getWaitObj().blockingNotify();
}
}
@Override
public void onSessionStatusNotificationReceived(long sessionId, int state, int reasonCode) {
Log.i(TAG, "onSessionStatusNotificationReceived - Session ID : " + sessionId + ", state : "
+ UwbSessionNotificationHelper.getSessionStateString(state)
+ ", reasonCode:" + reasonCode);
UwbSession uwbSession = getUwbSession((int) sessionId);
if (uwbSession == null) {
Log.d(TAG, "onSessionStatusNotificationReceived - invalid session");
return;
}
if (mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) {
PersistableBundle sessionStatusBundle = new SessionStatus.Builder()
.setSessionId(sessionId)
.setState(state)
.setReasonCode(reasonCode)
.setAppPackageName(uwbSession.getAttributionSource().getPackageName())
.setSessiontoken(mSessionTokenMap.getOrDefault(uwbSession.getSessionId(), 0))
.build()
.toBundle();
try {
mUwbInjector.getUwbServiceCore().getOemExtensionCallback()
.onSessionStatusNotificationReceived(sessionStatusBundle);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send vendor notification", e);
}
}
int prevState = uwbSession.getSessionState();
setCurrentSessionState((int) sessionId, state);
if ((uwbSession.getOperationType() == SESSION_ON_DEINIT
&& state == UwbUciConstants.UWB_SESSION_STATE_IDLE)
|| (uwbSession.getOperationType() == SESSION_STOP_RANGING
&& state == UwbUciConstants.UWB_SESSION_STATE_IDLE
&& reasonCode != REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS)) {
Log.d(TAG, "Session status NTF is received due to in-band session state change");
} else {
synchronized (uwbSession.getWaitObj()) {
uwbSession.getWaitObj().blockingNotify();
}
}
//TODO : process only error handling in this switch function, b/218921154
switch (state) {
case UwbUciConstants.UWB_SESSION_STATE_IDLE:
if (prevState == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) {
// If session was stopped explicitly, then the onStopped() is sent from
// stopRanging method.
if (reasonCode != REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS) {
mSessionNotificationManager.onRangingStoppedWithUciReasonCode(
uwbSession, reasonCode);
mUwbMetrics.longRangingStopEvent(uwbSession);
}
} else if (prevState == UwbUciConstants.UWB_SESSION_STATE_IDLE) {
//mSessionNotificationManager.onRangingReconfigureFailed(
// uwbSession, reasonCode);
}
break;
case UwbUciConstants.UWB_SESSION_STATE_DEINIT:
mEventTask.execute(SESSION_ON_DEINIT, uwbSession);
break;
default:
break;
}
}
private int setAppConfigurations(UwbSession uwbSession) {
int status = mConfigurationManager.setAppConfigurations(uwbSession.getSessionId(),
uwbSession.getParams(), uwbSession.getChipId());
if (status == UwbUciConstants.STATUS_CODE_OK
&& mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) {
try {
status = mUwbInjector.getUwbServiceCore().getOemExtensionCallback()
.onSessionConfigurationReceived(uwbSession.getParams().toBundle());
} catch (RemoteException e) {
Log.e(TAG, "Failed to send vendor notification", e);
}
}
return status;
}
public synchronized void initSession(AttributionSource attributionSource,
SessionHandle sessionHandle, int sessionId, byte sessionType, String protocolName,
Params params, IUwbRangingCallbacks rangingCallbacks, String chipId)
throws RemoteException {
Log.i(TAG, "initSession() - sessionId: " + sessionId + ", sessionHandle: " + sessionHandle
+ ", sessionType: " + sessionType);
UwbSession uwbSession = createUwbSession(attributionSource, sessionHandle, sessionId,
sessionType, protocolName, params, rangingCallbacks, chipId);
// Check the attribution source chain to ensure that there are no 3p apps which are not in
// fg which can receive the ranging results.
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
Log.d(TAG, "Found a 3p app/service in the attribution source of request: "
+ nonPrivilegedAppAttrSource);
// TODO(b/211445008): Move this operation to uwb thread.
long identity = Binder.clearCallingIdentity();
boolean hasNonPrivilegedFgApp = mUwbInjector.isForegroundAppOrService(
nonPrivilegedAppAttrSource.getUid(),
nonPrivilegedAppAttrSource.getPackageName());
Binder.restoreCallingIdentity(identity);
uwbSession.setHasNonPrivilegedFgApp(hasNonPrivilegedFgApp);
if (!hasNonPrivilegedFgApp) {
if (!mUwbInjector.getDeviceConfigFacade().isBackgroundRangingEnabled()) {
Log.e(TAG, "openRanging - System policy disallows for non fg 3p apps");
rangingCallbacks.onRangingOpenFailed(sessionHandle,
RangingChangeReason.SYSTEM_POLICY, new PersistableBundle());
return;
} else {
Log.d(TAG, "openRanging - System policy allows for non fg 3p apps");
}
}
}
if (isExistedSession(sessionHandle) || isExistedSession(sessionId)) {
Log.i(TAG, "Duplicated session. SessionHandle: " + sessionHandle + ", SessionId: "
+ sessionId);
rangingCallbacks.onRangingOpenFailed(sessionHandle, RangingChangeReason.BAD_PARAMETERS,
UwbSessionNotificationHelper.convertUciStatusToParam(protocolName,
UwbUciConstants.STATUS_CODE_ERROR_SESSION_DUPLICATE));
mUwbMetrics.logRangingInitEvent(uwbSession,
UwbUciConstants.STATUS_CODE_ERROR_SESSION_DUPLICATE);
return;
}
boolean maxSessionsExceeded = false;
// TODO: getCccSessionCount and getFiraSessionCount should be chip specific
if (protocolName.equals(CccParams.PROTOCOL_NAME)
&& getCccSessionCount() >= getMaxCccSessionsNumber(chipId)) {
Log.i(TAG, "Max CCC Sessions Exceeded");
// All CCC sessions have the same priority so there's no point in trying to make space
// if max sessions are already reached.
maxSessionsExceeded = true;
} else if (protocolName.equals(FiraParams.PROTOCOL_NAME)
&& getFiraSessionCount() >= getMaxFiraSessionsNumber(chipId)) {
Log.i(TAG, "Max Fira Sessions Exceeded");
maxSessionsExceeded = !tryMakeSpaceForFiraSession(
uwbSession.getStackSessionPriority());
}
if (maxSessionsExceeded) {
rangingCallbacks.onRangingOpenFailed(sessionHandle,
RangingChangeReason.MAX_SESSIONS_REACHED,
UwbSessionNotificationHelper.convertUciStatusToParam(protocolName,
UwbUciConstants.STATUS_CODE_ERROR_MAX_SESSIONS_EXCEEDED));
mUwbMetrics.logRangingInitEvent(uwbSession,
UwbUciConstants.STATUS_CODE_ERROR_MAX_SESSIONS_EXCEEDED);
return;
}
try {
uwbSession.getBinder().linkToDeath(uwbSession, 0);
} catch (RemoteException e) {
uwbSession.binderDied();
Log.e(TAG, "linkToDeath fail - sessionID : " + uwbSession.getSessionId());
rangingCallbacks.onRangingOpenFailed(sessionHandle, RangingChangeReason.UNKNOWN,
UwbSessionNotificationHelper.convertUciStatusToParam(protocolName,
UwbUciConstants.STATUS_CODE_FAILED));
mUwbMetrics.logRangingInitEvent(uwbSession,
UwbUciConstants.STATUS_CODE_FAILED);
removeSession(uwbSession);
return;
}
mSessionTable.put(sessionHandle, uwbSession);
addToNonPrivilegedUidToFiraSessionTableIfNecessary(uwbSession);
mEventTask.execute(SESSION_OPEN_RANGING, uwbSession);
return;
}
private boolean tryMakeSpaceForFiraSession(int priorityThreshold) {
Optional<UwbSession> lowestPrioritySession = getSessionWithLowestPriorityByProtocol(
FiraParams.PROTOCOL_NAME);
if (!lowestPrioritySession.isPresent()) {
Log.w(TAG,
"New session blocked by max sessions exceeded, but list of sessions is "
+ "empty");
return false;
}
if (lowestPrioritySession.get().getStackSessionPriority() < priorityThreshold) {
return deInitDueToLowPriority(lowestPrioritySession.get().getSessionHandle());
}
return false;
}
// TODO: use UwbInjector.
@VisibleForTesting
UwbSession createUwbSession(AttributionSource attributionSource, SessionHandle sessionHandle,
int sessionId, byte sessionType, String protocolName, Params params,
IUwbRangingCallbacks iUwbRangingCallbacks, String chipId) {
return new UwbSession(attributionSource, sessionHandle, sessionId, sessionType,
protocolName, params, iUwbRangingCallbacks, chipId);
}
public synchronized void deInitSession(SessionHandle sessionHandle) {
if (!isExistedSession(sessionHandle)) {
Log.i(TAG, "Not initialized session ID");
return;
}
int sessionId = getSessionId(sessionHandle);
Log.i(TAG, "deinitSession() - sessionId: " + sessionId
+ ", sessionHandle: " + sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
mEventTask.execute(SESSION_DEINIT, uwbSession, STATUS_CODE_OK);
return;
}
/**
* Logs and executes session de-init task with low priority being sent as the reason in
* ranging closed callback.
*/
private synchronized boolean deInitDueToLowPriority(SessionHandle sessionHandle) {
int sessionId = getSessionId(sessionHandle);
if (!isExistedSession(sessionHandle)) {
Log.w(TAG, "Session " + sessionId + " expected to exist but not found. "
+ "Failed to de-initialize low priority session.");
return false;
}
Log.i(TAG, "deInitDueToLowPriority() - sessionId: " + sessionId
+ ", sessionHandle: " + sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
mEventTask.execute(SESSION_DEINIT, uwbSession,
UwbUciConstants.STATUS_CODE_ERROR_MAX_SESSIONS_EXCEEDED);
return true;
}
public synchronized void startRanging(SessionHandle sessionHandle, @Nullable Params params) {
if (!isExistedSession(sessionHandle)) {
Log.i(TAG, "Not initialized session ID");
return;
}
int sessionId = getSessionId(sessionHandle);
Log.i(TAG, "startRanging() - sessionId: " + sessionId
+ ", sessionHandle: " + sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
int currentSessionState = getCurrentSessionState(sessionId);
if (currentSessionState == UwbUciConstants.UWB_SESSION_STATE_IDLE) {
if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME)
&& params instanceof CccStartRangingParams) {
CccStartRangingParams rangingStartParams = (CccStartRangingParams) params;
Log.i(TAG, "startRanging() - update RAN multiplier: "
+ rangingStartParams.getRanMultiplier());
// Need to update the RAN multiplier from the CccStartRangingParams for CCC session.
uwbSession.updateCccParamsOnStart(rangingStartParams);
}
if (uwbSession.getProtocolName().equals(FiraParams.PROTOCOL_NAME)) {
// Need to update session priority if it changed.
uwbSession.updateFiraParamsOnStartIfChanged();
}
mEventTask.execute(SESSION_START_RANGING, uwbSession);
} else if (currentSessionState == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) {
Log.i(TAG, "session is already ranging");
mSessionNotificationManager.onRangingStartFailed(
uwbSession, UwbUciConstants.STATUS_CODE_REJECTED);
} else {
Log.i(TAG, "session can't start ranging");
mSessionNotificationManager.onRangingStartFailed(
uwbSession, UwbUciConstants.STATUS_CODE_FAILED);
mUwbMetrics.longRangingStartEvent(uwbSession, UwbUciConstants.STATUS_CODE_FAILED);
}
}
private synchronized void stopRangingInternal(SessionHandle sessionHandle,
boolean triggeredBySystemPolicy) {
if (!isExistedSession(sessionHandle)) {
Log.i(TAG, "Not initialized session ID");
return;
}
int sessionId = getSessionId(sessionHandle);
Log.i(TAG, "stopRanging() - sessionId: " + sessionId
+ ", sessionHandle: " + sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
int currentSessionState = getCurrentSessionState(sessionId);
if (currentSessionState == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) {
mEventTask.execute(SESSION_STOP_RANGING, uwbSession, triggeredBySystemPolicy ? 1 : 0);
} else if (currentSessionState == UwbUciConstants.UWB_SESSION_STATE_IDLE) {
Log.i(TAG, "session is already idle state");
mSessionNotificationManager.onRangingStopped(uwbSession,
UwbUciConstants.STATUS_CODE_OK);
mUwbMetrics.longRangingStopEvent(uwbSession);
} else {
mSessionNotificationManager.onRangingStopFailed(uwbSession,
UwbUciConstants.STATUS_CODE_REJECTED);
Log.i(TAG, "Not active session ID");
}
}
public synchronized void stopRanging(SessionHandle sessionHandle) {
stopRangingInternal(sessionHandle, false /* triggeredBySystemPolicy */);
}
/**
* Get the UwbSession corresponding to the given UWB Session ID. This API returns {@code null}
* when the UWB session is not found.
*/
@Nullable
public UwbSession getUwbSession(int sessionId) {
return mSessionTable.values()
.stream()
.filter(v -> v.getSessionId() == sessionId)
.findAny()
.orElse(null);
}
/**
* Get the Uwb Session ID corresponding to the given UWB Session Handle. This API returns
* {@code null} when the UWB session ID is not found.
*/
@Nullable
public Integer getSessionId(SessionHandle sessionHandle) {
UwbSession session = mSessionTable.get(sessionHandle);
if (session == null) return null;
return session.getSessionId();
}
private int getActiveSessionCount() {
return Math.toIntExact(
mSessionTable.values()
.stream()
.filter(v -> v.getSessionState() == UwbUciConstants.DEVICE_STATE_ACTIVE)
.count()
);
}
private void processRangeData(UwbRangingData rangingData, UwbSession uwbSession) {
if (rangingData.getRangingMeasuresType()
!= UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA) {
return;
}
if (!isValidUwbSessionForOwrAoaRanging(uwbSession)) {
return;
}
// Record the OWR Aoa Measurement from the RANGE_DATA_NTF.
UwbOwrAoaMeasurement uwbOwrAoaMeasurement = rangingData.getRangingOwrAoaMeasure();
mAdvertiseManager.updateAdvertiseTarget(uwbOwrAoaMeasurement);
byte[] macAddressBytes = getValidMacAddressFromOwrAoaMeasurement(
rangingData, uwbOwrAoaMeasurement);
if (macAddressBytes == null) {
Log.i(TAG, "OwR Aoa UwbSession: Invalid MacAddress for remote device");
return;
}
boolean advertisePointingResult = mAdvertiseManager.isPointedTarget(macAddressBytes);
if (mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) {
try {
PersistableBundle pointedTargetBundle = new AdvertisePointedTarget.Builder()
.setMacAddress(macAddressBytes)
.setAdvertisePointingResult(advertisePointingResult)
.build()
.toBundle();
advertisePointingResult = mUwbInjector
.getUwbServiceCore()
.getOemExtensionCallback()
.onCheckPointedTarget(pointedTargetBundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}
if (advertisePointingResult) {
// Use a loop to notify all the received application data payload(s) (in sequence number
// order) for this OWR AOA ranging session.
long macAddress = macAddressByteArrayToLong(macAddressBytes);
UwbAddress uwbAddress = UwbAddress.fromBytes(macAddressBytes);
List<ReceivedDataInfo> receivedDataInfoList = uwbSession.getAllReceivedDataInfo(
macAddress);
if (receivedDataInfoList.isEmpty()) {
Log.i(TAG, "OwR Aoa UwbSession: Application Payload data not found for"
+ " MacAddress = " + UwbUtil.toHexString(macAddress));
return;
}
receivedDataInfoList.stream().forEach(r ->
mSessionNotificationManager.onDataReceived(
uwbSession, uwbAddress, new PersistableBundle(), r.payload));
mUwbMetrics.logDataToUpperLayer(uwbSession, receivedDataInfoList.size());
mAdvertiseManager.removeAdvertiseTarget(macAddress);
}
}
@Nullable
private byte[] getValidMacAddressFromOwrAoaMeasurement(UwbRangingData rangingData,
UwbOwrAoaMeasurement uwbOwrAoaMeasurement) {
byte[] macAddress = uwbOwrAoaMeasurement.getMacAddress();
if (rangingData.getMacAddressMode() == MAC_ADDRESSING_MODE_SHORT) {
return (macAddress.length == UWB_DEVICE_SHORT_MAC_ADDRESS_LEN) ? macAddress : null;
} else if (rangingData.getMacAddressMode() == MAC_ADDRESSING_MODE_EXTENDED) {
return (macAddress.length == UWB_DEVICE_EXT_MAC_ADDRESS_LEN) ? macAddress : null;
}
return null;
}
public boolean isExistedSession(SessionHandle sessionHandle) {
return (getSessionId(sessionHandle) != null);
}
public boolean isExistedSession(int sessionId) {
return getUwbSession(sessionId) != null;
}
public void stopAllRanging() {
Log.d(TAG, "stopAllRanging()");
for (UwbSession uwbSession : mSessionTable.values()) {
int status = mNativeUwbManager.stopRanging(uwbSession.getSessionId(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
Log.i(TAG, "stopAllRanging() - Session " + uwbSession.getSessionId()
+ " is failed to stop ranging");
} else {
mUwbMetrics.longRangingStopEvent(uwbSession);
uwbSession.setSessionState(UwbUciConstants.UWB_SESSION_STATE_IDLE);
}
}
}
public synchronized void deinitAllSession() {
Log.d(TAG, "deinitAllSession()");
for (UwbSession uwbSession : mSessionTable.values()) {
handleOnDeInit(uwbSession);
}
// Not resetting chip on UWB toggle off.
// mNativeUwbManager.deviceReset(UwbUciConstants.UWBS_RESET);
}
public synchronized void handleOnDeInit(UwbSession uwbSession) {
if (!isExistedSession(uwbSession.getSessionHandle())) {
Log.i(TAG, "onDeinit - Ignoring already deleted session "
+ uwbSession.getSessionId());
return;
}
Log.d(TAG, "onDeinit: " + uwbSession.getSessionId());
mSessionNotificationManager.onRangingClosedWithApiReasonCode(uwbSession,
RangingChangeReason.SYSTEM_POLICY);
mUwbMetrics.logRangingCloseEvent(uwbSession, UwbUciConstants.STATUS_CODE_OK);
// Reset all UWB session timers when the session is de-init.
uwbSession.stopTimers();
removeSession(uwbSession);
}
public void setCurrentSessionState(int sessionId, int state) {
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession != null) {
uwbSession.setSessionState(state);
}
}
public int getCurrentSessionState(int sessionId) {
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession != null) {
return uwbSession.getSessionState();
}
return UwbUciConstants.UWB_SESSION_STATE_ERROR;
}
public int getSessionCount() {
return mSessionTable.size();
}
public long getCccSessionCount() {
return mSessionTable.values().stream().filter(
s -> s.mProtocolName.equals(CccParams.PROTOCOL_NAME)).count();
}
public long getFiraSessionCount() {
return mSessionTable.values().stream().filter(
s -> s.mProtocolName.equals(FiraParams.PROTOCOL_NAME)).count();
}
/** Returns max number of CCC sessions possible on given chip */
public long getMaxCccSessionsNumber(String chipId) {
GenericSpecificationParams params =
mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(chipId);
if (params != null && params.getCccSpecificationParams() != null) {
return params.getCccSpecificationParams().getMaxRangingSessionNumber();
} else {
// specification params are empty, return the default CCC max sessions value
return CccSpecificationParams.DEFAULT_MAX_RANGING_SESSIONS_NUMBER;
}
}
/** Returns max number of Fira sessions possible on given chip */
public long getMaxFiraSessionsNumber(String chipId) {
GenericSpecificationParams params =
mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(chipId);
if (params != null && params.getFiraSpecificationParams() != null) {
return params.getFiraSpecificationParams().getMaxRangingSessionNumber();
} else {
// specification params are empty, return the default Fira max sessions value
return FiraSpecificationParams.DEFAULT_MAX_RANGING_SESSIONS_NUMBER;
}
}
/** Gets the session with the lowest session priority among all sessions with given protocol. */
public Optional<UwbSession> getSessionWithLowestPriorityByProtocol(String protocolName) {
return mSessionTable.values().stream().filter(
s -> s.mProtocolName.equals(protocolName)).min(
Comparator.comparingInt(UwbSession::getStackSessionPriority));
}
public Set<Integer> getSessionIdSet() {
return mSessionTable.values()
.stream()
.map(v -> v.getSessionId())
.collect(Collectors.toSet());
}
private synchronized int reconfigureInternal(SessionHandle sessionHandle,
@Nullable Params params, boolean triggeredByFgStateChange) {
int status = UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST;
if (!isExistedSession(sessionHandle)) {
Log.i(TAG, "Not initialized session ID");
return status;
}
int sessionId = getSessionId(sessionHandle);
Log.i(TAG, "reconfigure() - Session ID : " + sessionId);
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession.getProtocolName().equals(FiraParams.PROTOCOL_NAME)
&& params instanceof FiraRangingReconfigureParams) {
FiraRangingReconfigureParams rangingReconfigureParams =
(FiraRangingReconfigureParams) params;
Log.i(TAG, "reconfigure() - update reconfigure params: "
+ rangingReconfigureParams);
uwbSession.updateFiraParamsOnReconfigure(rangingReconfigureParams);
}
mEventTask.execute(SESSION_RECONFIG_RANGING,
new ReconfigureEventParams(uwbSession, params, triggeredByFgStateChange));
return 0;
}
public synchronized int reconfigure(SessionHandle sessionHandle, @Nullable Params params) {
return reconfigureInternal(sessionHandle, params, false /* triggeredByFgStateChange */);
}
/** Send the payload data to a remote device in the UWB session */
public synchronized void sendData(SessionHandle sessionHandle, UwbAddress remoteDeviceAddress,
PersistableBundle params, byte[] data) {
SendDataInfo info = new SendDataInfo();
info.sessionHandle = sessionHandle;
info.remoteDeviceAddress = remoteDeviceAddress;
info.params = params;
info.data = data;
mEventTask.execute(SESSION_SEND_DATA, info);
}
private static final class SendDataInfo {
public SessionHandle sessionHandle;
public UwbAddress remoteDeviceAddress;
public PersistableBundle params;
public byte[] data;
}
private static final class RangingRoundsUpdateDtTagInfo {
public SessionHandle sessionHandle;
public PersistableBundle params;
}
/** DT Tag ranging round update */
public void rangingRoundsUpdateDtTag(SessionHandle sessionHandle,
PersistableBundle bundle) {
RangingRoundsUpdateDtTagInfo info = new RangingRoundsUpdateDtTagInfo();
info.sessionHandle = sessionHandle;
info.params = bundle;
mEventTask.execute(SESSION_UPDATE_DT_TAG_RANGING_ROUNDS, info);
}
/** Query Max Application data size for the given UWB Session */
public synchronized int queryMaxDataSizeBytes(SessionHandle sessionHandle) {
if (!isExistedSession(sessionHandle)) {
throw new IllegalStateException("Not initialized session ID");
}
int sessionId = getSessionId(sessionHandle);
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession == null) {
throw new IllegalStateException("UwbSession not found");
}
synchronized (uwbSession.getWaitObj()) {
return mNativeUwbManager.queryMaxDataSizeBytes(uwbSession.getSessionId(),
uwbSession.getChipId());
}
}
/** Handle ranging rounds update for DT Tag */
public void handleRangingRoundsUpdateDtTag(RangingRoundsUpdateDtTagInfo info) {
SessionHandle sessionHandle = info.sessionHandle;
Integer sessionId = getSessionId(sessionHandle);
if (sessionId == null) {
Log.i(TAG, "UwbSessionId not found");
return;
}
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession == null) {
Log.i(TAG, "UwbSession not found");
return;
}
DlTDoARangingRoundsUpdate dlTDoARangingRoundsUpdate = DlTDoARangingRoundsUpdate
.fromBundle(info.params);
if (dlTDoARangingRoundsUpdate.getSessionId() != getSessionId(sessionHandle)) {
throw new IllegalArgumentException("Wrong session ID");
}
FutureTask<DtTagUpdateRangingRoundsStatus> rangingRoundsUpdateTask = new FutureTask<>(
() -> {
synchronized (uwbSession.getWaitObj()) {
return mNativeUwbManager.sessionUpdateDtTagRangingRounds(
(int) dlTDoARangingRoundsUpdate.getSessionId(),
dlTDoARangingRoundsUpdate.getNoOfRangingRounds(),
dlTDoARangingRoundsUpdate.getRangingRoundIndexes(),
uwbSession.getChipId());
}
}
);
DtTagUpdateRangingRoundsStatus status = null;
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(rangingRoundsUpdateTask);
try {
status = rangingRoundsUpdateTask.get(IUwbAdapter
.RANGING_ROUNDS_UPDATE_DT_TAG_THRESHOLD_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to update ranging rounds for Dt tag - status : TIMEOUT");
executor.shutdownNow();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// Native stack returns null if unsuccessful
if (status == null) {
status = new DtTagUpdateRangingRoundsStatus(
UwbUciConstants.STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED,
0,
new byte[]{});
}
PersistableBundle params = new DlTDoARangingRoundsUpdateStatus.Builder()
.setStatus(status.getStatus())
.setNoOfRangingRounds(status.getNoOfRangingRounds())
.setRangingRoundIndexes(status.getRangingRoundIndexes())
.build()
.toBundle();
mSessionNotificationManager.onRangingRoundsUpdateStatus(uwbSession, params);
}
void removeSession(UwbSession uwbSession) {
if (uwbSession != null) {
uwbSession.getBinder().unlinkToDeath(uwbSession, 0);
removeAdvertiserData(uwbSession);
uwbSession.close();
removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(uwbSession);
mSessionTokenMap.remove(uwbSession.getSessionId());
mSessionTable.remove(uwbSession.getSessionHandle());
mDbgRecentlyClosedSessions.add(uwbSession);
}
}
private void removeAdvertiserData(UwbSession uwbSession) {
for (long remoteMacAddress : uwbSession.getRemoteMacAddressList()) {
mAdvertiseManager.removeAdvertiseTarget(remoteMacAddress);
}
}
void addToNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) {
if (uwbSession.getSessionType() == UwbUciConstants.SESSION_TYPE_RANGING) {
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
Log.d(TAG, "Detected start of non privileged FIRA session from "
+ nonPrivilegedAppAttrSource);
List<UwbSession> sessions = mNonPrivilegedUidToFiraSessionsTable.computeIfAbsent(
nonPrivilegedAppAttrSource.getUid(), v -> new ArrayList<>());
sessions.add(uwbSession);
}
}
}
void removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) {
if (uwbSession.getSessionType() == UwbUciConstants.SESSION_TYPE_RANGING) {
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
Log.d(TAG, "Detected end of non privileged FIRA session from "
+ nonPrivilegedAppAttrSource);
List<UwbSession> sessions = mNonPrivilegedUidToFiraSessionsTable.get(
nonPrivilegedAppAttrSource.getUid());
if (sessions == null) {
Log.wtf(TAG, "No sessions found for uid: "
+ nonPrivilegedAppAttrSource.getUid());
return;
}
sessions.remove(uwbSession);
if (sessions.isEmpty()) {
mNonPrivilegedUidToFiraSessionsTable.remove(
nonPrivilegedAppAttrSource.getUid());
}
}
}
}
private static class ReconfigureEventParams {
public final UwbSession uwbSession;
public final Params params;
public final boolean triggeredByFgStateChange;
ReconfigureEventParams(UwbSession uwbSession, Params params,
boolean triggeredByFgStateChange) {
this.uwbSession = uwbSession;
this.params = params;
this.triggeredByFgStateChange = triggeredByFgStateChange;
}
}
private class EventTask extends Handler {
EventTask(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
int type = msg.what;
switch (type) {
case SESSION_OPEN_RANGING: {
UwbSession uwbSession = (UwbSession) msg.obj;
handleOpenRanging(uwbSession);
break;
}
case SESSION_START_RANGING: {
UwbSession uwbSession = (UwbSession) msg.obj;
handleStartRanging(uwbSession);
break;
}
case SESSION_STOP_RANGING: {
UwbSession uwbSession = (UwbSession) msg.obj;
boolean triggeredBySystemPolicy = msg.arg1 == 1;
handleStopRanging(uwbSession, triggeredBySystemPolicy);
break;
}
case SESSION_RECONFIG_RANGING: {
Log.d(TAG, "SESSION_RECONFIG_RANGING");
ReconfigureEventParams params = (ReconfigureEventParams) msg.obj;
handleReconfigure(
params.uwbSession, params.params, params.triggeredByFgStateChange);
break;
}
case SESSION_DEINIT: {
UwbSession uwbSession = (UwbSession) msg.obj;
int reason = msg.arg1;
handleDeInitWithReason(uwbSession, reason);
break;
}
case SESSION_ON_DEINIT: {
UwbSession uwbSession = (UwbSession) msg.obj;
handleOnDeInit(uwbSession);
break;
}
case SESSION_SEND_DATA: {
Log.d(TAG, "SESSION_SEND_DATA");
SendDataInfo info = (SendDataInfo) msg.obj;
handleSendData(info);
break;
}
case SESSION_UPDATE_DT_TAG_RANGING_ROUNDS: {
Log.d(TAG, "SESSION_UPDATE_DT_TAG_RANGING_ROUNDS");
RangingRoundsUpdateDtTagInfo info = (RangingRoundsUpdateDtTagInfo) msg.obj;
handleRangingRoundsUpdateDtTag(info);
break;
}
default: {
Log.d(TAG, "EventTask : Undefined Task");
break;
}
}
}
public void execute(int task, Object obj) {
Message msg = mEventTask.obtainMessage();
msg.what = task;
msg.obj = obj;
this.sendMessage(msg);
}
public void execute(int task, Object obj, int arg1) {
Message msg = mEventTask.obtainMessage();
msg.what = task;
msg.obj = obj;
msg.arg1 = arg1;
this.sendMessage(msg);
}
private void handleOpenRanging(UwbSession uwbSession) {
Trace.beginSection("UWB#handleOpenRanging");
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> initSessionTask = new FutureTask<>(
() -> {
int status = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
uwbSession.setOperationType(OPERATION_TYPE_INIT_SESSION);
status = mNativeUwbManager.initSession(
uwbSession.getSessionId(),
uwbSession.getSessionType(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
return status;
}
mSessionTokenMap.put(uwbSession.getSessionId(), mNativeUwbManager
.getSessionToken(uwbSession.getSessionId(),
uwbSession.getChipId()));
uwbSession.getWaitObj().blockingWait();
status = UwbUciConstants.STATUS_CODE_FAILED;
if (uwbSession.getSessionState()
== UwbUciConstants.UWB_SESSION_STATE_INIT) {
status = UwbSessionManager.this.setAppConfigurations(uwbSession);
if (status != UwbUciConstants.STATUS_CODE_OK) {
return status;
}
uwbSession.getWaitObj().blockingWait();
status = UwbUciConstants.STATUS_CODE_FAILED;
if (uwbSession.getSessionState()
== UwbUciConstants.UWB_SESSION_STATE_IDLE) {
mSessionNotificationManager.onRangingOpened(uwbSession);
status = UwbUciConstants.STATUS_CODE_OK;
} else {
status = UwbUciConstants.STATUS_CODE_FAILED;
}
return status;
}
return status;
}
});
int status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(initSessionTask,
IUwbAdapter.RANGING_SESSION_OPEN_THRESHOLD_MS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to initialize session - status : TIMEOUT");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
mUwbMetrics.logRangingInitEvent(uwbSession, status);
if (status != UwbUciConstants.STATUS_CODE_OK) {
Log.i(TAG, "Failed to initialize session - status : " + status);
mSessionNotificationManager.onRangingOpenFailed(uwbSession, status);
uwbSession.setOperationType(SESSION_ON_DEINIT);
mNativeUwbManager.deInitSession(uwbSession.getSessionId(), uwbSession.getChipId());
removeSession(uwbSession);
}
Log.i(TAG, "sessionInit() : finish - sessionId : " + uwbSession.getSessionId());
Trace.endSection();
}
private void handleStartRanging(UwbSession uwbSession) {
Trace.beginSection("UWB#handleStartRanging");
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> startRangingTask = new FutureTask<>(
() -> {
int status = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
if (uwbSession.getNeedsAppConfigUpdate()) {
uwbSession.resetNeedsAppConfigUpdate();
status = mConfigurationManager.setAppConfigurations(
uwbSession.getSessionId(),
uwbSession.getParams(), uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
mSessionNotificationManager.onRangingStartFailed(
uwbSession, status);
return status;
}
}
uwbSession.setOperationType(SESSION_START_RANGING);
status = mNativeUwbManager.startRanging(uwbSession.getSessionId(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
mSessionNotificationManager.onRangingStartFailed(
uwbSession, status);
return status;
}
uwbSession.getWaitObj().blockingWait();
if (uwbSession.getSessionState()
== UwbUciConstants.UWB_SESSION_STATE_ACTIVE) {
// TODO: Ensure |rangingStartedParams| is valid for FIRA sessions
// as well.
Params rangingStartedParams = uwbSession.getParams();
// For CCC sessions, retrieve the app configs
if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME)) {
Pair<Integer, CccRangingStartedParams> statusAndParams =
mConfigurationManager.getAppConfigurations(
uwbSession.getSessionId(),
CccParams.PROTOCOL_NAME,
new byte[0],
CccRangingStartedParams.class,
uwbSession.getChipId());
if (statusAndParams.first != UwbUciConstants.STATUS_CODE_OK) {
Log.e(TAG, "Failed to get CCC ranging started params");
}
rangingStartedParams = statusAndParams.second;
}
mSessionNotificationManager.onRangingStarted(
uwbSession, rangingStartedParams);
} else {
status = UwbUciConstants.STATUS_CODE_FAILED;
mSessionNotificationManager.onRangingStartFailed(uwbSession,
status);
}
}
return status;
});
int status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(startRangingTask,
IUwbAdapter.RANGING_SESSION_START_THRESHOLD_MS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to Start Ranging - status : TIMEOUT");
mSessionNotificationManager.onRangingStartFailed(
uwbSession, UwbUciConstants.STATUS_CODE_FAILED);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
mUwbMetrics.longRangingStartEvent(uwbSession, status);
Trace.endSection();
}
private void handleStopRanging(UwbSession uwbSession, boolean triggeredBySystemPolicy) {
Trace.beginSection("UWB#handleStopRanging");
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> stopRangingTask = new FutureTask<>(
() -> {
int status = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
uwbSession.setOperationType(SESSION_STOP_RANGING);
status = mNativeUwbManager.stopRanging(uwbSession.getSessionId(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
mSessionNotificationManager.onRangingStopFailed(uwbSession, status);
return status;
}
uwbSession.getWaitObj().blockingWait();
if (uwbSession.getSessionState()
== UwbUciConstants.UWB_SESSION_STATE_IDLE) {
int apiReasonCode = triggeredBySystemPolicy
? RangingChangeReason.SYSTEM_POLICY
: RangingChangeReason.LOCAL_API;
mSessionNotificationManager.onRangingStoppedWithApiReasonCode(
uwbSession, apiReasonCode);
} else {
status = UwbUciConstants.STATUS_CODE_FAILED;
mSessionNotificationManager.onRangingStopFailed(uwbSession,
status);
}
}
return status;
});
int status = UwbUciConstants.STATUS_CODE_FAILED;
int timeoutMs = IUwbAdapter.RANGING_SESSION_START_THRESHOLD_MS;
if (uwbSession.getProtocolName().equals(PROTOCOL_NAME)) {
int minTimeoutNecessary = uwbSession.getCurrentFiraRangingIntervalMs() * 4;
timeoutMs = timeoutMs > minTimeoutNecessary ? timeoutMs : minTimeoutNecessary;
}
Log.v(TAG, "Stop timeout: " + timeoutMs);
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(stopRangingTask, timeoutMs);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to Stop Ranging - status : TIMEOUT");
mSessionNotificationManager.onRangingStopFailed(
uwbSession, UwbUciConstants.STATUS_CODE_FAILED);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (status != UwbUciConstants.STATUS_CODE_FAILED) {
mUwbMetrics.longRangingStopEvent(uwbSession);
}
// Reset all UWB session timers when the session is stopped.
uwbSession.stopTimers();
removeAdvertiserData(uwbSession);
Trace.endSection();
}
private void handleReconfigure(UwbSession uwbSession, @Nullable Params param,
boolean triggeredByFgStateChange) {
if (!(param instanceof FiraRangingReconfigureParams)) {
Log.e(TAG, "Invalid reconfigure params: " + param);
mSessionNotificationManager.onRangingReconfigureFailed(
uwbSession, UwbUciConstants.STATUS_CODE_INVALID_PARAM);
return;
}
Trace.beginSection("UWB#handleReconfigure");
FiraRangingReconfigureParams rangingReconfigureParams =
(FiraRangingReconfigureParams) param;
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> cmdTask = new FutureTask<>(
() -> {
int status = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
// Handle SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_CMD
UwbAddress[] addrList = rangingReconfigureParams.getAddressList();
Integer action = rangingReconfigureParams.getAction();
// Action will indicate if this is a controlee add/remove.
// if null, it's a session configuration change.
if (action != null) {
if (addrList == null) {
Log.e(TAG,
"Multicast update missing the address list.");
return status;
}
int dstAddressListSize = addrList.length;
List<byte[]> dstAddressList = new ArrayList<>();
for (UwbAddress address : addrList) {
dstAddressList.add(address.toBytes());
}
int[] subSessionIdList;
if (!ArrayUtils.isEmpty(
rangingReconfigureParams.getSubSessionIdList())) {
subSessionIdList =
rangingReconfigureParams.getSubSessionIdList();
} else {
// Set to 0's for the UCI stack.
subSessionIdList = new int[dstAddressListSize];
}
boolean isV2 = action
== P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_16_BYTE
|| action
== P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_32_BYTE;
status = mNativeUwbManager.controllerMulticastListUpdate(
uwbSession.getSessionId(),
action,
subSessionIdList.length,
ArrayUtils.toPrimitive(dstAddressList),
subSessionIdList,
isV2 ? rangingReconfigureParams
.getSubSessionKeyList() : null,
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
Log.e(TAG, "Unable to update controller multicast list.");
if (isMulticastActionAdd(action)) {
mSessionNotificationManager.onControleeAddFailed(
uwbSession, status);
} else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) {
mSessionNotificationManager.onControleeRemoveFailed(
uwbSession, status);
}
return status;
}
uwbSession.getWaitObj().blockingWait();
UwbMulticastListUpdateStatus multicastList =
uwbSession.getMulticastListUpdateStatus();
if (multicastList == null) {
Log.e(TAG, "Confirmed controller multicast list is empty!");
return status;
}
for (int i = 0; i < multicastList.getNumOfControlee(); i++) {
int actionStatus = multicastList.getStatus()[i];
if (actionStatus == UwbUciConstants.STATUS_CODE_OK) {
if (isMulticastActionAdd(action)) {
uwbSession.addControlee(
multicastList.getControleeUwbAddresses()[i]);
mSessionNotificationManager.onControleeAdded(
uwbSession);
} else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) {
uwbSession.removeControlee(
multicastList.getControleeUwbAddresses()[i]);
mSessionNotificationManager.onControleeRemoved(
uwbSession);
}
}
else {
status = actionStatus;
if (isMulticastActionAdd(action)) {
mSessionNotificationManager.onControleeAddFailed(
uwbSession, actionStatus);
} else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) {
mSessionNotificationManager.onControleeRemoveFailed(
uwbSession, actionStatus);
}
}
}
} else {
// setAppConfigurations only applies to config changes,
// not controlee list changes
status = mConfigurationManager.setAppConfigurations(
uwbSession.getSessionId(), param, uwbSession.getChipId());
}
if (status == UwbUciConstants.STATUS_CODE_OK) {
// only call this if all controlees succeeded otherwise the
// fail status cause a onRangingReconfigureFailed later.
if (!triggeredByFgStateChange) {
mSessionNotificationManager.onRangingReconfigured(uwbSession);
}
}
Log.d(TAG, "Multicast update status: " + status);
return status;
}
});
int status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(cmdTask,
IUwbAdapter.RANGING_SESSION_OPEN_THRESHOLD_MS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to Reconfigure - status : TIMEOUT");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (status != UwbUciConstants.STATUS_CODE_OK) {
Log.i(TAG, "Failed to Reconfigure : " + status);
if (!triggeredByFgStateChange) {
mSessionNotificationManager.onRangingReconfigureFailed(uwbSession, status);
}
}
Trace.endSection();
}
private boolean isMulticastActionAdd(Integer action) {
return action == MULTICAST_LIST_UPDATE_ACTION_ADD
|| action == P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_16_BYTE
|| action == P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_32_BYTE;
}
private void handleDeInitWithReason(UwbSession uwbSession, int reason) {
Trace.beginSection("UWB#handleDeInitWithReason");
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> deInitTask = new FutureTask<>(
(Callable<Integer>) () -> {
int status = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
status = mNativeUwbManager.deInitSession(uwbSession.getSessionId(),
uwbSession.getChipId());
if (status != UwbUciConstants.STATUS_CODE_OK) {
mSessionNotificationManager.onRangingClosed(uwbSession, status);
return status;
}
uwbSession.getWaitObj().blockingWait();
Log.i(TAG, "onRangingClosed - status : " + status);
mSessionNotificationManager.onRangingClosed(uwbSession,
reason);
}
return status;
});
int status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(deInitTask,
IUwbAdapter.RANGING_SESSION_CLOSE_THRESHOLD_MS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to Stop Ranging - status : TIMEOUT");
mSessionNotificationManager.onRangingClosed(uwbSession, status);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
mUwbMetrics.logRangingCloseEvent(uwbSession, status);
// Reset all UWB session timers when the session is de-initialized (ie, closed).
uwbSession.stopTimers();
removeSession(uwbSession);
Log.i(TAG, "deinit finish : status :" + status);
Trace.endSection();
}
private void handleSendData(SendDataInfo sendDataInfo) {
int status = UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST;
SessionHandle sessionHandle = sendDataInfo.sessionHandle;
if (sessionHandle == null) {
Log.i(TAG, "Not present sessionHandle");
mSessionNotificationManager.onDataSendFailed(
null, sendDataInfo.remoteDeviceAddress, status, sendDataInfo.params);
return;
}
Integer sessionId = getSessionId(sessionHandle);
if (sessionId == null) {
Log.i(TAG, "UwbSessionId not found");
mSessionNotificationManager.onDataSendFailed(
null, sendDataInfo.remoteDeviceAddress, status, sendDataInfo.params);
return;
}
// TODO(b/256675656): Check if there is race condition between uwbSession being
// retrieved here and used below (and similar for uwbSession being stored in the
// mLooper message and being used during processing for all other message types).
UwbSession uwbSession = getUwbSession(sessionId);
if (uwbSession == null) {
Log.i(TAG, "UwbSession not found");
mSessionNotificationManager.onDataSendFailed(
null, sendDataInfo.remoteDeviceAddress, status, sendDataInfo.params);
return;
}
// TODO(b/211445008): Consolidate to a single uwb thread.
FutureTask<Integer> sendDataTask = new FutureTask<>((Callable<Integer>) () -> {
int sendDataStatus = UwbUciConstants.STATUS_CODE_FAILED;
synchronized (uwbSession.getWaitObj()) {
if (!isValidUwbSessionForApplicationDataTransfer(uwbSession)) {
sendDataStatus = UwbUciConstants.STATUS_CODE_FAILED;
Log.i(TAG, "UwbSession not in active state");
mSessionNotificationManager.onDataSendFailed(
uwbSession, sendDataInfo.remoteDeviceAddress, sendDataStatus,
sendDataInfo.params);
return sendDataStatus;
}
if (!isValidSendDataInfo(sendDataInfo)) {
sendDataStatus = UwbUciConstants.STATUS_CODE_INVALID_PARAM;
mSessionNotificationManager.onDataSendFailed(
uwbSession, sendDataInfo.remoteDeviceAddress, sendDataStatus,
sendDataInfo.params);
return sendDataStatus;
}
// Get the UCI sequence number for this data packet, and store it.
byte sequenceNum = uwbSession.getAndIncrementDataSndSequenceNumber();
uwbSession.addSendDataInfo(sequenceNum, sendDataInfo);
sendDataStatus = mNativeUwbManager.sendData(
uwbSession.getSessionId(),
DataTypeConversionUtil.convertShortMacAddressBytesToExtended(
sendDataInfo.remoteDeviceAddress.toBytes()),
UwbUciConstants.UWB_DESTINATION_END_POINT_HOST, sequenceNum,
sendDataInfo.data, uwbSession.getChipId());
mUwbMetrics.logDataTx(uwbSession, sendDataStatus);
if (sendDataStatus != STATUS_CODE_OK) {
Log.e(TAG, "MSG_SESSION_SEND_DATA error status: " + sendDataStatus
+ " for data packet sessionId: " + sessionId
+ ", sequence number: " + sequenceNum);
mSessionNotificationManager.onDataSendFailed(
uwbSession, sendDataInfo.remoteDeviceAddress, sendDataStatus,
sendDataInfo.params);
uwbSession.removeSendDataInfo(sequenceNum);
}
return sendDataStatus;
}
});
status = UwbUciConstants.STATUS_CODE_FAILED;
try {
status = mUwbInjector.runTaskOnSingleThreadExecutor(sendDataTask,
IUwbAdapter.RANGING_SESSION_OPEN_THRESHOLD_MS);
} catch (TimeoutException e) {
Log.i(TAG, "Failed to Send data - status : TIMEOUT");
mSessionNotificationManager.onDataSendFailed(uwbSession,
sendDataInfo.remoteDeviceAddress, status, sendDataInfo.params);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
private boolean isValidUwbSessionForOwrAoaRanging(UwbSession uwbSession) {
Params params = uwbSession.getParams();
if (params instanceof FiraOpenSessionParams) {
FiraOpenSessionParams firaParams = (FiraOpenSessionParams) params;
if (firaParams.getRangingRoundUsage() != ROUND_USAGE_OWR_AOA_MEASUREMENT) {
Log.i(TAG, "OwR Aoa UwbSession: Invalid ranging round usage value = "
+ firaParams.getRangingRoundUsage());
return false;
}
if (firaParams.getDeviceRole() != RANGING_DEVICE_ROLE_OBSERVER) {
Log.i(TAG, "OwR Aoa UwbSession: Invalid device role value = "
+ firaParams.getDeviceRole());
return false;
}
return true;
}
return false;
}
private boolean isValidUwbSessionForApplicationDataTransfer(UwbSession uwbSession) {
// The session state must be SESSION_STATE_ACTIVE, as that's required to transmit or receive
// application data.
return uwbSession != null && uwbSession.getSessionState() == UWB_SESSION_STATE_ACTIVE;
}
private boolean isValidSendDataInfo(SendDataInfo sendDataInfo) {
if (sendDataInfo.data == null) {
return false;
}
if (sendDataInfo.remoteDeviceAddress == null) {
return false;
}
if (sendDataInfo.remoteDeviceAddress.size()
> UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN) {
return false;
}
return true;
}
/** Represents a UWB session */
public class UwbSession implements IBinder.DeathRecipient, Closeable {
@VisibleForTesting
public static final long RANGING_RESULT_ERROR_NO_TIMEOUT = 0;
private static final String RANGING_RESULT_ERROR_STREAK_TIMER_TAG =
"UwbSessionRangingResultError";
private static final long NON_PRIVILEGED_BG_APP_TIMEOUT_MS = 120_000;
@VisibleForTesting
public static final String NON_PRIVILEGED_BG_APP_TIMER_TAG =
"UwbSessionNonPrivilegedBgAppError";
@VisibleForTesting
static final int CCC_SESSION_PRIORITY = 80;
@VisibleForTesting
static final int SYSTEM_APP_SESSION_PRIORITY = 70;
@VisibleForTesting
static final int FG_SESSION_PRIORITY = 60;
// Default session priority value needs to be different from other session priority buckets,
// so we can detect overrides from the shell or System API.
@VisibleForTesting
static final int DEFAULT_SESSION_PRIORITY = 50;
@VisibleForTesting
static final int BG_SESSION_PRIORITY = 40;
private final AttributionSource mAttributionSource;
private final SessionHandle mSessionHandle;
private final int mSessionId;
private final byte mSessionType;
private final IUwbRangingCallbacks mIUwbRangingCallbacks;
private final String mProtocolName;
private final IBinder mIBinder;
private final WaitObj mWaitObj;
private boolean mAcquiredDefaultPose = false;
private Params mParams;
private int mSessionState;
// Session priority as tracked by the UWB stack that changes based on the requesting
// app/service bg/fg state changes. Note, it will differ from the Fira SESSION_PRIORITY
// param given to UWBS if the state changed after the session became active.
private int mStackSessionPriority;
private boolean mSessionPriorityOverride = false;
private boolean mNeedsAppConfigUpdate = false;
private UwbMulticastListUpdateStatus mMulticastListUpdateStatus;
private final int mProfileType;
private AlarmManager.OnAlarmListener mRangingResultErrorStreakTimerListener;
private AlarmManager.OnAlarmListener mNonPrivilegedBgAppTimerListener;
private int mOperationType = OPERATION_TYPE_INIT_SESSION;
private final String mChipId;
private boolean mHasNonPrivilegedFgApp = false;
private @FiraParams.RangeDataNtfConfig Integer mOrigRangeDataNtfConfig;
private long mRangingErrorStreakTimeoutMs = RANGING_RESULT_ERROR_NO_TIMEOUT;
// Use a Map<RemoteMacAddress, SortedMap<SequenceNumber, ReceivedDataInfo>> to store all
// the Application payload data packets received in this (active) UWB Session.
// - The outer key (RemoteMacAddress) is used to identify the Advertiser device that sends
// the data (there can be multiple advertisers in the same UWB session).
// - The inner key (SequenceNumber) is used to ensure we don't store duplicate packets,
// and notify them to the higher layers in-order.
// TODO(b/270068278): Change the type of SequenceNumber from Long to Integer everywhere.
private final ConcurrentHashMap<Long, SortedMap<Long, ReceivedDataInfo>>
mReceivedDataInfoMap;
private IPoseSource mPoseSource;
// Store the UCI sequence number for the next Data packet (to be sent to UWBS).
private byte mDataSndSequenceNumber;
// Store a Map<SequenceNumber, SendDataInfo>, for every Data packet (sent to UWBS). It's
// used when the corresponding DataTransferStatusNtf is received (from UWBS).
private final ConcurrentHashMap<Long, SendDataInfo> mSendDataInfoMap;
@VisibleForTesting
public List<UwbControlee> mControleeList;
UwbSession(AttributionSource attributionSource, SessionHandle sessionHandle, int sessionId,
byte sessionType, String protocolName, Params params,
IUwbRangingCallbacks iUwbRangingCallbacks, String chipId) {
this.mAttributionSource = attributionSource;
this.mSessionHandle = sessionHandle;
this.mSessionId = sessionId;
this.mSessionType = sessionType;
this.mProtocolName = protocolName;
this.mIUwbRangingCallbacks = iUwbRangingCallbacks;
this.mIBinder = iUwbRangingCallbacks.asBinder();
this.mSessionState = UwbUciConstants.UWB_SESSION_STATE_DEINIT;
this.mStackSessionPriority = calculateSessionPriority();
this.mParams = params;
this.mWaitObj = new WaitObj();
this.mProfileType = convertProtolNameToProfileType(protocolName);
this.mChipId = chipId;
if (params instanceof FiraOpenSessionParams) {
FiraOpenSessionParams firaParams = (FiraOpenSessionParams) params;
// Set up pose sources before we start creating UwbControlees.
switch (firaParams.getFilterType()) {
case FILTER_TYPE_DEFAULT:
this.mPoseSource = mUwbInjector.acquirePoseSource();
this.mAcquiredDefaultPose = true;
break;
case FILTER_TYPE_APPLICATION:
this.mPoseSource = new ApplicationPoseSource();
break;
}
if (firaParams.getDestAddressList() != null) {
// Set up list of all controlees involved.
mControleeList = firaParams.getDestAddressList().stream()
.map(addr -> new UwbControlee(addr, createFilterEngine(), mUwbInjector))
.collect(Collectors.toList());
}
mRangingErrorStreakTimeoutMs = firaParams
.getRangingErrorStreakTimeoutMs();
// Add stack calculated session priority to Fira open session params. The stack
// session priority might change later based on fg/bg state changes, but the
// SESSION_PRIORITY given to the UWBS on open session will stay the same since
// UWBS doesn't support reconfiguring session priority while the session is active.
// In case the session stops being active, session priority will update on next
// start ranging call.
if (firaParams.getSessionPriority() != DEFAULT_SESSION_PRIORITY) {
mSessionPriorityOverride = true;
mStackSessionPriority = firaParams.getSessionPriority();
} else {
mParams = firaParams.toBuilder().setSessionPriority(
mStackSessionPriority).build();
}
}
this.mReceivedDataInfoMap = new ConcurrentHashMap<>();
this.mDataSndSequenceNumber = 0;
this.mSendDataInfoMap = new ConcurrentHashMap<>();
}
/**
* Calculates the priority of the session based on the protocol type and the originating
* app/service requesting the session.
*
* Session priority ranking order (from highest to lowest priority):
* 1. Any CCC session
* 2. Any System app/service
* 3. Other apps/services running in Foreground
* 4. Other apps/services running in Background
*/
public int calculateSessionPriority() {
if (mProtocolName.equals(CccParams.PROTOCOL_NAME)) {
return CCC_SESSION_PRIORITY;
}
AttributionSource nonPrivilegedAppAttrSource =
this.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource == null) {
return SYSTEM_APP_SESSION_PRIORITY;
}
long identity = Binder.clearCallingIdentity();
boolean isFgAppOrService = mUwbInjector.isForegroundAppOrService(
nonPrivilegedAppAttrSource.getUid(),
nonPrivilegedAppAttrSource.getPackageName());
Binder.restoreCallingIdentity(identity);
if (isFgAppOrService) {
return FG_SESSION_PRIORITY;
}
return BG_SESSION_PRIORITY;
}
private boolean isPrivilegedApp(int uid, String packageName) {
return mUwbInjector.isSystemApp(uid, packageName)
|| mUwbInjector.isAppSignedWithPlatformKey(uid);
}
/**
* Check the attribution source chain to check if there are any 3p apps.
* @return AttributionSource of first non-system app found in the chain, null otherwise.
*/
@Nullable
public AttributionSource getAnyNonPrivilegedAppInAttributionSource() {
// Iterate attribution source chain to ensure that there is no non-fg 3p app in the
// request.
AttributionSource attributionSource = mAttributionSource;
while (attributionSource != null) {
int uid = attributionSource.getUid();
String packageName = attributionSource.getPackageName();
if (!isPrivilegedApp(uid, packageName)) {
return attributionSource;
}
attributionSource = attributionSource.getNext();
}
return null;
}
/**
* Gets the list of controlees active under this session.
*/
public List<UwbControlee> getControleeList() {
return Collections.unmodifiableList(mControleeList);
}
/**
* Store a ReceivedDataInfo for the UwbSession. If we already have stored data from the
* same advertiser and with the same sequence number, this is a no-op.
*/
public void addReceivedDataInfo(ReceivedDataInfo receivedDataInfo) {
SortedMap<Long, ReceivedDataInfo> innerMap = mReceivedDataInfoMap.get(
receivedDataInfo.address);
if (innerMap == null) {
innerMap = new TreeMap<>();
mReceivedDataInfoMap.put(receivedDataInfo.address, innerMap);
}
// Check if the sorted InnerMap has reached the max number of Rx packets we want to
// store; if so we drop the smallest (sequence number) packet between the new received
// packet and the stored packets.
int maxRxPacketsToStore =
mUwbInjector.getDeviceConfigFacade().getRxDataMaxPacketsToStore();
if (innerMap.size() < maxRxPacketsToStore) {
innerMap.putIfAbsent(receivedDataInfo.sequenceNum, receivedDataInfo);
} else if (innerMap.size() == maxRxPacketsToStore) {
Long smallestStoredSequenceNumber = innerMap.firstKey();
if (smallestStoredSequenceNumber < receivedDataInfo.sequenceNum
&& !innerMap.containsKey(receivedDataInfo.sequenceNum)) {
innerMap.remove(smallestStoredSequenceNumber);
innerMap.putIfAbsent(receivedDataInfo.sequenceNum, receivedDataInfo);
}
}
}
/**
* Return all the ReceivedDataInfo from the given remote device, in sequence number order.
* This method also removes the returned packets from the Map, so the same packet will
* not be returned again (in a future call).
*/
public List<ReceivedDataInfo> getAllReceivedDataInfo(long macAddress) {
SortedMap<Long, ReceivedDataInfo> innerMap = mReceivedDataInfoMap.get(macAddress);
if (innerMap == null) {
// No stored ReceivedDataInfo(s) for the address.
return List.of();
}
List<ReceivedDataInfo> receivedDataInfoList = new ArrayList<>(innerMap.values());
innerMap.clear();
return receivedDataInfoList;
}
private void clearReceivedDataInfo() {
for (long macAddress : getRemoteMacAddressList()) {
SortedMap<Long, ReceivedDataInfo> innerMap = mReceivedDataInfoMap.get(macAddress);
innerMap.clear();
}
mReceivedDataInfoMap.clear();
}
/**
* Get (and increment) the UCI sequence number for the next Data packet to be sent to UWBS.
*/
public byte getAndIncrementDataSndSequenceNumber() {
return mDataSndSequenceNumber++;
}
/**
* Store a SendDataInfo for a UCI Data packet sent to UWBS.
*/
public void addSendDataInfo(long sequenceNumber, SendDataInfo sendDataInfo) {
mSendDataInfoMap.put(sequenceNumber, sendDataInfo);
}
/**
* Remove the SendDataInfo for a UCI packet from the current UWB Session.
*/
public void removeSendDataInfo(long sequenceNumber) {
mSendDataInfoMap.remove(sequenceNumber);
}
/**
* Get the SendDataInfo for a UCI packet from the current UWB Session.
*/
@Nullable
public SendDataInfo getSendDataInfo(long sequenceNumber) {
return mSendDataInfoMap.get(sequenceNumber);
}
/**
* Adds a Controlee to the session. This should only be called to reflect
* the state of the native UWB interface.
* @param address The UWB address of the Controlee to add.
*/
public void addControlee(UwbAddress address) {
if (mControleeList != null
&& mControleeList.stream().noneMatch(e -> e.getUwbAddress().equals(address))) {
mControleeList.add(new UwbControlee(address, createFilterEngine(), mUwbInjector));
}
}
/**
* Fetches a {@link UwbControlee} object by {@link UwbAddress}.
* @param address The UWB address of the Controlee to find.
* @return The matching {@link UwbControlee}, or null if not found.
*/
public UwbControlee getControlee(UwbAddress address) {
UwbControlee result = mControleeList
.stream()
.filter(e -> e.getUwbAddress().equals(address))
.findFirst()
.orElse(null);
if (result == null) {
Log.d(TAG, "Failure to find controlee " + address);
}
return result;
}
/**
* Removes a Controlee from the session. This should only be called to reflect
* the state of the native UWB interface.
* @param address The UWB address of the Controlee to remove.
*/
public void removeControlee(UwbAddress address) {
if (mControleeList != null) {
for (UwbControlee controlee : mControleeList) {
if (controlee.getUwbAddress().equals(address)) {
controlee.close();
mControleeList.remove(controlee);
break;
}
}
}
}
public AttributionSource getAttributionSource() {
return this.mAttributionSource;
}
public int getSessionId() {
return this.mSessionId;
}
public byte getSessionType() {
return this.mSessionType;
}
public String getChipId() {
return this.mChipId;
}
public SessionHandle getSessionHandle() {
return this.mSessionHandle;
}
public Params getParams() {
return this.mParams;
}
public void updateCccParamsOnStart(CccStartRangingParams rangingStartParams) {
// Need to update the RAN multiplier and initiation time
// from the CccStartRangingParams for CCC session.
CccOpenRangingParams newParams =
new CccOpenRangingParams.Builder((CccOpenRangingParams) mParams)
.setRanMultiplier(rangingStartParams.getRanMultiplier())
.setInitiationTimeMs(rangingStartParams.getInitiationTimeMs())
.build();
this.mParams = newParams;
this.mNeedsAppConfigUpdate = true;
}
/**
* Checks if session priority of the current session changed from the initial value, if so
* updates the session priority param and marks session for needed app config update.
*/
public void updateFiraParamsOnStartIfChanged() {
// Need to check if session priority changed and update if it did
FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams;
if (mStackSessionPriority != firaOpenSessionParams.getSessionPriority()) {
this.mParams = ((FiraOpenSessionParams) mParams).toBuilder().setSessionPriority(
mStackSessionPriority).build();
this.mNeedsAppConfigUpdate = true;
}
}
public void updateFiraParamsOnReconfigure(FiraRangingReconfigureParams reconfigureParams) {
// Need to update the reconfigure params from the FiraRangingReconfigureParams for
// FiRa session.
FiraOpenSessionParams.Builder newParamsBuilder =
new FiraOpenSessionParams.Builder((FiraOpenSessionParams) mParams);
if (reconfigureParams.getBlockStrideLength() != null) {
newParamsBuilder.setBlockStrideLength(reconfigureParams.getBlockStrideLength());
}
if (reconfigureParams.getRangeDataNtfConfig() != null) {
newParamsBuilder.setRangeDataNtfConfig(reconfigureParams.getRangeDataNtfConfig());
}
if (reconfigureParams.getRangeDataProximityNear() != null) {
newParamsBuilder.setRangeDataNtfProximityNear(
reconfigureParams.getRangeDataProximityNear());
}
if (reconfigureParams.getRangeDataProximityFar() != null) {
newParamsBuilder.setRangeDataNtfProximityFar(
reconfigureParams.getRangeDataProximityFar());
}
if (reconfigureParams.getRangeDataAoaAzimuthLower() != null) {
newParamsBuilder.setRangeDataNtfAoaAzimuthLower(
reconfigureParams.getRangeDataAoaAzimuthLower());
}
if (reconfigureParams.getRangeDataAoaAzimuthUpper() != null) {
newParamsBuilder.setRangeDataNtfAoaAzimuthUpper(
reconfigureParams.getRangeDataAoaAzimuthUpper());
}
if (reconfigureParams.getRangeDataAoaElevationLower() != null) {
newParamsBuilder.setRangeDataNtfAoaElevationLower(
reconfigureParams.getRangeDataAoaElevationLower());
}
if (reconfigureParams.getRangeDataAoaElevationUpper() != null) {
newParamsBuilder.setRangeDataNtfAoaElevationUpper(
reconfigureParams.getRangeDataAoaElevationUpper());
}
this.mParams = newParamsBuilder.build();
}
public int getCurrentFiraRangingIntervalMs() {
FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams;
return firaOpenSessionParams.getRangingIntervalMs()
* (firaOpenSessionParams.getBlockStrideLength() + 1);
}
public String getProtocolName() {
return this.mProtocolName;
}
public IUwbRangingCallbacks getIUwbRangingCallbacks() {
return this.mIUwbRangingCallbacks;
}
public int getSessionState() {
return this.mSessionState;
}
public void setSessionState(int state) {
this.mSessionState = state;
}
public int getStackSessionPriority() {
return this.mStackSessionPriority;
}
public void setStackSessionPriority(int priority) {
this.mStackSessionPriority = priority;
}
public boolean getNeedsAppConfigUpdate() {
return this.mNeedsAppConfigUpdate;
}
/** Reset the needsAppConfigUpdate flag to false. */
public void resetNeedsAppConfigUpdate() {
this.mNeedsAppConfigUpdate = false;
}
public Set<Long> getRemoteMacAddressList() {
return mReceivedDataInfoMap.keySet();
}
public void setMulticastListUpdateStatus(
UwbMulticastListUpdateStatus multicastListUpdateStatus) {
mMulticastListUpdateStatus = multicastListUpdateStatus;
}
public UwbMulticastListUpdateStatus getMulticastListUpdateStatus() {
return mMulticastListUpdateStatus;
}
private int convertProtolNameToProfileType(String protocolName) {
if (protocolName.equals(FiraParams.PROTOCOL_NAME)) {
return UwbStatsLog.UWB_SESSION_INITIATED__PROFILE__FIRA;
} else if (protocolName.equals(CccParams.PROTOCOL_NAME)) {
return UwbStatsLog.UWB_SESSION_INITIATED__PROFILE__CCC;
} else {
return UwbStatsLog.UWB_SESSION_INITIATED__PROFILE__CUSTOMIZED;
}
}
public int getProfileType() {
return mProfileType;
}
public int getParallelSessionCount() {
if (mSessionTable.containsKey(mSessionHandle)) {
return getSessionCount() - 1;
}
return getSessionCount();
}
public IBinder getBinder() {
return mIBinder;
}
public WaitObj getWaitObj() {
return mWaitObj;
}
public boolean hasNonPrivilegedFgApp() {
return mHasNonPrivilegedFgApp;
}
public void setHasNonPrivilegedFgApp(boolean hasNonPrivilegedFgApp) {
mHasNonPrivilegedFgApp = hasNonPrivilegedFgApp;
}
/**
* Starts a timer to detect if the error streak is longer than
* {@link UwbSession#mRangingErrorStreakTimeoutMs }.
*/
public void startRangingResultErrorStreakTimerIfNotSet() {
// Start a timer on first failure to detect continuous failures.
if (mRangingResultErrorStreakTimerListener == null) {
mRangingResultErrorStreakTimerListener = () -> {
Log.w(TAG, "Continuous errors or no ranging results detected for "
+ mRangingErrorStreakTimeoutMs + " ms."
+ " Stopping session");
stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */);
};
Log.v(TAG, "Starting error timer for "
+ mRangingErrorStreakTimeoutMs + " ms.");
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mUwbInjector.getElapsedSinceBootMillis()
+ mRangingErrorStreakTimeoutMs,
RANGING_RESULT_ERROR_STREAK_TIMER_TAG,
mRangingResultErrorStreakTimerListener, mEventTask);
}
}
public void stopRangingResultErrorStreakTimerIfSet() {
// Cancel error streak timer on any success.
if (mRangingResultErrorStreakTimerListener != null) {
mAlarmManager.cancel(mRangingResultErrorStreakTimerListener);
mRangingResultErrorStreakTimerListener = null;
}
}
/**
* Starts a timer to detect if the app that started the UWB session is in the background
* for longer than {@link UwbSession#NON_PRIVILEGED_BG_APP_TIMEOUT_MS}.
*/
private void startNonPrivilegedBgAppTimerIfNotSet() {
// Start a timer when the non-privileged app goes into the background.
if (mNonPrivilegedBgAppTimerListener == null) {
mNonPrivilegedBgAppTimerListener = () -> {
Log.w(TAG, "Non-privileged app in background for longer than timeout - "
+ " Stopping session");
stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */);
};
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mUwbInjector.getElapsedSinceBootMillis()
+ NON_PRIVILEGED_BG_APP_TIMEOUT_MS,
NON_PRIVILEGED_BG_APP_TIMER_TAG,
mNonPrivilegedBgAppTimerListener, mEventTask);
}
}
private void stopNonPrivilegedBgAppTimerIfSet() {
// Stop the timer when the non-privileged app goes into the foreground.
if (mNonPrivilegedBgAppTimerListener != null) {
mAlarmManager.cancel(mNonPrivilegedBgAppTimerListener);
mNonPrivilegedBgAppTimerListener = null;
}
}
private void stopTimers() {
// Reset any stored error streak or non-privileged background app timestamps.
stopRangingResultErrorStreakTimerIfSet();
stopNonPrivilegedBgAppTimerIfSet();
}
public void reconfigureFiraSessionOnFgStateChange() {
if (mOrigRangeDataNtfConfig == null) {
mOrigRangeDataNtfConfig = ((FiraOpenSessionParams) mParams).getRangeDataNtfConfig();
}
// Reconfigure the session to change notification control when the app transitions
// from fg to bg and vice versa.
FiraRangingReconfigureParams reconfigureParams =
new FiraRangingReconfigureParams.Builder()
// If app is in fg, use the configured ntf control, else disable.
.setRangeDataNtfConfig(mHasNonPrivilegedFgApp
// use to retrieve the latest configured ntf control.
? mOrigRangeDataNtfConfig : FiraParams.RANGE_DATA_NTF_CONFIG_DISABLE)
.build();
reconfigureInternal(
mSessionHandle, reconfigureParams, true /* triggeredByFgStateChange */);
if (!mUwbInjector.getDeviceConfigFacade().isBackgroundRangingEnabled()) {
Log.d(TAG, "reconfigureFiraSessionOnFgStateChange - System policy disallows for "
+ "non fg 3p apps");
// When a non-privileged app goes into the background, start a timer (that will stop
// the ranging session). If the app goes back into the foreground, the timer will
// get reset (but any stopped UWB session will not be auto-resumed).
if (!mHasNonPrivilegedFgApp) {
startNonPrivilegedBgAppTimerIfNotSet();
} else {
stopNonPrivilegedBgAppTimerIfSet();
}
} else {
Log.d(TAG, "reconfigureFiraSessionOnFgStateChange - System policy allows for "
+ "non fg 3p apps");
}
}
public int getOperationType() {
return mOperationType;
}
public void setOperationType(int type) {
mOperationType = type;
}
/** Creates a filter engine based on the device configuration. */
public UwbFilterEngine createFilterEngine() {
if (mParams instanceof FiraOpenSessionParams) {
FiraOpenSessionParams firaParams = (FiraOpenSessionParams) mParams;
if (firaParams.getFilterType() == FILTER_TYPE_NONE) {
return null; /* Bail early. App requested no engine. */
}
}
return mUwbInjector.createFilterEngine(mPoseSource);
}
/** Updates the pose information if an ApplicationPoseSource is being used. */
public void updatePose(FiraPoseUpdateParams updateParams) {
if (mPoseSource instanceof ApplicationPoseSource) {
ApplicationPoseSource aps = (ApplicationPoseSource) mPoseSource;
aps.applyPose(updateParams.getPoseInfo());
} else {
throw new IllegalStateException("Session not configured for application poses.");
}
}
@Override
public void binderDied() {
Log.i(TAG, "binderDied : getSessionId is getSessionId() " + getSessionId());
synchronized (UwbSessionManager.this) {
int status = mNativeUwbManager.deInitSession(getSessionId(), getChipId());
mUwbMetrics.logRangingCloseEvent(this, status);
if (status == UwbUciConstants.STATUS_CODE_OK) {
removeSession(this);
Log.i(TAG,
"binderDied : Fira/CCC Session counts currently are "
+ getFiraSessionCount()
+ "/" + getCccSessionCount());
} else {
Log.e(TAG,
"binderDied : sessionDeinit Failure because of NativeSessionDeinit "
+ "Error");
}
}
}
/**
* Cleans up resources held by this object.
*/
public void close() {
if (this.mAcquiredDefaultPose) {
if (mControleeList != null) {
for (UwbControlee controlee : mControleeList) {
controlee.close();
}
mControleeList.clear();
}
this.mAcquiredDefaultPose = false;
mUwbInjector.releasePoseSource();
}
mSendDataInfoMap.clear();
clearReceivedDataInfo();
}
/**
* Gets the pose source for this session. This may be the default pose source provided
* by UwbInjector.java when the session was created, or a specialized pose source later
* requested by the application.
*/
public IPoseSource getPoseSource() {
return mPoseSource;
}
@Override
public String toString() {
return "UwbSession: { Session Id: " + getSessionId()
+ ", Handle: " + getSessionHandle()
+ ", Protocol: " + getProtocolName()
+ ", State: " + getSessionState()
+ ", Data Send Sequence Number: " + mDataSndSequenceNumber
+ ", Params: " + getParams()
+ ", AttributionSource: " + getAttributionSource()
+ " }";
}
}
// TODO: refactor the async operation flow.
// Wrapper for unit test.
@VisibleForTesting
static class WaitObj {
WaitObj() {
}
void blockingWait() throws InterruptedException {
wait();
}
void blockingNotify() {
notify();
}
}
/**
* Dump the UWB session manager debug info
*/
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- Dump of UwbSessionManager ----");
pw.println("Active sessions: ");
for (UwbSession uwbSession : mSessionTable.values()) {
pw.println(uwbSession);
}
pw.println("Recently closed sessions: ");
for (UwbSession uwbSession: mDbgRecentlyClosedSessions.getEntries()) {
pw.println(uwbSession);
}
List<Integer> nonPrivilegedSessionIds =
mNonPrivilegedUidToFiraSessionsTable.entrySet()
.stream()
.map(e -> e.getValue()
.stream()
.map(UwbSession::getSessionId)
.collect(Collectors.toList()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
pw.println("Non Privileged Fira Session Ids: " + nonPrivilegedSessionIds);
pw.println("---- Dump of UwbSessionManager ----");
}
}