1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.TIRAMISU; 4 5 import android.os.Build.VERSION_CODES; 6 import android.os.CancellationSignal; 7 import android.os.PersistableBundle; 8 import android.uwb.AdapterState; 9 import android.uwb.RangingSession; 10 import android.uwb.StateChangeReason; 11 import android.uwb.UwbManager; 12 import android.uwb.UwbManager.AdapterStateCallback; 13 import com.google.common.collect.ImmutableList; 14 import java.util.ArrayList; 15 import java.util.List; 16 import java.util.concurrent.Executor; 17 import org.robolectric.annotation.Implementation; 18 import org.robolectric.annotation.Implements; 19 import org.robolectric.shadow.api.Shadow; 20 21 /** Adds Robolectric support for UWB ranging. */ 22 @Implements(value = UwbManager.class, minSdk = VERSION_CODES.S, isInAndroidSdk = false) 23 public class ShadowUwbManager { 24 25 private AdapterStateCallback callback; 26 27 private int adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE; 28 29 private int stateChangedReason = AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY; 30 31 private PersistableBundle specificationInfo = new PersistableBundle(); 32 33 private List<PersistableBundle> chipInfos = new ArrayList<>(); 34 35 private ShadowRangingSession.Adapter adapter = 36 new ShadowRangingSession.Adapter() { 37 @Override 38 public void onOpen( 39 RangingSession session, RangingSession.Callback callback, PersistableBundle params) {} 40 41 @Override 42 public void onStart( 43 RangingSession session, RangingSession.Callback callback, PersistableBundle params) {} 44 45 @Override 46 public void onReconfigure( 47 RangingSession session, RangingSession.Callback callback, PersistableBundle params) {} 48 49 @Override 50 public void onStop(RangingSession session, RangingSession.Callback callback) {} 51 52 @Override 53 public void onClose(RangingSession session, RangingSession.Callback callback) {} 54 }; 55 56 @Implementation registerAdapterStateCallback(Executor executor, AdapterStateCallback callback)57 protected void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) { 58 this.callback = callback; 59 callback.onStateChanged(adapterState, stateChangedReason); 60 } 61 62 /** 63 * Simulates adapter state change by invoking a callback registered by {@link 64 * ShadowUwbManager#registerAdapterStateCallback(Executor executor, AdapterStateCallback 65 * callback)}. 66 * 67 * @param state A state that should be passed to the callback. 68 * @param reason A reason that should be passed to the callback. 69 * @throws IllegalArgumentException if the callback is missing. 70 */ simulateAdapterStateChange(@dapterState int state, @StateChangeReason int reason)71 public void simulateAdapterStateChange(@AdapterState int state, @StateChangeReason int reason) { 72 if (this.callback == null) { 73 throw new IllegalArgumentException("AdapterStateCallback should not be null"); 74 } 75 76 adapterState = state; 77 stateChangedReason = reason; 78 79 this.callback.onStateChanged(state, reason); 80 } 81 82 /** 83 * Simply returns the bundle provided by {@link ShadowUwbManager#setSpecificationInfo()}, allowing 84 * the tester to dictate available features. 85 */ 86 @Implementation getSpecificationInfo()87 protected PersistableBundle getSpecificationInfo() { 88 return specificationInfo; 89 } 90 91 /** 92 * Instantiates a {@link ShadowRangingSession} with the adapter provided by {@link 93 * ShadowUwbManager#setUwbAdapter()}, allowing the tester dictate the results of ranging attempts. 94 * 95 * @throws IllegalArgumentException if UWB is disabled. 96 */ 97 @Implementation openRangingSession( PersistableBundle params, Executor executor, RangingSession.Callback callback)98 protected CancellationSignal openRangingSession( 99 PersistableBundle params, Executor executor, RangingSession.Callback callback) { 100 if (!isUwbEnabled()) { 101 throw new IllegalStateException("Uwb is not enabled"); 102 } 103 RangingSession session = ShadowRangingSession.newInstance(executor, callback, adapter); 104 CancellationSignal cancellationSignal = new CancellationSignal(); 105 cancellationSignal.setOnCancelListener(session::close); 106 Shadow.<ShadowRangingSession>extract(session).open(params); 107 return cancellationSignal; 108 } 109 110 /** Sets the UWB adapter to use for new {@link ShadowRangingSession}s. */ setUwbAdapter(ShadowRangingSession.Adapter adapter)111 public void setUwbAdapter(ShadowRangingSession.Adapter adapter) { 112 this.adapter = adapter; 113 } 114 115 /** Sets the bundle to be returned by {@link android.uwb.UwbManager#getSpecificationInfo}. */ setSpecificationInfo(PersistableBundle specificationInfo)116 public void setSpecificationInfo(PersistableBundle specificationInfo) { 117 this.specificationInfo = new PersistableBundle(specificationInfo); 118 } 119 120 /** 121 * Instantiates a {@link ShadowRangingSession} with the multi-chip API call. {@code chipId} is 122 * unused in the shadow implementation, so this is equivalent to {@link 123 * ShadowUwbManager#openRangingSession(PersistableBundle, Executor, RangingSession.Callback)} 124 */ 125 @Implementation(minSdk = TIRAMISU) openRangingSession( PersistableBundle params, Executor executor, RangingSession.Callback callback, String chipId)126 protected CancellationSignal openRangingSession( 127 PersistableBundle params, 128 Executor executor, 129 RangingSession.Callback callback, 130 String chipId) { 131 return openRangingSession(params, executor, callback); 132 } 133 134 /** Returns whether UWB is enabled or disabled. */ 135 @Implementation(minSdk = TIRAMISU) isUwbEnabled()136 protected boolean isUwbEnabled() { 137 return adapterState != AdapterStateCallback.STATE_DISABLED; 138 } 139 140 /** 141 * Disables or enables UWB by the user. 142 * 143 * @param enabled value representing intent to disable or enable UWB. 144 */ 145 @Implementation setUwbEnabled(boolean enabled)146 protected void setUwbEnabled(boolean enabled) { 147 boolean stateChanged = false; 148 149 if (enabled && adapterState == AdapterStateCallback.STATE_DISABLED) { 150 adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE; 151 stateChanged = true; 152 } else if (!enabled && adapterState != AdapterStateCallback.STATE_DISABLED) { 153 adapterState = AdapterStateCallback.STATE_DISABLED; 154 stateChanged = true; 155 } 156 157 if (this.callback != null && stateChanged) { 158 this.callback.onStateChanged( 159 adapterState, AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); 160 } 161 } 162 163 /** 164 * Simply returns the List of bundles provided by {@link ShadowUwbManager#setChipInfos(List)} , 165 * allowing the tester to set multi-chip configuration. 166 */ 167 @Implementation(minSdk = TIRAMISU) getChipInfos()168 protected List<PersistableBundle> getChipInfos() { 169 return ImmutableList.copyOf(chipInfos); 170 } 171 172 /** Sets the list of bundles to be returned by {@link android.uwb.UwbManager#getChipInfos}. */ setChipInfos(List<PersistableBundle> chipInfos)173 public void setChipInfos(List<PersistableBundle> chipInfos) { 174 this.chipInfos = new ArrayList<>(chipInfos); 175 } 176 } 177