/* * Copyright 2020 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 android.uwb; import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.Binder; import android.os.Build; import android.os.CancellationSignal; import android.os.PersistableBundle; import android.os.RemoteException; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * This class provides a way to perform Ultra Wideband (UWB) operations such as querying the * device's capabilities and determining the distance and angle between the local device and a * remote device. * *
To get a {@link UwbManager}, call the Context.getSystemService(UwbManager.class)
.
*
*
Note: This API surface uses opaque {@link PersistableBundle} params. These params are to be
* created using the provided UWB support library. The support library is present in this
* location on AOSP: packages/modules/Uwb/service/support_lib/
*
* @hide
*/
@SystemApi
@SystemService(Context.UWB_SERVICE)
public final class UwbManager {
private static final String TAG = "UwbManager";
private final Context mContext;
private final IUwbAdapter mUwbAdapter;
private final AdapterStateListener mAdapterStateListener;
private final RangingManager mRangingManager;
private final UwbVendorUciCallbackListener mUwbVendorUciCallbackListener;
private final UwbOemExtensionCallbackListener mUwbOemExtensionCallbackListener;
/**
* Interface for receiving UWB adapter state changes
*/
public interface AdapterStateCallback {
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
STATE_CHANGED_REASON_SESSION_STARTED,
STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED,
STATE_CHANGED_REASON_SYSTEM_POLICY,
STATE_CHANGED_REASON_SYSTEM_BOOT,
STATE_CHANGED_REASON_ERROR_UNKNOWN,
STATE_CHANGED_REASON_SYSTEM_REGULATION})
@interface StateChangedReason {}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
STATE_ENABLED_INACTIVE,
STATE_ENABLED_ACTIVE,
STATE_DISABLED,
STATE_ENABLED_HW_IDLE})
@interface State {}
/**
* Indicates that the state change was due to opening of first UWB session
*/
int STATE_CHANGED_REASON_SESSION_STARTED = 0;
/**
* Indicates that the state change was due to closure of all UWB sessions
*/
int STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED = 1;
/**
* Indicates that the state change was due to changes in system policy
*/
int STATE_CHANGED_REASON_SYSTEM_POLICY = 2;
/**
* Indicates that the current state is due to a system boot
*/
int STATE_CHANGED_REASON_SYSTEM_BOOT = 3;
/**
* Indicates that the state change was due to some unknown error
*/
int STATE_CHANGED_REASON_ERROR_UNKNOWN = 4;
/**
* Indicates that the state change is due to a system regulation.
*/
int STATE_CHANGED_REASON_SYSTEM_REGULATION = 5;
/**
* Indicates that UWB is disabled on device
*/
int STATE_DISABLED = 0;
/**
* Indicates that UWB is enabled on device but has no active ranging sessions
*/
int STATE_ENABLED_INACTIVE = 1;
/**
* Indicates that UWB is enabled and has active ranging session
*/
int STATE_ENABLED_ACTIVE = 2;
/**
* The state when UWB is enabled by user but the hardware is not enabled since no clients
* have requested for it.
* Only sent if the device supports {@link #isUwbHwIdleTurnOffEnabled()} feature.
*/
@FlaggedApi("com.android.uwb.flags.hw_state")
int STATE_ENABLED_HW_IDLE = 3;
/**
* Invoked when underlying UWB adapter's state is changed
*
Invoked with the adapter's current state after registering an * {@link AdapterStateCallback} using * {@link UwbManager#registerAdapterStateCallback(Executor, AdapterStateCallback)}. * *
Possible reasons for the state to change are * {@link #STATE_CHANGED_REASON_SESSION_STARTED}, * {@link #STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED}, * {@link #STATE_CHANGED_REASON_SYSTEM_POLICY}, * {@link #STATE_CHANGED_REASON_SYSTEM_BOOT}, * {@link #STATE_CHANGED_REASON_ERROR_UNKNOWN}. * {@link #STATE_CHANGED_REASON_SYSTEM_REGULATION}. * *
Possible values for the UWB state are
* {@link #STATE_ENABLED_INACTIVE},
* {@link #STATE_ENABLED_ACTIVE},
* {@link #STATE_DISABLED}.
*
* @param state the UWB state; inactive, active or disabled
* @param reason the reason for the state change
*/
void onStateChanged(@State int state, @StateChangedReason int reason);
}
/**
* Abstract class for receiving ADF provisioning state.
* Should be extended by applications and set when calling
* {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
* AdfProvisionStateCallback)}
*/
public abstract static class AdfProvisionStateCallback {
private final AdfProvisionStateCallbackProxy mAdfProvisionStateCallbackProxy;
public AdfProvisionStateCallback() {
mAdfProvisionStateCallbackProxy = new AdfProvisionStateCallbackProxy();
}
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
REASON_INVALID_OID,
REASON_SE_FAILURE,
REASON_UNKNOWN
})
@interface Reason { }
/**
* Indicates that the OID provided was not valid.
*/
public static final int REASON_INVALID_OID = 1;
/**
* Indicates that there was some SE (secure element) failure while provisioning.
*/
public static final int REASON_SE_FAILURE = 2;
/**
* No known reason for the failure.
*/
public static final int REASON_UNKNOWN = 3;
/**
* Invoked when {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
* AdfProvisionStateCallback)} is successful.
*
* @param params protocol specific params that provide the caller with provisioning info
**/
public abstract void onProfileAdfsProvisioned(@NonNull PersistableBundle params);
/**
* Invoked when {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
* AdfProvisionStateCallback)} fails.
*
* @param reason Reason for failure
* @param params protocol specific parameters to indicate failure reason
*/
public abstract void onProfileAdfsProvisionFailed(
@Reason int reason, @NonNull PersistableBundle params);
/*package*/
@NonNull
AdfProvisionStateCallbackProxy getProxy() {
return mAdfProvisionStateCallbackProxy;
}
private static class AdfProvisionStateCallbackProxy extends
IUwbAdfProvisionStateCallbacks.Stub {
private final Object mLock = new Object();
@Nullable
@GuardedBy("mLock")
private Executor mExecutor;
@Nullable
@GuardedBy("mLock")
private AdfProvisionStateCallback mCallback;
AdfProvisionStateCallbackProxy() {
mCallback = null;
mExecutor = null;
}
/*package*/ void initProxy(@NonNull Executor executor,
@NonNull AdfProvisionStateCallback callback) {
synchronized (mLock) {
mExecutor = executor;
mCallback = callback;
}
}
/*package*/ void cleanUpProxy() {
synchronized (mLock) {
mExecutor = null;
mCallback = null;
}
}
@Override
public void onProfileAdfsProvisioned(@NonNull PersistableBundle params) {
Log.v(TAG, "AdfProvisionStateCallbackProxy: onProfileAdfsProvisioned : " + params);
AdfProvisionStateCallback callback;
Executor executor;
synchronized (mLock) {
executor = mExecutor;
callback = mCallback;
}
if (callback == null || executor == null) {
return;
}
Binder.clearCallingIdentity();
executor.execute(() -> callback.onProfileAdfsProvisioned(params));
cleanUpProxy();
}
@Override
public void onProfileAdfsProvisionFailed(@AdfProvisionStateCallback.Reason int reason,
@NonNull PersistableBundle params) {
Log.v(TAG, "AdfProvisionStateCallbackProxy: onProfileAdfsProvisionFailed : "
+ reason + ", " + params);
AdfProvisionStateCallback callback;
Executor executor;
synchronized (mLock) {
executor = mExecutor;
callback = mCallback;
}
if (callback == null || executor == null) {
return;
}
Binder.clearCallingIdentity();
executor.execute(() -> callback.onProfileAdfsProvisionFailed(reason, params));
cleanUpProxy();
}
}
}
/**
* Interface for receiving vendor UCI responses and notifications.
*/
public interface UwbVendorUciCallback {
/**
* Invoked when a vendor specific UCI response is received.
*
* @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
* the UCI specification.
* @param oid Opcode ID of the command. This is left to the OEM / vendor to decide.
* @param payload containing vendor Uci message payload.
*/
void onVendorUciResponse(
@IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload);
/**
* Invoked when a vendor specific UCI notification is received.
*
* @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
* the UCI specification.
* @param oid Opcode ID of the command. This is left to the OEM / vendor to decide.
* @param payload containing vendor Uci message payload.
*/
void onVendorUciNotification(
@IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload);
}
/**
* @hide
* Vendor configuration successful for the session
*/
public static final int VENDOR_SET_SESSION_CONFIGURATION_SUCCESS = 0;
/**
* @hide
* Failure to set vendor configuration for the session
*/
public static final int VENDOR_SET_SESSION_CONFIGURATION_FAILURE = 1;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
VENDOR_SET_SESSION_CONFIGURATION_SUCCESS,
VENDOR_SET_SESSION_CONFIGURATION_FAILURE,
})
@interface VendorConfigStatus {}
/**
* Interface for Oem extensions on ongoing session
*/
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public interface UwbOemExtensionCallback {
/**
* Invoked when session status changes.
*
* @param sessionStatusBundle session related info
*/
void onSessionStatusNotificationReceived(@NonNull PersistableBundle sessionStatusBundle);
/**
* Invoked when DeviceStatusNotification is received from UCI.
*
* @param deviceStatusBundle device state
*/
void onDeviceStatusNotificationReceived(@NonNull PersistableBundle deviceStatusBundle);
/**
* Invoked when session configuration is complete.
*
* @param openSessionBundle Session Params
* @return Error code
*/
@NonNull @VendorConfigStatus int onSessionConfigurationComplete(
@NonNull PersistableBundle openSessionBundle);
/**
* Invoked when ranging report is generated.
*
* @param rangingReport ranging report generated
* @return Oem modified ranging report
*/
@NonNull RangingReport onRangingReportReceived(
@NonNull RangingReport rangingReport);
/**
* Invoked to check pointed target decision by Oem.
*
* @param pointedTargetBundle pointed target params
* @return Oem pointed status
*/
boolean onCheckPointedTarget(@NonNull PersistableBundle pointedTargetBundle);
}
/**
* Use Context.getSystemService(UwbManager.class)
to get an instance.
*
* @param ctx Context of the client.
* @param adapter an instance of an {@link android.uwb.IUwbAdapter}
* @hide
*/
public UwbManager(@NonNull Context ctx, @NonNull IUwbAdapter adapter) {
mContext = ctx;
mUwbAdapter = adapter;
mAdapterStateListener = new AdapterStateListener(adapter);
mRangingManager = new RangingManager(adapter);
mUwbVendorUciCallbackListener = new UwbVendorUciCallbackListener(adapter);
mUwbOemExtensionCallbackListener = new UwbOemExtensionCallbackListener(adapter);
}
/**
* Register an {@link AdapterStateCallback} to listen for UWB adapter state changes
*
The provided callback will be invoked by the given {@link Executor}. * *
When first registering a callback, the callbacks's * {@link AdapterStateCallback#onStateChanged(int, int)} is immediately invoked to indicate * the current state of the underlying UWB adapter with the most recent * {@link AdapterStateCallback.StateChangedReason} that caused the change. * * @param executor an {@link Executor} to execute given callback * @param callback user implementation of the {@link AdapterStateCallback} */ @RequiresPermission(permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor, @NonNull AdapterStateCallback callback) { mAdapterStateListener.register(executor, callback); } /** * Unregister the specified {@link AdapterStateCallback} *
The same {@link AdapterStateCallback} object used when calling * {@link #registerAdapterStateCallback(Executor, AdapterStateCallback)} must be used. * *
Callbacks are automatically unregistered when application process goes away * * @param callback user implementation of the {@link AdapterStateCallback} */ @RequiresPermission(permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) { mAdapterStateListener.unregister(callback); } /** * Register an {@link UwbVendorUciCallback} to listen for UWB vendor responses and notifications *
The provided callback will be invoked by the given {@link Executor}. * *
When first registering a callback, the callbacks's * {@link UwbVendorUciCallback#onVendorUciCallBack(byte[])} is immediately invoked to * notify the vendor notification. * * @param executor an {@link Executor} to execute given callback * @param callback user implementation of the {@link UwbVendorUciCallback} */ @RequiresPermission(permission.UWB_PRIVILEGED) public void registerUwbVendorUciCallback(@NonNull @CallbackExecutor Executor executor, @NonNull UwbVendorUciCallback callback) { mUwbVendorUciCallbackListener.register(executor, callback); } /** * Unregister the specified {@link UwbVendorUciCallback} * *
The same {@link UwbVendorUciCallback} object used when calling * {@link #registerUwbVendorUciCallback(Executor, UwbVendorUciCallback)} must be used. * *
Callbacks are automatically unregistered when application process goes away * * @param callback user implementation of the {@link UwbVendorUciCallback} */ public void unregisterUwbVendorUciCallback(@NonNull UwbVendorUciCallback callback) { mUwbVendorUciCallbackListener.unregister(callback); } /** * Register an {@link UwbOemExtensionCallback} to listen for UWB oem extension callbacks *
The provided callback will be invoked by the given {@link Executor}. * * @param executor an {@link Executor} to execute given callback * @param callback oem implementation of {@link UwbOemExtensionCallback} */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @RequiresPermission(permission.UWB_PRIVILEGED) public void registerUwbOemExtensionCallback(@NonNull @CallbackExecutor Executor executor, @NonNull UwbOemExtensionCallback callback) { mUwbOemExtensionCallbackListener.register(executor, callback); } /** * Unregister the specified {@link UwbOemExtensionCallback} * *
The same {@link UwbOemExtensionCallback} object used when calling * {@link #registerUwbOemExtensionCallback(Executor, UwbOemExtensionCallback)} must be used. * *
Callbacks are automatically unregistered when an application process goes away * * @param callback oem implementation of {@link UwbOemExtensionCallback} */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @RequiresPermission(permission.UWB_PRIVILEGED) public void unregisterUwbOemExtensionCallback(@NonNull UwbOemExtensionCallback callback) { mUwbOemExtensionCallbackListener.unregister(callback); } /** * Get a {@link PersistableBundle} with the supported UWB protocols and parameters. *
The {@link PersistableBundle} should be parsed using a support library * *
Android reserves the '^android.*' namespace
* * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters */ @NonNull @RequiresPermission(permission.UWB_PRIVILEGED) public PersistableBundle getSpecificationInfo() { return getSpecificationInfoInternal(/* chipId= */ null); } /** * Get a {@link PersistableBundle} with the supported UWB protocols and parameters. * * @see #getSpecificationInfo() if you don't need multi-HAL support * * @param chipId identifier of UWB chip for multi-HAL devices * * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters */ // TODO(b/205614701): Add documentation about how to find the relevant chipId @NonNull @RequiresPermission(permission.UWB_PRIVILEGED) public PersistableBundle getSpecificationInfo(@NonNull String chipId) { checkNotNull(chipId); return getSpecificationInfoInternal(chipId); } private PersistableBundle getSpecificationInfoInternal(String chipId) { try { return mUwbAdapter.getSpecificationInfo(chipId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Get uwbs timestamp in micros. * * @return uwb device timestamp in micros. */ @NonNull @RequiresPermission(permission.UWB_PRIVILEGED) @FlaggedApi("com.android.uwb.flags.query_timestamp_micros") public long queryUwbsTimestampMicros() { try { return mUwbAdapter.queryUwbsTimestampMicros(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Get the timestamp resolution for events in nanoseconds *This value defines the maximum error of all timestamps for events reported to * {@link RangingSession.Callback}. * * @return the timestamp resolution in nanoseconds */ @SuppressLint("MethodNameUnits") @RequiresPermission(permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos() { return elapsedRealtimeResolutionNanosInternal(/* chipId= */ null); } /** * Get the timestamp resolution for events in nanoseconds * * @see #elapsedRealtimeResolutionNanos() if you don't need multi-HAL support * * @param chipId identifier of UWB chip for multi-HAL devices * * @return the timestamp resolution in nanoseconds */ @SuppressLint("MethodNameUnits") @RequiresPermission(permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos(@NonNull String chipId) { checkNotNull(chipId); return elapsedRealtimeResolutionNanosInternal(chipId); } private long elapsedRealtimeResolutionNanosInternal(String chipId) { try { return mUwbAdapter.getTimestampResolutionNanos(chipId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Open a {@link RangingSession} with the given parameters *
The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a * {@link RangingSession} object used to control ranging when the session is successfully * opened. * * if this session uses FIRA defined profile (not custom profile), this triggers: * - OOB discovery using service UUID * - OOB connection establishment after discovery for session params * negotiation. * - Secure element interactions needed for dynamic STS based session establishment. * - Setup the UWB session based on the parameters negotiated via OOB. * - Note: The OOB flow requires additional BLE Permissions * {permission.BLUETOOTH_ADVERTISE/permission.BLUETOOTH_SCAN * and permission.BLUETOOTH_CONNECT}. * *
If a session cannot be opened, then * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} will be invoked with the * appropriate {@link RangingSession.Callback.Reason}. * *
An open {@link RangingSession} will be automatically closed if client application process * dies. * *
A UWB support library must be used in order to construct the {@code parameter} * {@link PersistableBundle}. * * @param parameters the parameters that define the ranging session * @param executor {@link Executor} to run callbacks * @param callbacks {@link RangingSession.Callback} to associate with the * {@link RangingSession} that is being opened. * * @return an {@link CancellationSignal} that is able to be used to cancel the opening of a * {@link RangingSession} that has been requested through {@link #openRangingSession} * but has not yet been made available by * {@link RangingSession.Callback#onOpened(RangingSession)}. */ @NonNull @RequiresPermission(allOf = { permission.UWB_PRIVILEGED, permission.UWB_RANGING }) public CancellationSignal openRangingSession(@NonNull PersistableBundle parameters, @NonNull @CallbackExecutor Executor executor, @NonNull RangingSession.Callback callbacks) { return openRangingSessionInternal(parameters, executor, callbacks, /* chipId= */ null); } /** * Open a {@link RangingSession} with the given parameters on a specific UWB subsystem * * @see #openRangingSession(PersistableBundle, Executor, RangingSession.Callback) if you don't * need multi-HAL support * * @param parameters the parameters that define the ranging session * @param executor {@link Executor} to run callbacks * @param callbacks {@link RangingSession.Callback} to associate with the * {@link RangingSession} that is being opened. * @param chipId identifier of UWB chip for multi-HAL devices * * @return an {@link CancellationSignal} that is able to be used to cancel the opening of a * {@link RangingSession} that has been requested through {@link #openRangingSession} * but has not yet been made available by * {@link RangingSession.Callback#onOpened(RangingSession)}. */ @NonNull @RequiresPermission(allOf = { permission.UWB_PRIVILEGED, permission.UWB_RANGING }) public CancellationSignal openRangingSession(@NonNull PersistableBundle parameters, @NonNull @CallbackExecutor Executor executor, @NonNull RangingSession.Callback callbacks, @SuppressLint("ListenerLast") @NonNull String chipId) { checkNotNull(chipId); return openRangingSessionInternal(parameters, executor, callbacks, chipId); } private CancellationSignal openRangingSessionInternal(PersistableBundle parameters, Executor executor, RangingSession.Callback callbacks, String chipId) { return mRangingManager.openSession( mContext.getAttributionSource(), parameters, executor, callbacks, chipId); } /** * Returns the current enabled/disabled state for UWB. * * Possible values are: * AdapterStateCallback#STATE_DISABLED * AdapterStateCallback#STATE_ENABLED_INACTIVE * AdapterStateCallback#STATE_ENABLED_ACTIVE * * @return value representing current enabled/disabled state for UWB. */ public @AdapterStateCallback.State int getAdapterState() { return mAdapterStateListener.getAdapterState(); } /** * Whether UWB is enabled or disabled. * *
* If disabled, this could indicate that either *
* If the device supports automatically turning off UWB hardware, the state of UWB hardware * is controlled by: *
* This does not indicate the global state of UWB, this only indicates whether this app * (identified by {@link Context#getAttributionSource()}) has requested for UWB hardware to be * enabled or disabled. *
* * @return true if enabled, false otherwise. * @throws IllegalStateException if the device does not support this feature * * @see #isUwbHwIdleTurnOffEnabled() * @see #requestUwbHwEnable(boolean) */ @FlaggedApi("com.android.uwb.flags.hw_state") @RequiresPermission(permission.UWB_PRIVILEGED) public boolean isUwbHwEnableRequested() { try { return mUwbAdapter.isHwEnableRequested(mContext.getAttributionSource()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * This client has requested for UWB hardware to be enabled or disabled. * Only supported on devices which supports hw idle turn off (indicated by * {@link #isUwbHwIdleTurnOffEnabled()}) * ** This does not indicate the global state of UWB, this only indicates whether this app * (identified by {@link Context#getAttributionSource()}) has requested for UWB hardware to be * enabled or disabled. * If UWB is enabled by the user and has at least 1 privileged client requesting UWB toggle on, * then UWB hardware is enabled, else the UWB hardware is disabled. *
* * @param enabled value representing intent to disable or enable UWB. * @throws IllegalStateException if the device does not support this feature * * @see #isUwbHwIdleTurnOffEnabled() * @see #isUwbHwEnableRequested() () */ @FlaggedApi("com.android.uwb.flags.hw_state") @RequiresPermission(permission.UWB_PRIVILEGED) public void requestUwbHwEnabled(boolean enabled) { try { mUwbAdapter.requestHwEnabled( enabled, mContext.getAttributionSource(), new Binder(mContext.getPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns a list of UWB chip infos in a {@link PersistableBundle}. * * Callers can invoke methods on a specific UWB chip by passing its {@code chipId} to the * method, which can be determined by calling: ** List* * @return list of {@link PersistableBundle} containing info about UWB chips for a multi-HAL * system, or a list of info for a single chip for a single HAL system. */ @RequiresPermission(permission.UWB_PRIVILEGED) @NonNull public ListchipInfos = getChipInfos(); * for (PersistableBundle chipInfo : chipInfos) { * String chipId = ChipInfoParams.fromBundle(chipInfo).getChipId(); * } *