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