/* * 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.DEVICE_TYPE_CONTROLLER; import static com.android.server.uwb.data.UwbUciConstants.FIRA_VERSION_MAJOR_2; 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.RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_EDGE_TRIG; import static com.google.uwb.support.fira.FiraParams.RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; import android.content.AttributionSource; 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.GuardedBy; import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.server.uwb.advertisement.UwbAdvertiseManager; import com.android.server.uwb.data.DtTagUpdateRangingRoundsStatus; import com.android.server.uwb.data.UwbDeviceInfoResponse; import com.android.server.uwb.data.UwbDlTDoAMeasurement; import com.android.server.uwb.data.UwbMulticastListUpdateStatus; import com.android.server.uwb.data.UwbOwrAoaMeasurement; import com.android.server.uwb.data.UwbRadarData; 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.params.TlvUtil; import com.android.server.uwb.proto.UwbStatsLog; import com.android.server.uwb.rftest.RfNotificationEvent; 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.android.uwb.fusion.UwbFilterEngine; import com.android.uwb.fusion.pose.ApplicationPoseSource; import com.android.uwb.fusion.pose.IPoseSource; import com.google.common.collect.Sets; import com.google.uwb.support.aliro.AliroOpenRangingParams; import com.google.uwb.support.aliro.AliroParams; import com.google.uwb.support.aliro.AliroRangingStartedParams; import com.google.uwb.support.aliro.AliroRangingStoppedParams; import com.google.uwb.support.aliro.AliroSpecificationParams; import com.google.uwb.support.aliro.AliroStartRangingParams; 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.CccRangingStartedParams; import com.google.uwb.support.ccc.CccRangingStoppedParams; 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.FiraDataTransferPhaseConfig; import com.google.uwb.support.fira.FiraDataTransferPhaseConfig.FiraDataTransferPhaseManagementList; import com.google.uwb.support.fira.FiraHybridSessionControleeConfig; import com.google.uwb.support.fira.FiraHybridSessionControllerConfig; import com.google.uwb.support.fira.FiraOnControleeAddRemoveParams; 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.FiraProtocolVersion; 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.SessionConfigParams; import com.google.uwb.support.oemextension.SessionStatus; import com.google.uwb.support.rftest.RfTestParams; import com.google.uwb.support.rftest.RfTestSessionStatus; import com.google.uwb.support.rftest.RfTestStartSessionParams; import java.io.Closeable; import java.io.FileDescriptor; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; 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; private static final int UWB_HUS_CONTROLLER_PHASE_LIST_EXTENDED_MAC_ADDRESS_SIZE = 17; private static final int UWB_HUS_CONTROLEE_PHASE_LIST_SIZE = 4; @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; @VisibleForTesting public static final int SESSION_SET_HUS_CONTROLLER_CONFIG = 9; @VisibleForTesting public static final int SESSION_SET_HUS_CONTROLEE_CONFIG = 10; @VisibleForTesting public static final int SESSION_DATA_TRANSFER_PHASE_CONFIG = 11; @VisibleForTesting public static final int SESSION_RF_TEST_CMD = 12; @VisibleForTesting public static final int SESSION_STOP_RF_TEST_SESSION = 13; // TODO: don't expose the internal field for testing. @VisibleForTesting final ConcurrentHashMap mSessionTable = new ConcurrentHashMap(); // Used for storing recently closed sessions for debugging purposes. final LruList mDbgRecentlyClosedSessions = new LruList<>(5); final ConcurrentHashMap> mNonPrivilegedUidToFiraSessionsTable = new ConcurrentHashMap(); final ConcurrentHashMap 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; 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(); } @Override public void onUidImportance(final int uid, final int importance) { Handler handler = new Handler(mLooper); handler.post(() -> { synchronized (mNonPrivilegedUidToFiraSessionsTable) { List uwbSessions = mNonPrivilegedUidToFiraSessionsTable.get(uid); // Not a uid in the watch list if (uwbSessions == null) return; boolean newModeHasNonPrivilegedFgAppOrService = UwbInjector.isForegroundAppOrServiceImportance(importance); for (UwbSession uwbSession : uwbSessions) { // already at correct state. if (newModeHasNonPrivilegedFgAppOrService == uwbSession.hasNonPrivilegedFgAppOrService()) { continue; } uwbSession.setHasNonPrivilegedFgAppOrService( newModeHasNonPrivilegedFgAppOrService); int sessionId = uwbSession.getSessionId(); Log.i(TAG, "App state change for session " + sessionId + ". IsFg: " + newModeHasNonPrivilegedFgAppOrService); // Reconfigure the session based on the new fg/bg state 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; } } else if (rangingData.getRangingMeasuresType() == UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA) { for (UwbDlTDoAMeasurement measure : rangingData.getUwbDlTDoAMeasurements()) { if (measure.getStatus() == STATUS_CODE_OK) { return false; } } } return true; } private void handlePerControleeErrorStreakTimers(@NonNull UwbRangingData rangingData, @NonNull UwbSession uwbSession) { // TODO(b/343508180): The outer conditional is a workaround for inconsistent behavior on // cuttlefish (see b/343495601). Remove and/or refactor once this behavior is fixed. if (rangingData.getNoOfRangingMeasures() == 0) { // If we got no ranging measurements, start a session-level error streak timer. uwbSession.startRangingResultErrorStreakTimerIfNotSet(); } else { for (UwbTwoWayMeasurement measure : rangingData.getRangingTwoWayMeasures()) { UwbAddress address = UwbAddress.fromBytes(measure.getMacAddress()); if (measure.isStatusCodeOk()) { uwbSession.stopRangingResultErrorStreakTimerIfSet(address); uwbSession.stopRangingResultErrorStreakTimerIfSet(); } else { uwbSession.startRangingResultErrorStreakTimerIfNotSet(address); } } } } private void handlePerSessionErrorStreakTimer(@NonNull UwbRangingData rangingData, @NonNull UwbSession uwbSession) { if (hasAllRangingResultError(rangingData)) { uwbSession.startRangingResultErrorStreakTimerIfNotSet(); } else { uwbSession.stopRangingResultErrorStreakTimerIfSet(); } } private void handleRangingResultErrorStreakTimers(@NonNull UwbRangingData rangingData, @NonNull UwbSession uwbSession) { if (!mUwbInjector.getDeviceConfigFacade().isRangingErrorStreakTimerEnabled() || uwbSession.mRangingErrorStreakTimeoutMs == UwbSession.RANGING_RESULT_ERROR_NO_TIMEOUT) { return; } boolean isDeviceControllerOfTwoWayRangingSession = rangingData.getRangingMeasuresType() == UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY && uwbSession.getDeviceType() == DEVICE_TYPE_CONTROLLER; if (isDeviceControllerOfTwoWayRangingSession) { handlePerControleeErrorStreakTimers(rangingData, uwbSession); } else { handlePerSessionErrorStreakTimer(rangingData, uwbSession); } } @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); handleRangingResultErrorStreakTimers(rangingData, uwbSession); } 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, 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; } mUwbMetrics.logDataRx(uwbSession, status); Long longAddress = macAddressByteArrayToLong(address); UwbAddress uwbAddress = UwbAddress.fromBytes(address); // When the data packet is received on a non OWR-for-AoA ranging session, send it to the // higher layer. For the OWR-for-AoA ranging session, the data packet is only sent when the // received SESSION_INFO_NTF indicate this Observer device is pointing to an Advertiser. if (uwbSession.getRangingRoundUsage() != ROUND_USAGE_OWR_AOA_MEASUREMENT) { mSessionNotificationManager.onDataReceived( uwbSession, uwbAddress, new PersistableBundle(), data); return; } ReceivedDataInfo info = new ReceivedDataInfo(); info.sessionId = sessionId; info.status = status; info.sequenceNum = sequenceNum; info.address = longAddress; info.payload = data; uwbSession.addReceivedDataInfo(info); } /* Notification of data send status */ @Override public void onDataSendStatus( long sessionId, int dataTransferStatus, long sequenceNum, int txCount) { Log.d(TAG, "onDataSendStatus(): Received data send status - " + ", sessionId: " + sessionId + ", status: " + dataTransferStatus + ", sequenceNum: " + sequenceNum + ", txCount: " + txCount); 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); uwbSession.removeSendDataInfo(sequenceNum); } // when transmission count equals to data repetition count, SendDataInfo will be removed for // the particular sequence number if (txCount >= (uwbSession.getDataRepetitionCount() + 1) && dataTransferStatus == UwbUciConstants.STATUS_CODE_DATA_TRANSFER_OK) { uwbSession.removeSendDataInfo(sequenceNum); } } @Override public void onRadarDataMessageReceived(UwbRadarData radarData) { Trace.beginSection("UWB#onRadarDataMessageReceived"); long sessionId = radarData.sessionId; UwbSession uwbSession = getUwbSession((int) sessionId); if (uwbSession != null) { mSessionNotificationManager.onRadarDataMessageReceived(uwbSession, radarData); } else { Log.i(TAG, "Session is not initialized or Radar Data is Null"); } Trace.endSection(); } @Override public void onDataTransferPhaseConfigNotificationReceived(long sessionId, int dataTransferPhaseConfigStatus) { Log.d(TAG, "onDataTransferPhaseConfigNotificationReceived:" + ", sessionId: " + sessionId + ", status: " + dataTransferPhaseConfigStatus); UwbSession uwbSession = getUwbSession((int) sessionId); if (uwbSession == null) { Log.e(TAG, "onDataTransferPhaseConfigNotificationReceived: Received data transfer" + "config notification for unknown sessionId = " + sessionId); return; } if (dataTransferPhaseConfigStatus == UwbUciConstants.STATUS_CODE_DATA_TRANSFER_PHASE_CONFIG_DTPCM_CONFIG_SUCCESS) { mSessionNotificationManager.onDataTransferPhaseConfigured( uwbSession, dataTransferPhaseConfigStatus); } else { mSessionNotificationManager.onDataTransferPhaseConfigFailed( uwbSession, dataTransferPhaseConfigStatus); } } /** 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 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); int actionStatus = UwbUciConstants.STATUS_CODE_OK; for (int i = 0; i < multicastListUpdateStatus.getNumOfControlee(); i++) { actionStatus = multicastListUpdateStatus.getStatus()[i]; // Action - delete controlee, State - Active if (actionStatus == UwbUciConstants.STATUS_CODE_OK) { if (uwbSession.getOperationType() == SESSION_RECONFIG_RANGING) { synchronized (uwbSession.getWaitObj()) { uwbSession.getWaitObj().blockingNotify(); } break; } } else { // Handle the failure case for adding a controlee mSessionNotificationManager.onControleeAddFailed( uwbSession, multicastListUpdateStatus.getControleeUwbAddresses()[i], actionStatus); } } if (actionStatus != UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingReconfigureFailed( uwbSession, actionStatus); } } @Override public void onRfTestNotificationReceived(RfNotificationEvent rfNotificationEvent) { UwbSession uwbSession = getUwbSession(RfTestParams.SESSION_ID_RFTEST); if (uwbSession == null) { Log.d(TAG, "UwbSession doesn't exist for SESSION_ID_RFTEST"); return; } mSessionNotificationManager.onRfTestNotificationReceived(uwbSession, rfNotificationEvent); } @Override public void onSessionStatusNotificationReceived(long sessionId, int sessionToken, int state, int reasonCode) { Log.i(TAG, "onSessionStatusNotificationReceived - Session ID : " + sessionId + ", sessionToken: " + sessionToken + ", state : " + UwbSessionNotificationHelper.getSessionStateString(state) + ", reasonCode:" + reasonCode); UwbSession uwbSession = getUwbSession((int) sessionId); if (uwbSession == null) { Log.d(TAG, "onSessionStatusNotificationReceived - invalid session"); return; } 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"); } // Store the reasonCode before notifying on the waitObj. synchronized (uwbSession.getWaitObj()) { uwbSession.setLastSessionStatusNtfReasonCode(reasonCode); 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; } if (mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) { String appPackageName = uwbSession.getAnyNonPrivilegedAppInAttributionSource() != null ? uwbSession.getAnyNonPrivilegedAppInAttributionSource().getPackageName() : uwbSession.getAttributionSource().getPackageName(); PersistableBundle sessionStatusBundle = new SessionStatus.Builder() .setSessionId(sessionId) .setState(state) .setReasonCode(reasonCode) .setAppPackageName(appPackageName) .setSessiontoken(sessionToken) .setProtocolName(uwbSession.getProtocolName()) .build() .toBundle(); try { mUwbInjector.getUwbServiceCore().getOemExtensionCallback() .onSessionStatusNotificationReceived(sessionStatusBundle); } catch (RemoteException e) { Log.e(TAG, "Failed to send vendor notification", e); } } } private int setAppConfigurations(UwbSession uwbSession) { if (uwbSession.getProtocolName().equals(FiraParams.PROTOCOL_NAME) && uwbSession.getParams() instanceof FiraOpenSessionParams) { FiraOpenSessionParams params = (FiraOpenSessionParams) uwbSession.getParams(); if (mSessionTokenMap.containsKey(params.getReferenceSessionHandle())) { uwbSession.updateFiraParamsForSessionTimeBase( mSessionTokenMap.get(params.getReferenceSessionHandle())); } } int status = mConfigurationManager.setAppConfigurations(uwbSession.getSessionId(), uwbSession.getParams(), uwbSession.getChipId(), getUwbsFiraProtocolVersion(uwbSession.getChipId())); if (status == UwbUciConstants.STATUS_CODE_OK && uwbSession.getProtocolName().equals(RfTestParams.PROTOCOL_NAME)) { status = mConfigurationManager.setRfTestAppConfigurations(uwbSession.getSessionId(), uwbSession.getParams(), uwbSession.getChipId()); } if (status == UwbUciConstants.STATUS_CODE_OK && mUwbInjector.getUwbServiceCore().isOemExtensionCbRegistered()) { try { SessionConfigParams sessionConfigParams = new SessionConfigParams.Builder() .setSessionId(uwbSession.getSessionId()) .setSessiontoken( mSessionTokenMap.getOrDefault(uwbSession.getSessionId(), 0)) .setOpenSessionParamsBundle(uwbSession.getParams().toBundle()) .build(); status = mUwbInjector.getUwbServiceCore().getOemExtensionCallback() .onSessionConfigurationReceived(sessionConfigParams.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. boolean hasNonPrivilegedFgAppOrService = mUwbInjector.isForegroundAppOrService( nonPrivilegedAppAttrSource.getUid(), nonPrivilegedAppAttrSource.getPackageName()); uwbSession.setHasNonPrivilegedFgAppOrService(hasNonPrivilegedFgAppOrService); if (!hasNonPrivilegedFgAppOrService) { 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(AliroParams.PROTOCOL_NAME) && getAliroSessionCount() >= getMaxAliroSessionsNumber(chipId)) { Log.i(TAG, "Max ALIRO Sessions Exceeded"); // All ALIRO 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(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 && getSessionCount() >= getMaxSupportedSessionCount(chipId)) { maxSessionsExceeded = true; Log.i(TAG, "Session count exceeds max supported Session count"); } 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 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); mEventTask.execute(SESSION_DEINIT, sessionHandle, 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); mEventTask.execute(SESSION_DEINIT, sessionHandle, 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(AliroParams.PROTOCOL_NAME) && params instanceof AliroStartRangingParams) { AliroStartRangingParams aliroStartRangingParams = (AliroStartRangingParams) params; Log.i(TAG, "startRanging() - update RAN multiplier: " + aliroStartRangingParams.getRanMultiplier() + ", stsIndex: " + aliroStartRangingParams.getStsIndex()); // Need to update the RAN multiplier from the AliroStartRangingParams for an // ALIRO session. uwbSession.updateAliroParamsOnStart(aliroStartRangingParams); } else if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME) && params instanceof CccStartRangingParams) { CccStartRangingParams cccStartRangingParams = (CccStartRangingParams) params; Log.i(TAG, "startRanging() - update RAN multiplier: " + cccStartRangingParams.getRanMultiplier() + ", stsIndex: " + cccStartRangingParams.getStsIndex()); // Need to update the RAN multiplier from the CccStartRangingParams for CCC session. uwbSession.updateCccParamsOnStart(cccStartRangingParams); } else if (uwbSession.getProtocolName().equals(FiraParams.PROTOCOL_NAME)) { // Need to update session priority if it changed. uwbSession.updateFiraParamsOnStartIfChanged(); } else if (uwbSession.getProtocolName().equals(RfTestParams.PROTOCOL_NAME)) { if (params instanceof RfTestStartSessionParams) { uwbSession.setRfTestStartSessionParams((RfTestStartSessionParams) params); mEventTask.execute(SESSION_RF_TEST_CMD, uwbSession); } return; } 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 void handleRfTestCommand(UwbSession uwbSession) { RfTestStartSessionParams params = uwbSession.getRfTestStartSessionParams(); // create task to send RF command FutureTask rfCommandTask = new FutureTask<>((Callable) () -> { int rfTestOperationType = params.getRfTestOperationType(); int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { switch (rfTestOperationType) { case RfTestParams.TEST_PERIODIC_TX: status = mNativeUwbManager.testPeriodicTx(params.getPsduData(), uwbSession.getChipId()); break; case RfTestParams.TEST_PER_RX: status = mNativeUwbManager.testPerRx(params.getPsduData(), uwbSession.getChipId()); break; default: Log.i(TAG, "Unknown RF command: " + rfTestOperationType); } if (status != UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingStartFailed(uwbSession, status); } else { uwbSession.getWaitObj().blockingWait(); RfTestSessionStatus rfTestSessionStatus = new RfTestSessionStatus.Builder() .setRfTestOperationType(rfTestOperationType) .setStatusCode(status).build(); mSessionNotificationManager.onRangingStarted(uwbSession, rfTestSessionStatus); } } return status; }); // Execute task int status = UwbUciConstants.STATUS_CODE_FAILED; try { status = mUwbInjector.runTaskOnSingleThreadExecutor(rfCommandTask, IUwbAdapter.RF_TEST_OPERATION_THRESHOLD_MS); } catch (TimeoutException e) { Log.i(TAG, "Failed to send RF command: TIMEOUT"); mSessionNotificationManager.onRangingStartFailed(uwbSession, status); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } private void handleStopRfTest(UwbSession uwbSession) { Trace.beginSection("UWB#handleStopRfTest"); FutureTask stopRfSessionTask = new FutureTask<>( () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { status = mNativeUwbManager.stopRfTest(uwbSession.getChipId()); if (status != UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingStopFailed(uwbSession, status); return status; } uwbSession.getWaitObj().blockingWait(); // After stop rf test session command, UWBS will go to IDLE state if (uwbSession.getSessionState() == UwbUciConstants.UWB_SESSION_STATE_IDLE) { mSessionNotificationManager.onRangingStopped(uwbSession, status); } else { status = UwbUciConstants.STATUS_CODE_FAILED; mSessionNotificationManager.onRangingStopFailed(uwbSession, status); } } return status; }); int status = UwbUciConstants.STATUS_CODE_FAILED; try { status = mUwbInjector.runTaskOnSingleThreadExecutor(stopRfSessionTask, IUwbAdapter.RF_TEST_OPERATION_THRESHOLD_MS); } catch (TimeoutException e) { Log.i(TAG, "Failed to Stop RF test - status : TIMEOUT"); mSessionNotificationManager.onRangingStopFailed( uwbSession, UwbUciConstants.STATUS_CODE_FAILED); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } Trace.endSection(); } 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); //RF Test session if (uwbSession.getProtocolName().equals(RfTestParams.PROTOCOL_NAME)) { if (sessionId != RfTestParams.SESSION_ID_RFTEST) { mSessionNotificationManager.onRangingStopped(uwbSession, UwbUciConstants.STATUS_CODE_REJECTED); } if (currentSessionState == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { mEventTask.execute(SESSION_STOP_RF_TEST_SESSION, uwbSession); } else { mSessionNotificationManager.onRangingStopped(uwbSession, UwbUciConstants.STATUS_CODE_REJECTED); Log.i(TAG, "RF session is not in ACTIVE state"); } return; } 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 UwbSession corresponding to the given UWB SessionHandle. This API returns * {@code null} when the UWB session is not found. */ @Nullable private UwbSession getUwbSession(SessionHandle sessionHandle) { return mSessionTable.get(sessionHandle); } /** * 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 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 getAliroSessionCount() { return getProtocolSessionCount(AliroParams.PROTOCOL_NAME); } public long getCccSessionCount() { return getProtocolSessionCount(CccParams.PROTOCOL_NAME); } public long getFiraSessionCount() { return getProtocolSessionCount(FiraParams.PROTOCOL_NAME); } private long getProtocolSessionCount(String protocolName) { return mSessionTable.values().stream().filter( s -> s.mProtocolName.equals(protocolName)).count(); } /** Returns max number of ALIRO sessions possible on given chip. */ public long getMaxAliroSessionsNumber(String chipId) { GenericSpecificationParams params = mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(chipId); if (params != null && params.getAliroSpecificationParams() != null) { return params.getAliroSpecificationParams().getMaxRangingSessionNumber(); } else { // Specification params are empty, return the default ALIRO max sessions value return AliroSpecificationParams.DEFAULT_MAX_RANGING_SESSIONS_NUMBER; } } /** 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; } } /** Returns max supported session count possible on given chip. */ public long getMaxSupportedSessionCount(String chipId) { GenericSpecificationParams params = mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(chipId); return (params != null) ? params.getMaxSupportedSessionCount() : getMaxFiraSessionsNumber(chipId) + getMaxCccSessionsNumber(chipId); } /** Gets the session with the lowest session priority among all sessions with given protocol. */ public Optional getSessionWithLowestPriorityByProtocol(String protocolName) { return mSessionTable.values().stream().filter( s -> s.mProtocolName.equals(protocolName)).min( Comparator.comparingInt(UwbSession::getStackSessionPriority)); } public Set getSessionIdSet() { return mSessionTable.values() .stream() .map(v -> v.getSessionId()) .collect(Collectors.toSet()); } private boolean suspendRangingPreconditionCheck(UwbSession uwbSession) { FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) uwbSession.getParams(); int deviceType = firaOpenSessionParams.getDeviceType(); int scheduleMode = firaOpenSessionParams.getScheduledMode(); int sessionState = uwbSession.getSessionState(); if (deviceType != FiraParams.RANGING_DEVICE_TYPE_CONTROLLER || scheduleMode != FiraParams.TIME_SCHEDULED_RANGING || sessionState != UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { Log.e(TAG, "suspendRangingPreconditionCheck failed - deviceType: " + deviceType + " scheduleMode: " + scheduleMode + " sessionState: " + sessionState); return false; } return true; } private boolean sessionUpdateMulticastListCmdPreconditioncheck(UwbSession uwbSession, int action, byte[] subSessionKeyList) { FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) uwbSession.getParams(); int deviceType = firaOpenSessionParams.getDeviceType(); int stsConfig = firaOpenSessionParams.getStsConfig(); byte[] sessionKey = firaOpenSessionParams.getSessionKey(); Log.i(TAG, "sessionUpdateMulticastListCmdPreconditioncheck - deviceType: " + deviceType + " stsConfig: " + stsConfig + " action: " + action); if (deviceType == FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) { switch (action) { case FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD: case FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE: if (subSessionKeyList != null) { return false; } break; case FiraParams.P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_16_BYTE: case FiraParams.P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_32_BYTE: if (stsConfig != FiraParams.STS_CONFIG_PROVISIONED_FOR_CONTROLEE_INDIVIDUAL_KEY) { return false; } // sessionKey is provided while opening the session and subSessionKeyList // is provided while updating the multicast list. Check that both the // sessionKey and subSessionKeyList are either set or not set (as both // have to be provided by the same source - Host or SE). if ((sessionKey == null && subSessionKeyList != null) || (sessionKey != null && subSessionKeyList == null)) { return false; } break; default: break; } } else { return false; } return true; } private synchronized int reconfigureInternal(SessionHandle sessionHandle, @Nullable Params params, Reconfiguration.Reason reason) { 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); // suspendRangingPreconditionCheck only on suspend ranging reconfigure if ((rangingReconfigureParams.getSuspendRangingRounds() != null) && (!suspendRangingPreconditionCheck(uwbSession))) { return UwbUciConstants.STATUS_CODE_REJECTED; } if ((rangingReconfigureParams.getAddressList() != null) && (!sessionUpdateMulticastListCmdPreconditioncheck(uwbSession, rangingReconfigureParams.getAction(), rangingReconfigureParams.getSubSessionKeyList()))) { mSessionNotificationManager.onRangingReconfigureFailed( uwbSession, UwbUciConstants.STATUS_CODE_INVALID_PARAM); return UwbUciConstants.STATUS_CODE_REJECTED; } // Do not update mParams if this was triggered by framework. if (reason != Reconfiguration.Reason.FG_STATE_CHANGE) { uwbSession.updateFiraParamsOnReconfigure(rangingReconfigureParams); } } else if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME) && params instanceof CccRangingReconfiguredParams) { CccRangingReconfiguredParams rangingReconfigureParams = (CccRangingReconfiguredParams) params; // Do not update mParams if this was triggered by framework. if (reason != Reconfiguration.Reason.FG_STATE_CHANGE) { uwbSession.updateCccParamsOnReconfigure(rangingReconfigureParams); } } mEventTask.execute(SESSION_RECONFIG_RANGING, new Reconfiguration(uwbSession, params, reason)); return 0; } public synchronized int reconfigure(SessionHandle sessionHandle, @Nullable Params params) { return reconfigureInternal(sessionHandle, params, Reconfiguration.Reason.REQUESTED_BY_API); } /** 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); } /** * Sets the hybrid UWB controller configuration. * * @param sessionHandle : Primary session handle. * @param params : protocol specific parameters to configure the hybrid * session controller. */ public void setHybridSessionControllerConfiguration(SessionHandle sessionHandle, PersistableBundle params) { if (!isExistedSession(sessionHandle)) { throw new IllegalStateException("Not initialized session ID: " + getSessionId(sessionHandle)); } HybridSessionConfig hybridSessionConfig = new HybridSessionConfig(); hybridSessionConfig.sessionHandle = sessionHandle; hybridSessionConfig.params = params; mEventTask.execute(SESSION_SET_HUS_CONTROLLER_CONFIG, hybridSessionConfig); } /** * Sets the hybrid UWB controlee configuration. * * @param sessionHandle : Primary session handle. * @param params : protocol specific parameters to configure the hybrid * session controlee. */ public void setHybridSessionControleeConfiguration(SessionHandle sessionHandle, PersistableBundle params) { if (!isExistedSession(sessionHandle)) { throw new IllegalStateException("Not initialized session ID: " + getSessionId(sessionHandle)); } HybridSessionConfig hybridSessionConfig = new HybridSessionConfig(); hybridSessionConfig.sessionHandle = sessionHandle; hybridSessionConfig.params = params; mEventTask.execute(SESSION_SET_HUS_CONTROLEE_CONFIG, hybridSessionConfig); } /** * Sets the data transfer session configuration * * @param sessionHandle : session handle * @param params : protocol specific parameters to configure data transfer session */ public void setDataTransferPhaseConfig(SessionHandle sessionHandle, PersistableBundle params) { if (!isExistedSession(sessionHandle)) { throw new IllegalStateException("Not initialized session ID: " + getSessionId(sessionHandle)); } UpdateSessionInfo updateSessionInfo = new UpdateSessionInfo(); updateSessionInfo.sessionHandle = sessionHandle; updateSessionInfo.params = params; mEventTask.execute(SESSION_DATA_TRANSFER_PHASE_CONFIG, updateSessionInfo); } 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; } private static final class UpdateSessionInfo { public SessionHandle sessionHandle; public PersistableBundle params; } private static final class HybridSessionConfig { 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 */ private 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 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); } private void handleSetHybridSessionControllerConfiguration(HybridSessionConfig info) { SessionHandle sessionHandle = info.sessionHandle; if (!isExistedSession(sessionHandle)) { Log.e(TAG, "handleSetHybridSessionControllerConfiguration() - cannot find session"); return; } int sessionId = getSessionId(sessionHandle); UwbSession uwbSession = getUwbSession(sessionId); // precondition check int deviceType = uwbSession.getDeviceType(); int scheduleMode = uwbSession.getScheduledMode(); int sessionType = uwbSession.getSessionType(); if (UwbUciConstants.DEVICE_TYPE_CONTROLLER != deviceType || UwbUciConstants.HYBRID_SCHEDULED_RANGING != scheduleMode || UwbUciConstants.SESSION_TYPE_HUS_PRIMARY_SESSION != sessionType) { Log.e(TAG, "SetHybridSessionControllerConfiguration() failed: device type: " + deviceType + " schedule mode: " + scheduleMode + " sessionType: " + sessionType); mSessionNotificationManager.onHybridSessionControllerConfigurationFailed( uwbSession, UwbUciConstants.STATUS_CODE_FAILED); return; } FiraHybridSessionControllerConfig husConfig = FiraHybridSessionControllerConfig.fromBundle(info.params); int numberOfPhases = husConfig.getNumberOfPhases(); Log.i(TAG, "handleSetHybridSessionControllerConfiguration() - sessionId: " + sessionId + ", sessionHandle: " + sessionHandle + ", numberOfPhases: " + numberOfPhases); ByteBuffer buffer = ByteBuffer.allocate(numberOfPhases * UWB_HUS_CONTROLLER_PHASE_LIST_EXTENDED_MAC_ADDRESS_SIZE); buffer.order(ByteOrder.LITTLE_ENDIAN); for (FiraHybridSessionControllerConfig.FiraHybridSessionPhaseList phaseList : husConfig.getPhaseList()) { buffer.putInt(phaseList.getSessionId()); buffer.putShort(phaseList.getStartSlotIndex()); buffer.putShort(phaseList.getEndSlotIndex()); byte messageControl = phaseList.getMessageControl(); buffer.put(messageControl); // validate the MacAddress byte macAddressMode = (byte) (messageControl & 0x01); int addressByteLength = (macAddressMode == UwbUciConstants.SHORT_MAC_ADDRESS) ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH : UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH; UwbAddress uwbAddress = phaseList.getMacAddress(); if (uwbAddress == null || uwbAddress.size() != addressByteLength) { Log.e(TAG, "handleSetHybridSessionControllerConfiguration() invalid UWB address"); mSessionNotificationManager.onHybridSessionControllerConfigurationFailed( uwbSession, UwbUciConstants.STATUS_CODE_FAILED); return; } buffer.put(getComputedMacAddress(uwbAddress)); } byte[] phaseListArray = Arrays.copyOf(buffer.array(), buffer.position()); // create session set hus controller configuration task FutureTask sessionsetHybridControllerConfigTask = new FutureTask<>( (Callable) () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { status = mNativeUwbManager.setHybridSessionControllerConfiguration( sessionId, numberOfPhases, phaseListArray, uwbSession.getChipId()); } return status; } ); // execute task int status = UwbUciConstants.STATUS_CODE_FAILED; try { status = mUwbInjector.runTaskOnSingleThreadExecutor( sessionsetHybridControllerConfigTask, IUwbAdapter.SESSION_CONFIGURATION_THRESHOLD_MS); } catch (TimeoutException e) { Log.e(TAG, "Failed to set session hybrid controller config : TIMEOUT"); mSessionNotificationManager.onHybridSessionControllerConfigurationFailed( uwbSession, status); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Exception while executing task " + e); } if (UwbUciConstants.STATUS_CODE_OK == status) { mSessionNotificationManager.onHybridSessionControllerConfigured(uwbSession, status); } else { Log.e(TAG, "Failed to configure controller hybrid session - status : " + status); mSessionNotificationManager.onHybridSessionControllerConfigurationFailed(uwbSession, status); } } private void handleSetHybridSessionControleeConfiguration(HybridSessionConfig info) { SessionHandle sessionHandle = info.sessionHandle; if (!isExistedSession(sessionHandle)) { Log.e(TAG, "handleSetHybridSessionControlleeConfiguration() - cannot find session"); return; } int sessionId = getSessionId(sessionHandle); UwbSession uwbSession = getUwbSession(sessionId); // precondition check int deviceType = uwbSession.getDeviceType(); int scheduleMode = uwbSession.getScheduledMode(); int sessionType = uwbSession.getSessionType(); if (UwbUciConstants.DEVICE_TYPE_CONTROLEE != deviceType || UwbUciConstants.HYBRID_SCHEDULED_RANGING != scheduleMode || UwbUciConstants.SESSION_TYPE_HUS_PRIMARY_SESSION != sessionType) { Log.e(TAG, "handleSetHybridSessionControleeConfiguration() failed: device type: " + deviceType + " schedule mode: " + scheduleMode + " sessionType: " + sessionType); mSessionNotificationManager.onHybridSessionControleeConfigurationFailed( uwbSession, UwbUciConstants.STATUS_CODE_FAILED); return; } FiraHybridSessionControleeConfig controleeConfig = FiraHybridSessionControleeConfig.fromBundle(info.params); int numberOfPhases = controleeConfig.getNumberOfPhases(); Log.i(TAG, "handleSetHybridSessionControleeConfiguration() - sessionId: " + sessionId + ", sessionHandle: " + sessionHandle + ", numberOfPhases: " + numberOfPhases); ByteBuffer phaseListBuffer = ByteBuffer.allocate(numberOfPhases * UWB_HUS_CONTROLEE_PHASE_LIST_SIZE); phaseListBuffer.order(ByteOrder.LITTLE_ENDIAN); for (FiraHybridSessionControleeConfig.FiraHybridSessionPhaseList phaseList : controleeConfig.getPhaseList()) { phaseListBuffer.putInt(mNativeUwbManager.getSessionToken(phaseList.getSessionHandle(), uwbSession.getChipId())); } // create session set hus controlee configuration task FutureTask sessionsetHybridControleeConfigTask = new FutureTask<>( (Callable) () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { status = mNativeUwbManager.setHybridSessionControleeConfiguration( sessionId, numberOfPhases, phaseListBuffer.array(), uwbSession.getChipId()); } return status; } ); // execute task int status = UwbUciConstants.STATUS_CODE_FAILED; try { status = mUwbInjector.runTaskOnSingleThreadExecutor( sessionsetHybridControleeConfigTask, IUwbAdapter.SESSION_CONFIGURATION_THRESHOLD_MS); } catch (TimeoutException e) { Log.e(TAG, "Failed to set session hybrid controlee config : TIMEOUT"); mSessionNotificationManager.onHybridSessionControleeConfigurationFailed( uwbSession, status); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Exception while executing task " + e); } if (UwbUciConstants.STATUS_CODE_OK == status) { mSessionNotificationManager.onHybridSessionControleeConfigured(uwbSession, status); } else { Log.e(TAG, "Failed to configure controlee hybrid session - status : " + status); mSessionNotificationManager.onHybridSessionControleeConfigurationFailed(uwbSession, status); } } private void handleSetDataTransferPhaseConfig(UpdateSessionInfo info) { SessionHandle sessionHandle = info.sessionHandle; Integer sessionId = getSessionId(sessionHandle); UwbSession uwbSession = getUwbSession(sessionHandle); int sessionType = uwbSession.getSessionType(); int deviceType = uwbSession.getDeviceType(); int sessionState = uwbSession.getSessionState(); if (UwbUciConstants.DEVICE_TYPE_CONTROLLER != deviceType || (sessionType != FiraParams.SESSION_TYPE_DATA_TRANSFER && sessionType != FiraParams.SESSION_TYPE_IN_BAND_DATA_PHASE) || (sessionState != UwbUciConstants.UWB_SESSION_STATE_IDLE && sessionState != UwbUciConstants.UWB_SESSION_STATE_ACTIVE)) { Log.e(TAG, "SetDataTransferPhaseConfig failed: session type:" + sessionType + " device type:" + deviceType + " sessionState:" + sessionState); mSessionNotificationManager.onDataTransferPhaseConfigFailed(uwbSession, UwbUciConstants.STATUS_CODE_REJECTED); return; } FiraDataTransferPhaseConfig dataTransferPhaseConfig = FiraDataTransferPhaseConfig.fromBundle(info.params); List mDataTransferPhaseManagementList = dataTransferPhaseConfig.getDataTransferPhaseManagementList(); int dataTransferManagementListSize = mDataTransferPhaseManagementList.size(); int dataTransferControl = dataTransferPhaseConfig.getDataTransferControl(); int slotBitmapSizeInBytes = 1 << ((dataTransferControl & 0X0F) >> 1); List macAddressList = new ArrayList<>(); ByteBuffer slotBitmapByteBuffer = ByteBuffer.allocate(dataTransferManagementListSize * slotBitmapSizeInBytes); slotBitmapByteBuffer.order(ByteOrder.LITTLE_ENDIAN); ByteBuffer stopDataTransferByteBuffer = ByteBuffer.allocate(dataTransferManagementListSize); int addressByteLength = ((dataTransferControl & 0x01) == UwbUciConstants.SHORT_MAC_ADDRESS) ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH : UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH; for (FiraDataTransferPhaseManagementList dataTransferPhaseManagementList : mDataTransferPhaseManagementList) { UwbAddress uwbAddress = dataTransferPhaseManagementList.getUwbAddress(); byte[] slotBitMap = dataTransferPhaseManagementList.getSlotBitMap(); byte stopDataTransfer = dataTransferPhaseManagementList.getStopDataTransfer(); if (uwbAddress != null && uwbAddress.size() == addressByteLength && slotBitMap.length == slotBitmapSizeInBytes) { macAddressList.add(getComputedMacAddress(uwbAddress)); slotBitmapByteBuffer.put(slotBitMap); stopDataTransferByteBuffer.put(stopDataTransfer); } else { Log.e(TAG, "handleSetDataTransferPhaseConfig: slot bitmap size " + "or address is not matching"); return; } } // Check for buffer size mismatches if (slotBitmapByteBuffer.array().length != (slotBitmapSizeInBytes * dataTransferManagementListSize) || macAddressList.size() != dataTransferManagementListSize) { Log.e(TAG, "handleSetDataTransferPhaseConfig: slot bitmap buffer size or address list" + " size mismatch"); return; } // create session data transfer phase configuration task FutureTask sessionDataTransferPhaseConfigTask = new FutureTask<>( (Callable) () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { status = mNativeUwbManager.setDataTransferPhaseConfig(sessionId, (byte) dataTransferPhaseConfig.getDtpcmRepetition(), (byte) dataTransferControl, (byte) dataTransferManagementListSize, ArrayUtils.toPrimitive(macAddressList), slotBitmapByteBuffer.array(), stopDataTransferByteBuffer.array(), uwbSession.getChipId()); } return status; } ); // execute task int status = UwbUciConstants.STATUS_CODE_FAILED; try { status = mUwbInjector.runTaskOnSingleThreadExecutor(sessionDataTransferPhaseConfigTask, IUwbAdapter.SESSION_DATA_TRANSFER_PHASE_CONFIG_THRESHOLD_MS); } catch (TimeoutException e) { Log.e(TAG, "Failed to set session data transfer phase config : TIMEOUT"); mSessionNotificationManager.onDataTransferPhaseConfigFailed(uwbSession, status); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Exception while executing task " + e); } if (status != UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onDataTransferPhaseConfigFailed(uwbSession, status); } } void removeSession(UwbSession uwbSession) { if (uwbSession != null) { try { uwbSession.getBinder().unlinkToDeath(uwbSession, 0); } catch (NoSuchElementException e) { Log.e(TAG, "unlinkToDeath fail - sessionID : " + uwbSession.getSessionId()); } removeAdvertiserData(uwbSession); uwbSession.close(); removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(uwbSession); if (!uwbSession.isDataDeliveryPermissionCheckNeeded()) { mUwbInjector.finishUwbRangingPermissionForDataDelivery( uwbSession.getAttributionSource()); } 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) { return; } synchronized (mNonPrivilegedUidToFiraSessionsTable) { AttributionSource nonPrivilegedAppAttrSource = uwbSession.getAnyNonPrivilegedAppInAttributionSource(); if (nonPrivilegedAppAttrSource != null) { Log.d(TAG, "Detected start of non privileged FIRA session from " + nonPrivilegedAppAttrSource); List sessions = mNonPrivilegedUidToFiraSessionsTable.computeIfAbsent( nonPrivilegedAppAttrSource.getUid(), v -> new ArrayList<>()); sessions.add(uwbSession); } } } void removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) { if (uwbSession.getSessionType() != UwbUciConstants.SESSION_TYPE_RANGING) { return; } AttributionSource nonPrivilegedAppAttrSource = uwbSession.getAnyNonPrivilegedAppInAttributionSource(); if (nonPrivilegedAppAttrSource == null) { return; } Log.d(TAG, "Detected end of non privileged FIRA session from " + nonPrivilegedAppAttrSource); synchronized (mNonPrivilegedUidToFiraSessionsTable) { List 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 Reconfiguration { public final UwbSession mUwbSession; public final Params mParams; public final Reason mReason; /** * Reason for the reconfiguration. If mParams is an instance of {@link * FiraRangingReconfigureParams}, the reason can be interpreted as an action-specific * reason code. */ public enum Reason { UNKNOWN, LOST_CONNECTION, REQUESTED_BY_API, FG_STATE_CHANGE; /** * Use this for {@link FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE} actions. * @return the reason for controlee removal. */ public @FiraOnControleeAddRemoveParams.Reason int asControleeRemovedReason() { switch (this) { case LOST_CONNECTION: return FiraOnControleeAddRemoveParams.Reason.LOST_CONNECTION; case REQUESTED_BY_API: return FiraOnControleeAddRemoveParams.Reason.REQUESTED_BY_API; default: return FiraOnControleeAddRemoveParams.Reason.UNKNOWN; } } } Reconfiguration(UwbSession uwbSession, Params params, Reason reason) { mUwbSession = uwbSession; mParams = params; mReason = reason; } } 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"); Reconfiguration reconfiguration = (Reconfiguration) msg.obj; handleReconfigure(reconfiguration.mUwbSession, reconfiguration.mParams, reconfiguration.mReason); break; } case SESSION_DEINIT: { SessionHandle sessionHandle = (SessionHandle) msg.obj; int reason = msg.arg1; handleDeInitWithReason(sessionHandle, 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; } case SESSION_SET_HUS_CONTROLLER_CONFIG: { Log.d(TAG, "SESSION_SET_HUS_CONTROLLER_CONFIG"); HybridSessionConfig info = (HybridSessionConfig) msg.obj; handleSetHybridSessionControllerConfiguration(info); break; } case SESSION_SET_HUS_CONTROLEE_CONFIG: { Log.d(TAG, "SESSION_SET_HUS_CONTROLEE_CONFIG"); HybridSessionConfig info = (HybridSessionConfig) msg.obj; handleSetHybridSessionControleeConfiguration(info); break; } case SESSION_DATA_TRANSFER_PHASE_CONFIG: { Log.d(TAG, "SESSION_DATA_TRANSFER_PHASE_CONFIG"); UpdateSessionInfo info = (UpdateSessionInfo) msg.obj; handleSetDataTransferPhaseConfig(info); break; } case SESSION_RF_TEST_CMD: { Log.d(TAG, "SESSION_RF_TEST_CMD"); UwbSession uwbSession = (UwbSession) msg.obj; handleRfTestCommand(uwbSession); break; } case SESSION_STOP_RF_TEST_SESSION: { Log.d(TAG, "SESSION_STOP_RF_TEST_SESSION"); UwbSession uwbSession = (UwbSession) msg.obj; handleStopRfTest(uwbSession); 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 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) { uwbSession.setNeedsQueryUwbsTimestamp( null /* cccRangingStartParams */); uwbSession.setAbsoluteInitiationTimeIfNeeded(); status = UwbSessionManager.this.setAppConfigurations(uwbSession); uwbSession.resetAbsoluteInitiationTime(); 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 startRangingTask = new FutureTask<>( () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { uwbSession.setAbsoluteInitiationTimeIfNeeded(); if (uwbSession.getNeedsAppConfigUpdate()) { uwbSession.resetNeedsAppConfigUpdate(); status = mConfigurationManager.setAppConfigurations( uwbSession.getSessionId(), uwbSession.getParams(), uwbSession.getChipId(), getUwbsFiraProtocolVersion(uwbSession.getChipId())); uwbSession.resetAbsoluteInitiationTime(); 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 ALIRO sessions, retrieve the app configs if (uwbSession.getProtocolName().equals( AliroParams.PROTOCOL_NAME)) { Pair statusAndParams = mConfigurationManager.getAppConfigurations( uwbSession.getSessionId(), AliroParams.PROTOCOL_NAME, new byte[0], AliroRangingStartedParams.class, uwbSession.getChipId(), AliroParams.PROTOCOL_VERSION_1_0); if (statusAndParams.first != UwbUciConstants.STATUS_CODE_OK) { Log.e(TAG, "Failed to get ALIRO ranging started params"); } rangingStartedParams = statusAndParams.second; } // For CCC sessions, retrieve the app configs if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME)) { Pair statusAndParams = mConfigurationManager.getAppConfigurations( uwbSession.getSessionId(), CccParams.PROTOCOL_NAME, new byte[0], CccRangingStartedParams.class, uwbSession.getChipId(), CccParams.PROTOCOL_VERSION_1_0); if (statusAndParams.first != UwbUciConstants.STATUS_CODE_OK) { Log.e(TAG, "Failed to get CCC ranging started params"); } rangingStartedParams = statusAndParams.second; } mSessionNotificationManager.onRangingStarted( uwbSession, rangingStartedParams); if (uwbSession.hasNonPrivilegedApp() && !uwbSession.hasNonPrivilegedFgAppOrService()) { Log.i(TAG, "Session " + uwbSession.getSessionId() + " reconfiguring ntf control due to app state change"); uwbSession.reconfigureFiraSessionOnFgStateChange(); } } else { int reasonCode = uwbSession.getLastSessionStatusNtfReasonCode(); status = UwbSessionNotificationHelper.convertUciReasonCodeToUciStatusCode( reasonCode); mSessionNotificationManager.onRangingStartFailedWithUciReasonCode( uwbSession, reasonCode); } } 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 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) { if (uwbSession.getSessionState() == UwbUciConstants.UWB_SESSION_STATE_IDLE) { handleStopRangingParams(uwbSession, true /*systemPolicy*/); return UwbUciConstants.STATUS_CODE_OK; } mSessionNotificationManager.onRangingStopFailed(uwbSession, status); return status; } uwbSession.getWaitObj().blockingWait(); if (uwbSession.getSessionState() == UwbUciConstants.UWB_SESSION_STATE_IDLE) { handleStopRangingParams(uwbSession, triggeredBySystemPolicy); } 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 handleStopRangingParams(UwbSession uwbSession, boolean triggeredBySystemPolicy) { PersistableBundle rangingStoppedParamsBundle = new PersistableBundle(); // For ALIRO sessions, retrieve the app configs if (uwbSession.getProtocolName().equals(AliroParams.PROTOCOL_NAME) && mUwbInjector.getDeviceConfigFacade() .isCccRangingStoppedParamsSendEnabled()) { // Use CCC Flag for ALIRO. Pair statusAndParams = mConfigurationManager.getAppConfigurations( uwbSession.getSessionId(), AliroParams.PROTOCOL_NAME, new byte[0], AliroRangingStoppedParams.class, uwbSession.getChipId(), AliroParams.PROTOCOL_VERSION_1_0); if (statusAndParams.first != UwbUciConstants.STATUS_CODE_OK) { Log.e(TAG, "Failed to get ALIRO ranging stopped params"); } rangingStoppedParamsBundle = statusAndParams.second.toBundle(); } // For CCC sessions, retrieve the app configs if (uwbSession.getProtocolName().equals(CccParams.PROTOCOL_NAME) && mUwbInjector.getDeviceConfigFacade() .isCccRangingStoppedParamsSendEnabled()) { Pair statusAndParams = mConfigurationManager.getAppConfigurations( uwbSession.getSessionId(), CccParams.PROTOCOL_NAME, new byte[0], CccRangingStoppedParams.class, uwbSession.getChipId(), CccParams.PROTOCOL_VERSION_1_0); if (statusAndParams.first != UwbUciConstants.STATUS_CODE_OK) { Log.e(TAG, "Failed to get CCC ranging stopped params"); } rangingStoppedParamsBundle = statusAndParams.second.toBundle(); } int apiReasonCode = triggeredBySystemPolicy ? RangingChangeReason.SYSTEM_POLICY : RangingChangeReason.LOCAL_API; mSessionNotificationManager.onRangingStoppedWithApiReasonCode( uwbSession, apiReasonCode, rangingStoppedParamsBundle); } private void suspendRangingCallbacks(int suspendRangingRounds, int status, UwbSession uwbSession) { if (suspendRangingRounds == FiraParams.SUSPEND_RANGING_ENABLED) { if (status == UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingPaused(uwbSession); } else { mSessionNotificationManager.onRangingPauseFailed(uwbSession, status); } } else if (suspendRangingRounds == FiraParams.SUSPEND_RANGING_DISABLED) { if (status == UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingResumed(uwbSession); } else { mSessionNotificationManager.onRangingResumeFailed(uwbSession, status); } } } private int updateAddRemoveCallbacks(UwbSession uwbSession, UwbMulticastListUpdateStatus multicastList, Integer action, Reconfiguration.Reason reason) { int actionStatus = UwbUciConstants.STATUS_CODE_OK; for (int i = 0; i < multicastList.getNumOfControlee(); i++) { actionStatus = multicastList.getStatus()[i]; final UwbAddress address = multicastList.getControleeUwbAddresses()[i]; if (actionStatus == UwbUciConstants.STATUS_CODE_OK) { if (isMulticastActionAdd(action)) { uwbSession.addControlee(address); mSessionNotificationManager.onControleeAdded( uwbSession, address); } else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) { uwbSession.removeControlee(address); mSessionNotificationManager.onControleeRemoved(uwbSession, address, reason.asControleeRemovedReason()); } } else { if (isMulticastActionAdd(action)) { mSessionNotificationManager.onControleeAddFailed( uwbSession, address, actionStatus); } else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) { mSessionNotificationManager.onControleeRemoveFailed( uwbSession, address, actionStatus, reason.asControleeRemovedReason()); } } } return actionStatus; } private void handleReconfigure(UwbSession uwbSession, @Nullable Params param, Reconfiguration.Reason reason) { if (!(param instanceof FiraRangingReconfigureParams || param instanceof CccRangingReconfiguredParams)) { Log.e(TAG, "Invalid reconfigure params: " + param); mSessionNotificationManager.onRangingReconfigureFailed( uwbSession, UwbUciConstants.STATUS_CODE_INVALID_PARAM); return; } Trace.beginSection("UWB#handleReconfigure"); final FiraRangingReconfigureParams rangingReconfigureParams = (param instanceof FiraRangingReconfigureParams) ? (FiraRangingReconfigureParams) param : null; // TODO(b/211445008): Consolidate to a single uwb thread. FutureTask cmdTask = new FutureTask<>( () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; int ntfStatus = UwbUciConstants.STATUS_CODE_OK; synchronized (uwbSession.getWaitObj()) { // Handle SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_CMD UwbAddress[] addrList = null; Integer action = null; if (rangingReconfigureParams != null) { addrList = rangingReconfigureParams.getAddressList(); action = rangingReconfigureParams.getAction(); } uwbSession.setOperationType(SESSION_RECONFIG_RANGING); // 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 dstAddressList = new ArrayList<>(); for (UwbAddress address : addrList) { dstAddressList.add(getComputedMacAddress(address)); } 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; UwbMulticastListUpdateStatus multicastListUpdateStatus = mNativeUwbManager.controllerMulticastListUpdate( uwbSession.getSessionId(), action, subSessionIdList.length, ArrayUtils.toPrimitive(dstAddressList), subSessionIdList, isV2 ? rangingReconfigureParams .getSubSessionKeyList() : null, uwbSession.getChipId()); status = (multicastListUpdateStatus.getNumOfControlee() == 0) ? UwbUciConstants.STATUS_CODE_OK : UwbUciConstants.STATUS_CODE_FAILED; if (status != UwbUciConstants.STATUS_CODE_OK) { Log.e(TAG, "Unable to update controller multicast list."); int i = 0; UwbAddress[] addresses = multicastListUpdateStatus.getControleeUwbAddresses(); for (int st : multicastListUpdateStatus.getStatus()) { if (st == UwbUciConstants.STATUS_CODE_OK) { if (isMulticastActionAdd(action)) { uwbSession.addControlee(addresses[i]); mSessionNotificationManager.onControleeAdded( uwbSession, addresses[i]); } else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) { uwbSession.removeControlee(addresses[i]); mSessionNotificationManager.onControleeRemoved( uwbSession, addresses[i], reason.asControleeRemovedReason()); } } else { if (isMulticastActionAdd(action)) { mSessionNotificationManager.onControleeAddFailed( uwbSession, addresses[i], st); } else if (action == MULTICAST_LIST_UPDATE_ACTION_DELETE) { mSessionNotificationManager.onControleeRemoveFailed( uwbSession, addresses[i], st, reason.asControleeRemovedReason()); } status = st; } i++; } if (getUwbsFiraProtocolVersion(uwbSession.getChipId()) .getMajor() < FIRA_VERSION_MAJOR_2 || (uwbSession.getSessionState() == UwbUciConstants.UWB_SESSION_STATE_IDLE) || (multicastListUpdateStatus.getNumOfControlee() == subSessionIdList.length)) { return status; } } //Fira 2.0 if (getUwbsFiraProtocolVersion( uwbSession.getChipId()).getMajor() >= FIRA_VERSION_MAJOR_2) { // Action - Add, Status - STATUS_OK if (isMulticastActionAdd(action)) { for (UwbAddress address : addrList) { Log.i(TAG, "address: " + address + " added"); uwbSession.addControlee(address); mSessionNotificationManager.onControleeAdded( uwbSession, address); } } else { if (uwbSession.getSessionState() == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { //wait for NTF for delete action only uwbSession.getWaitObj().blockingWait(); UwbMulticastListUpdateStatus multicastList = uwbSession.getMulticastListUpdateStatus(); if (multicastList == null) { Log.e(TAG, "controller multicast list is empty!"); return status; } ntfStatus = updateAddRemoveCallbacks(uwbSession, multicastList, action, reason); } else { // Action - Delete, State - Idle, Status - STATUS_OK for (UwbAddress address : addrList) { Log.i(TAG, "address: " + address + " removed"); uwbSession.removeControlee(address); mSessionNotificationManager.onControleeRemoved( uwbSession, address, reason.asControleeRemovedReason()); } } } } else { //Fira 1.1 uwbSession.getWaitObj().blockingWait(); UwbMulticastListUpdateStatus multicastList = uwbSession.getMulticastListUpdateStatus(); if (multicastList == null) { Log.e(TAG, "Confirmed controller multicast list is " + "empty!"); return status; } status = updateAddRemoveCallbacks(uwbSession, multicastList, action, reason); } } else { // setAppConfigurations only applies to config changes, // not controlee list changes status = mConfigurationManager.setAppConfigurations( uwbSession.getSessionId(), param, uwbSession.getChipId(), getUwbsFiraProtocolVersion(uwbSession.getChipId())); // send suspendRangingCallbacks only on suspend ranging // reconfigure Integer suspendRangingRounds = rangingReconfigureParams .getSuspendRangingRounds(); if (suspendRangingRounds != null) { suspendRangingCallbacks(suspendRangingRounds, status, uwbSession); } } if (status == UwbUciConstants.STATUS_CODE_OK && ntfStatus == UwbUciConstants.STATUS_CODE_OK) { // only call this if all controlees succeeded otherwise the // fail status cause a onRangingReconfigureFailed later. if (reason != Reconfiguration.Reason.FG_STATE_CHANGE) { 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 (reason != Reconfiguration.Reason.FG_STATE_CHANGE) { 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(SessionHandle sessionHandle, int reason) { Trace.beginSection("UWB#handleDeInitWithReason"); UwbSession uwbSession = getUwbSession(sessionHandle); if (uwbSession == null) { Log.w(TAG, "handleDeInitWithReason(): UWB session not found for sessionHandle: " + sessionHandle); return; } // TODO(b/211445008): Consolidate to a single uwb thread. FutureTask deInitTask = new FutureTask<>( (Callable) () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { status = mNativeUwbManager.deInitSession(uwbSession.getSessionId(), uwbSession.getChipId()); if (status != UwbUciConstants.STATUS_CODE_OK) { return status; } uwbSession.getWaitObj().blockingWait(); } 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"); } 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); // Notify about Session closure after removing it from the SessionTable. Log.i(TAG, "onRangingClosed - status : " + status); mSessionNotificationManager.onRangingClosed(uwbSession, status == STATUS_CODE_OK ? reason : status); 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 sendDataTask = new FutureTask<>((Callable) () -> { 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, uwbSession.getChipId())) { 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. short sequenceNum = uwbSession.getAndIncrementDataSndSequenceNumber(); uwbSession.addSendDataInfo(sequenceNum, sendDataInfo); sendDataStatus = mNativeUwbManager.sendData( uwbSession.getSessionId(), DataTypeConversionUtil.convertShortMacAddressBytesToExtended( sendDataInfo.remoteDeviceAddress.toBytes()), 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; } /** Returns max length of data message possible on a given chip */ private int getMaxMessageSize(String chipId) { GenericSpecificationParams params = mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(chipId); return (params != null && params.getFiraSpecificationParams() != null) ? params.getFiraSpecificationParams().getMaxMessageSize() : 0; } private boolean isValidSendDataInfo(SendDataInfo sendDataInfo, String chipId) { if (sendDataInfo == null || sendDataInfo.data == null || sendDataInfo.remoteDeviceAddress == null) { return false; } if (sendDataInfo.remoteDeviceAddress.size() > UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN) { return false; } final int fixedLength = FiraParams.SESSION_HANDLE_LEN + UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN + FiraParams.SEQUENCE_NUMBER_LENGTH + FiraParams.DATA_MSG_LENGTH; int sendDataInfoLength = fixedLength + sendDataInfo.data.length; int maxMessageSize = getMaxMessageSize(chipId); if (sendDataInfoLength > maxMessageSize) { Log.e(TAG, "SendDataInfo length:" + sendDataInfoLength + " exceeds max supported message size:" + maxMessageSize + " for chipId: " + chipId); return false; } return true; } protected FiraProtocolVersion getUwbsFiraProtocolVersion(String chipId) { UwbDeviceInfoResponse deviceInfo = mUwbInjector.getUwbServiceCore().getCachedDeviceInfoResponse(chipId); if (deviceInfo != null) { return FiraProtocolVersion.fromLEShort((short) deviceInfo.mUciVersion); } // Return a (safe) backward-compatible FiraProtocolVersion if we couldn't retrieve it // from the UWBS. return FiraParams.PROTOCOL_VERSION_1_1; } /** 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 ALIRO_SESSION_PRIORITY = 80; @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 int mRangingRoundUsage; private final IUwbRangingCallbacks mIUwbRangingCallbacks; private final String mProtocolName; private final IBinder mIBinder; private final WaitObj mWaitObj; private final AttributionSource mNonPrivilegedAppInAttributionSource; 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 boolean mNeedsQueryUwbsTimestamp = false; private UwbMulticastListUpdateStatus mMulticastListUpdateStatus; private final int mProfileType; /** * Keeps track of per-controlee error streak timers for ranging sessions with multiple * controlees. */ @VisibleForTesting public Map mMulticastRangingErrorStreakTimerListeners; /** * Per-session error streak timer for all session modes except for two-way ranging. */ private AlarmManager.OnAlarmListener mRangingResultErrorStreakTimerListener; private AlarmManager.OnAlarmListener mNonPrivilegedBgAppTimerListener; private int mOperationType = OPERATION_TYPE_INIT_SESSION; private final String mChipId; private boolean mHasNonPrivilegedFgAppOrService = false; private long mRangingErrorStreakTimeoutMs = RANGING_RESULT_ERROR_NO_TIMEOUT; // Use a Map> 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> mReceivedDataInfoMap; private IPoseSource mPoseSource; // Application data repetition count private int mDataRepetitionCount; // Hybrid session private int mDeviceType; private int mScheduleMode; // Store the UCI sequence number for the next Data packet (to be sent to UWBS). private short mDataSndSequenceNumber; // Store a Map, for every Data packet (sent to UWBS). It's // used when the corresponding DataTransferStatusNtf is received (from UWBS). private final ConcurrentHashMap mSendDataInfoMap; // Whether data delivery permission check is needed for the ranging session. private boolean mDataDeliveryPermissionCheckNeeded = true; // reasonCode from the last received SESSION_STATUS_NTF for this session. private int mLastSessionStatusNtfReasonCode = -1; /** * Acquire to synchronized changes in controlee count * Guards mControlees and mControleesPendingDisconnection */ private final Object mControleeCountLock = new Object(); /** * Keeps track of all controlees in the session. */ public Map mControlees; /** Number of controlees pending disconnection due to error streak timeout */ @GuardedBy("mControleeCountLock") private final Set mControleesPendingDisconnection; // Keep track of RF Test start session params private RfTestStartSessionParams mRfTestStartSessionParams = null; 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.mParams = params; this.mWaitObj = new WaitObj(); this.mProfileType = convertProtolNameToProfileType(protocolName); this.mChipId = chipId; this.mNonPrivilegedAppInAttributionSource = mUwbInjector.getAnyNonPrivilegedAppInAttributionSource(mAttributionSource); this.mStackSessionPriority = calculateSessionPriority(); this.mControlees = new ConcurrentHashMap<>(); this.mControleesPendingDisconnection = Sets.newConcurrentHashSet(); if (params instanceof FiraOpenSessionParams) { FiraOpenSessionParams firaParams = (FiraOpenSessionParams) params; this.mRangingRoundUsage = firaParams.getRangingRoundUsage(); // 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. for (UwbAddress address : firaParams.getDestAddressList()) { mControlees.put(address, new UwbControlee(address, createFilterEngine(), mUwbInjector)); } } 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.mDataRepetitionCount = firaParams.getDataRepetitionCount(); this.mDeviceType = firaParams.getDeviceType(); this.mScheduleMode = firaParams.getScheduledMode(); } else { this.mRangingRoundUsage = -1; this.mDataRepetitionCount = 0; this.mDeviceType = -1; this.mScheduleMode = -1; } this.mReceivedDataInfoMap = new ConcurrentHashMap<>(); this.mDataSndSequenceNumber = 0; this.mSendDataInfoMap = new ConcurrentHashMap<>(); this.mMulticastRangingErrorStreakTimerListeners = 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(AliroParams.PROTOCOL_NAME)) { return ALIRO_SESSION_PRIORITY; } if (mProtocolName.equals(CccParams.PROTOCOL_NAME)) { return CCC_SESSION_PRIORITY; } if (mNonPrivilegedAppInAttributionSource == null) { return SYSTEM_APP_SESSION_PRIORITY; } boolean isFgAppOrService = mUwbInjector.isForegroundAppOrService( mNonPrivilegedAppInAttributionSource.getUid(), mNonPrivilegedAppInAttributionSource.getPackageName()); if (isFgAppOrService) { return FG_SESSION_PRIORITY; } return BG_SESSION_PRIORITY; } /** * 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() { return mNonPrivilegedAppInAttributionSource; } /** * Check the attribution source chain to check if there are any 3p apps. * @return true if 3p app found in attribution source chain. */ public boolean hasNonPrivilegedApp() { return mNonPrivilegedAppInAttributionSource != null; } /** * Gets the list of controlees active under this session. */ public List getControleeList() { return new ArrayList<>(mControlees.values()); } /** * Must be public for testing. * @return The list of controlee addresses that have active ranging error streak timers. */ public List getControleesWithOngoingRangingErrorStreak() { return new ArrayList<>(mMulticastRangingErrorStreakTimerListeners.keySet()); } /** * 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 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 getAllReceivedDataInfo(long macAddress) { SortedMap innerMap = mReceivedDataInfoMap.get(macAddress); if (innerMap == null) { // No stored ReceivedDataInfo(s) for the address. return List.of(); } List receivedDataInfoList = new ArrayList<>(innerMap.values()); innerMap.clear(); return receivedDataInfoList; } private void clearReceivedDataInfo() { for (long macAddress : getRemoteMacAddressList()) { SortedMap 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 short 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 (mControlees.containsKey(address)) { return; } synchronized (mControleeCountLock) { mControlees.put(address, 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) { if (mControlees == null || mControlees.isEmpty()) { Log.d(TAG, "Controlee list is null or empty"); return null; } UwbControlee result = mControlees.get(address); 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 (!mControlees.containsKey(address)) { Log.w(TAG, "Attempted to remove controlee with address " + address + " that is not in the session."); return; } Log.d(TAG, "Removing controlee."); stopRangingResultErrorStreakTimerIfSet(address); mControlees.get(address).close(); synchronized (mControleeCountLock) { mControlees.remove(address); mControleesPendingDisconnection.remove(address); } } public AttributionSource getAttributionSource() { return this.mAttributionSource; } public int getSessionId() { return this.mSessionId; } public byte getSessionType() { return this.mSessionType; } public int getRangingRoundUsage() { return this.mRangingRoundUsage; } public String getChipId() { return this.mChipId; } public SessionHandle getSessionHandle() { return this.mSessionHandle; } public Params getParams() { return this.mParams; } public int getDataRepetitionCount() { return mDataRepetitionCount; } public int getDeviceType() { return mDeviceType; } public int getScheduledMode() { return mScheduleMode; } public void updateAliroParamsOnStart(AliroStartRangingParams rangingStartParams) { setNeedsQueryUwbsTimestamp(rangingStartParams); // Need to update the RAN multiplier and initiation time // from the AliroStartRangingParams for CCC session. AliroOpenRangingParams newParams = new AliroOpenRangingParams.Builder((AliroOpenRangingParams) mParams) .setRanMultiplier(rangingStartParams.getRanMultiplier()) .setInitiationTimeMs(rangingStartParams.getInitiationTimeMs()) .setAbsoluteInitiationTimeUs(rangingStartParams .getAbsoluteInitiationTimeUs()) .setStsIndex(rangingStartParams.getStsIndex()) .build(); this.mParams = newParams; this.mNeedsAppConfigUpdate = true; } public void updateCccParamsOnStart(CccStartRangingParams rangingStartParams) { setNeedsQueryUwbsTimestamp(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()) .setAbsoluteInitiationTimeUs(rangingStartParams .getAbsoluteInitiationTimeUs()) .setStsIndex(rangingStartParams.getStsIndex()) .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; } setNeedsQueryUwbsTimestamp(null /* rangingStartParams */); } /** * Sets {@code mNeedsQueryUwbsTimestamp} to {@code true}, if the UWBS Timestamp needs to be * fetched from the UWBS controller (for computing an absolute UWB initiation time). */ public void setNeedsQueryUwbsTimestamp(@Nullable Params startRangingParams) { // When the UWBS supports Fira 2.0+, the application has configured a relative UWB // initation time, but not configured an absolute UWB initiation time, we must fetch // the UWBS timestamp (to compute the absolute UWB initiation time). if (getUwbsFiraProtocolVersion(mChipId).getMajor() >= 2) { if (mParams instanceof FiraOpenSessionParams) { FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams; if (firaOpenSessionParams.getInitiationTime() != 0 && firaOpenSessionParams.getAbsoluteInitiationTime() == 0) { this.mNeedsQueryUwbsTimestamp = true; } } else if (mParams instanceof CccOpenRangingParams && mUwbInjector.getDeviceConfigFacade() .isCccAbsoluteUwbInitiationTimeEnabled()) { // When CccStartRangingParams is present; we check only for it's fields, // since its values overrides the earlier CccOpenRangingParams. if (startRangingParams != null && startRangingParams instanceof CccStartRangingParams) { CccStartRangingParams cccStartRangingParams = (CccStartRangingParams) startRangingParams; if (cccStartRangingParams.getInitiationTimeMs() != 0 && cccStartRangingParams.getAbsoluteInitiationTimeUs() == 0) { this.mNeedsQueryUwbsTimestamp = true; } } else { CccOpenRangingParams cccOpenRangingParams = (CccOpenRangingParams) mParams; if (cccOpenRangingParams.getInitiationTimeMs() != 0 && cccOpenRangingParams.getAbsoluteInitiationTimeUs() == 0) { this.mNeedsQueryUwbsTimestamp = true; } } } else if (mParams instanceof AliroOpenRangingParams && mUwbInjector.getDeviceConfigFacade() .isCccAbsoluteUwbInitiationTimeEnabled()) { // Re-use CCC flag for ALIRO // When AliroStartRangingParams is present; we check only for it's fields, // since its values overrides the earlier AliroOpenRangingParams. if (startRangingParams != null && startRangingParams instanceof AliroStartRangingParams) { AliroStartRangingParams aliroStartRangingParams = (AliroStartRangingParams) startRangingParams; if (aliroStartRangingParams.getInitiationTimeMs() != 0 && aliroStartRangingParams.getAbsoluteInitiationTimeUs() == 0) { this.mNeedsQueryUwbsTimestamp = true; } } else { AliroOpenRangingParams aliroOpenRangingParams = (AliroOpenRangingParams) mParams; if (aliroOpenRangingParams.getInitiationTimeMs() != 0 && aliroOpenRangingParams.getAbsoluteInitiationTimeUs() == 0) { this.mNeedsQueryUwbsTimestamp = true; } } } } } /** * Computes an absolute UWB initiation time, if it's needed. */ public void setAbsoluteInitiationTimeIfNeeded() { if (this.mNeedsQueryUwbsTimestamp) { // Query the UWBS timestamp and add the relative initiation time // stored in the FiraOpenSessionParams, to get the absolute // initiation time to be configured. long uwbsTimestamp = mUwbInjector.getUwbServiceCore().queryUwbsTimestampMicros(); computeAbsoluteInitiationTime(uwbsTimestamp); } } /** * For Fira 2.0+ controller devices, replace the reference Session's SessionID with * its SessionToken, in the SessionTimeBase AppConfig parameter. */ public void updateFiraParamsForSessionTimeBase(int sessionToken) { if (mParams instanceof FiraOpenSessionParams) { FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams; int deviceRole = firaOpenSessionParams.getDeviceRole(); if (deviceRole == FiraParams.RANGING_DEVICE_TYPE_CONTROLLER && UwbUtil.isBitSet(firaOpenSessionParams.getReferenceTimeBase(), FiraParams.SESSION_TIME_BASE_REFERENCE_FEATURE_ENABLED)) { this.mParams = ((FiraOpenSessionParams) mParams).toBuilder().setSessionTimeBase( firaOpenSessionParams.getReferenceTimeBase(), sessionToken, firaOpenSessionParams.getSessionOffsetInMicroSeconds()) .build(); } } } /** * Compute absolute initiation time, by doing a sum of the UWBS Timestamp (in micro-seconds) * and the relative initiation time (in milli-seconds). This method should be * called only for FiRa UCI ProtocolVersion >= 2.0 devices. */ public void computeAbsoluteInitiationTime(long uwbsTimestamp) { if (this.mNeedsQueryUwbsTimestamp) { if (mParams instanceof FiraOpenSessionParams) { FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams; this.mParams = ((FiraOpenSessionParams) mParams).toBuilder() .setAbsoluteInitiationTime(uwbsTimestamp + (firaOpenSessionParams.getInitiationTime() * 1000)) .build(); } else if (mParams instanceof CccOpenRangingParams) { CccOpenRangingParams cccOpenRangingParams = (CccOpenRangingParams) mParams; this.mParams = ((CccOpenRangingParams) mParams).toBuilder() .setAbsoluteInitiationTimeUs(uwbsTimestamp + (cccOpenRangingParams.getInitiationTimeMs() * 1000)) .build(); } else if (mParams instanceof AliroOpenRangingParams) { AliroOpenRangingParams aliroOpenRangingParams = (AliroOpenRangingParams) mParams; this.mParams = ((AliroOpenRangingParams) mParams).toBuilder() .setAbsoluteInitiationTimeUs(uwbsTimestamp + (aliroOpenRangingParams.getInitiationTimeMs() * 1000)) .build(); } this.mNeedsAppConfigUpdate = true; } } /** * Reset the computed absolute initiation time, only when it was computed and set by this * class (it should not be reset when it was provided by the application). */ public void resetAbsoluteInitiationTime() { if (this.mNeedsQueryUwbsTimestamp) { if (mParams instanceof FiraOpenSessionParams) { // Reset the absolute Initiation time, so that it's re-computed if start // ranging is called in the future for this UWB session. this.mParams = ((FiraOpenSessionParams) mParams).toBuilder() .setAbsoluteInitiationTime(0) .build(); } else if (mParams instanceof CccOpenRangingParams) { this.mParams = ((CccOpenRangingParams) mParams).toBuilder() .setAbsoluteInitiationTimeUs(0) .build(); } else if (mParams instanceof AliroOpenRangingParams) { this.mParams = ((AliroOpenRangingParams) mParams).toBuilder() .setAbsoluteInitiationTimeUs(0) .build(); } this.mNeedsQueryUwbsTimestamp = false; } } 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(); } // Return the Ranging Interval (Fira 2.0: Ranging Duration) in milliseconds. public void updateCccParamsOnReconfigure(CccRangingReconfiguredParams reconfigureParams) { // Need to update the reconfigure params from the CccRangingReconfiguredParams for // Ccc session. CccOpenRangingParams.Builder newParamsBuilder = new CccOpenRangingParams.Builder((CccOpenRangingParams) mParams); 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 RfTestStartSessionParams getRfTestStartSessionParams() { return this.mRfTestStartSessionParams; } public void setRfTestStartSessionParams(RfTestStartSessionParams params) { this.mRfTestStartSessionParams = params; } 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 boolean getNeedsQueryUwbsTimestamp() { return this.mNeedsQueryUwbsTimestamp; } public Set getRemoteMacAddressList() { return mReceivedDataInfoMap.keySet(); } public boolean isDataDeliveryPermissionCheckNeeded() { return mDataDeliveryPermissionCheckNeeded; } public void setDataDeliveryPermissionCheckNeeded(boolean permissionCheckNeeded) { mDataDeliveryPermissionCheckNeeded = permissionCheckNeeded; } 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 if (protocolName.equals(AliroParams.PROTOCOL_NAME)) { return UwbStatsLog.UWB_SESSION_INITIATED__PROFILE__ALIRO; } 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 hasNonPrivilegedFgAppOrService() { return mHasNonPrivilegedFgAppOrService; } public void setHasNonPrivilegedFgAppOrService(boolean hasNonPrivilegedFgAppOrService) { mHasNonPrivilegedFgAppOrService = hasNonPrivilegedFgAppOrService; } /** * Starts a timer to detect if the error streak is longer than * {@link UwbSession#mRangingErrorStreakTimeoutMs }. The session is ended when the alarm * triggers. */ 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"); if (getSessionState() == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */); } else { Log.i(TAG, "Session is not in an active state"); } }; 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; } } private void removeControleeDueToErrorStreakTimeout(UwbAddress address) { reconfigureInternal(mSessionHandle, new FiraRangingReconfigureParams.Builder() .setAction(MULTICAST_LIST_UPDATE_ACTION_DELETE) .setAddressList(new UwbAddress[] { address }) .setSubSessionIdList(new int[] { 0 }) .build(), Reconfiguration.Reason.LOST_CONNECTION); } /** * Same as {@link UwbSession#startRangingResultErrorStreakTimerIfNotSet()}, except * starts multiple timers on a per-controlee basis for two-way ranging sessions. The * controlee will be removed from the session when the alarm triggers. The session is ended * only when the last controlee is removed. * * @param address : Address of the controlee to associate the timer with. */ public void startRangingResultErrorStreakTimerIfNotSet(UwbAddress address) { if (!mControlees.containsKey(address)) { Log.w(TAG, "Attempted to start error timer for controlee " + address + " that is not in the session."); return; } if (mMulticastRangingErrorStreakTimerListeners.containsKey(address)) { return; } Log.v(TAG, "Starting error timer for controlee " + address + " for " + mRangingErrorStreakTimeoutMs + " ms."); AlarmManager.OnAlarmListener onAlarm = () -> { Log.w(TAG, "Continuous errors or no ranging results detected from controlee " + address + " for " + mRangingErrorStreakTimeoutMs + " ms."); synchronized (mControleeCountLock) { if (mControlees.size() - mControleesPendingDisconnection.size() == 1) { Log.w(TAG, "Last controlee in session has disconnected, stopping session"); if (getSessionState() == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */); } else { Log.i(TAG, "Session is not in an active state"); } } else { mControleesPendingDisconnection.add(address); removeControleeDueToErrorStreakTimeout(address); } } }; mMulticastRangingErrorStreakTimerListeners.put(address, onAlarm); mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mUwbInjector.getElapsedSinceBootMillis() + mRangingErrorStreakTimeoutMs, RANGING_RESULT_ERROR_STREAK_TIMER_TAG, onAlarm, mEventTask); } /** * Stops the timer associated with a controlee, if set. * This function will never stop the session. * * @param address : Address of the controlee whose timer to stop. */ public void stopRangingResultErrorStreakTimerIfSet(UwbAddress address) { if (!mControlees.containsKey(address)) { Log.w(TAG, "Attempted to stop error timer for controlee " + address + "that is not in the session"); return; } if (!mMulticastRangingErrorStreakTimerListeners.containsKey(address)) { return; } mAlarmManager.cancel(mMulticastRangingErrorStreakTimerListeners.get(address)); mMulticastRangingErrorStreakTimerListeners.remove(address); } /** * 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"); if (getSessionState() == UwbUciConstants.UWB_SESSION_STATE_ACTIVE) { stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */); } else { Log.i(TAG, "Session is not in an active state"); } }; 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(); for (UwbAddress address : getControleesWithOngoingRangingErrorStreak()) { stopRangingResultErrorStreakTimerIfSet(address); } stopNonPrivilegedBgAppTimerIfSet(); } public void reconfigureFiraSessionOnFgStateChange() { // Reconfigure the session to change notification control when the app transitions // from fg to bg and vice versa. FiraRangingReconfigureParams.Builder builder = new FiraRangingReconfigureParams.Builder(); // If app is in fg, use the configured ntf control, else disable. if (mHasNonPrivilegedFgAppOrService) { FiraOpenSessionParams params = (FiraOpenSessionParams) mParams; int rangeDataNtfConfig = params.getRangeDataNtfConfig(); builder.setRangeDataNtfConfig(rangeDataNtfConfig); if (rangeDataNtfConfig == RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG || rangeDataNtfConfig == RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_EDGE_TRIG) { builder .setRangeDataProximityNear(params.getRangeDataNtfProximityNear()) .setRangeDataProximityFar(params.getRangeDataNtfProximityFar()); } } else { builder.setRangeDataNtfConfig(FiraParams.RANGE_DATA_NTF_CONFIG_DISABLE); } FiraRangingReconfigureParams reconfigureParams = builder.build(); reconfigureInternal(mSessionHandle, reconfigureParams, Reconfiguration.Reason.FG_STATE_CHANGE); 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 (!mHasNonPrivilegedFgAppOrService) { 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; } public int getLastSessionStatusNtfReasonCode() { return mLastSessionStatusNtfReasonCode; } public void setLastSessionStatusNtfReasonCode(int lastSessionStatusNtfReasonCode) { mLastSessionStatusNtfReasonCode = lastSessionStatusNtfReasonCode; } /** 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/ALIRO Session counts currently are " + getFiraSessionCount() + "/" + getCccSessionCount() + "/" + getAliroSessionCount()); } else { Log.e(TAG, "binderDied : sessionDeinit Failure because of NativeSessionDeinit " + "Error"); } } } /** * Cleans up resources held by this object. */ public void close() { if (this.mAcquiredDefaultPose) { for (UwbControlee controlee : mControlees.values()) { controlee.close(); } synchronized (mControleeCountLock) { mControlees.clear(); mControleesPendingDisconnection.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 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 ----"); } private static byte[] getComputedMacAddress(UwbAddress address) { if (!SdkLevel.isAtLeastU()) { return TlvUtil.getReverseBytes(address.toBytes()); } return address.toBytes(); } }