diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index bed77e6643372..95024b38575be 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -24,9 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.content.AttributionSource; import android.content.Context; -import android.content.ContextParams; import android.os.CancellationSignal; import android.os.IBinder; import android.os.PersistableBundle; @@ -49,7 +47,7 @@ import java.util.concurrent.Executor; @SystemApi @SystemService(Context.UWB_SERVICE) public final class UwbManager { - private static final String SERVICE_NAME = "uwb"; + private static final String SERVICE_NAME = Context.UWB_SERVICE; private final Context mContext; private final IUwbAdapter mUwbAdapter; diff --git a/services/Android.bp b/services/Android.bp index ad1406c73c2fd..20b89de7f2a60 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -65,6 +65,7 @@ filegroup { ":services.texttospeech-sources", ":services.usage-sources", ":services.usb-sources", + ":services.uwb-sources", ":services.voiceinteraction-sources", ":services.wifi-sources", ], @@ -129,6 +130,7 @@ java_library { "services.texttospeech", "services.usage", "services.usb", + "services.uwb", "services.voiceinteraction", "services.wifi", "service-blobstore", diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 912b8cad952c8..4c4c5821a2dbb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -384,6 +384,7 @@ public final class SystemServer implements Dumpable { private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService"; private static final String GAME_MANAGER_SERVICE_CLASS = "com.android.server.app.GameManagerService$Lifecycle"; + private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService"; private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector"; @@ -2637,6 +2638,12 @@ public final class SystemServer implements Dumpable { LocalManagerRegistry.addManager(ArtManagerLocal.class, new ArtManagerLocal()); t.traceEnd(); + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) { + t.traceBegin("UwbService"); + mSystemServiceManager.startService(UWB_SERVICE_CLASS); + t.traceEnd(); + } + t.traceBegin("StartBootPhaseDeviceSpecificServicesReady"); mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY); t.traceEnd(); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index d7fbd4913b2c0..9d055e0d431fb 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -37,6 +37,7 @@ android_test { "services.net", "services.people", "services.usage", + "services.uwb", "guava", "androidx.test.core", "androidx.test.ext.truth", diff --git a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java new file mode 100644 index 0000000000000..2f5a5cc971174 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java @@ -0,0 +1,299 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.IBinder; +import android.os.PersistableBundle; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.uwb.IUwbAdapter; +import android.uwb.IUwbAdapterStateCallbacks; +import android.uwb.IUwbRangingCallbacks; +import android.uwb.RangingReport; +import android.uwb.RangingSession; +import android.uwb.SessionHandle; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link UwbServiceImpl}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class UwbServiceImplTest { + @Mock private IUwbAdapter mVendorService; + @Mock private IBinder mVendorServiceBinder; + @Mock private Context mContext; + @Mock private UwbInjector mUwbInjector; + @Captor private ArgumentCaptor mRangingCbCaptor; + @Captor private ArgumentCaptor mClientDeathCaptor; + @Captor private ArgumentCaptor mVendorServiceDeathCaptor; + + private UwbServiceImpl mUwbServiceImpl; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mUwbInjector.getVendorService()).thenReturn(mVendorService); + when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder); + mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector); + } + + @Test + public void testApiCallThrowsIllegalStateExceptionIfVendorServiceNotFound() throws Exception { + when(mUwbInjector.getVendorService()).thenReturn(null); + + final IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); + try { + mUwbServiceImpl.registerAdapterStateCallbacks(cb); + fail(); + } catch (IllegalStateException e) { /* pass */ } + } + + @Test + public void testRegisterAdapterStateCallbacks() throws Exception { + final IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); + mUwbServiceImpl.registerAdapterStateCallbacks(cb); + + verify(mVendorService).registerAdapterStateCallbacks(cb); + } + + @Test + public void testUnregisterAdapterStateCallbacks() throws Exception { + final IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); + mUwbServiceImpl.unregisterAdapterStateCallbacks(cb); + + verify(mVendorService).unregisterAdapterStateCallbacks(cb); + } + + @Test + public void testGetTimestampResolutionNanos() throws Exception { + final long timestamp = 34L; + when(mVendorService.getTimestampResolutionNanos()).thenReturn(timestamp); + assertThat(mUwbServiceImpl.getTimestampResolutionNanos()).isEqualTo(timestamp); + + verify(mVendorService).getTimestampResolutionNanos(); + } + + @Test + public void testGetSpecificationInfo() throws Exception { + final PersistableBundle specification = new PersistableBundle(); + when(mVendorService.getSpecificationInfo()).thenReturn(specification); + assertThat(mUwbServiceImpl.getSpecificationInfo()).isEqualTo(specification); + + verify(mVendorService).getSpecificationInfo(); + } + + @Test + public void testOpenRanging() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); + final PersistableBundle parameters = new PersistableBundle(); + final IBinder cbBinder = mock(IBinder.class); + when(cb.asBinder()).thenReturn(cbBinder); + + mUwbServiceImpl.openRanging(sessionHandle, cb, parameters); + + verify(mVendorService).openRanging( + eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters)); + assertThat(mRangingCbCaptor.getValue()).isNotNull(); + } + + @Test + public void testStartRanging() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final PersistableBundle parameters = new PersistableBundle(); + + mUwbServiceImpl.startRanging(sessionHandle, parameters); + + verify(mVendorService).startRanging(sessionHandle, parameters); + } + + @Test + public void testReconfigureRanging() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final PersistableBundle parameters = new PersistableBundle(); + + mUwbServiceImpl.reconfigureRanging(sessionHandle, parameters); + + verify(mVendorService).reconfigureRanging(sessionHandle, parameters); + } + + @Test + public void testStopRanging() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + + mUwbServiceImpl.stopRanging(sessionHandle); + + verify(mVendorService).stopRanging(sessionHandle); + } + + @Test + public void testCloseRanging() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + + mUwbServiceImpl.closeRanging(sessionHandle); + + verify(mVendorService).closeRanging(sessionHandle); + } + + @Test + public void testRangingCallbacks() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); + final PersistableBundle parameters = new PersistableBundle(); + final IBinder cbBinder = mock(IBinder.class); + when(cb.asBinder()).thenReturn(cbBinder); + + mUwbServiceImpl.openRanging(sessionHandle, cb, parameters); + + verify(mVendorService).openRanging( + eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters)); + assertThat(mRangingCbCaptor.getValue()).isNotNull(); + + // Invoke vendor service callbacks and ensure that the corresponding app callback is + // invoked. + mRangingCbCaptor.getValue().onRangingOpened(sessionHandle); + verify(cb).onRangingOpened(sessionHandle); + + mRangingCbCaptor.getValue().onRangingOpenFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingOpenFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + + mRangingCbCaptor.getValue().onRangingStarted(sessionHandle, parameters); + verify(cb).onRangingStarted(sessionHandle, parameters); + + mRangingCbCaptor.getValue().onRangingStartFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingStartFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + + mRangingCbCaptor.getValue().onRangingReconfigured(sessionHandle, parameters); + verify(cb).onRangingReconfigured(sessionHandle, parameters); + + mRangingCbCaptor.getValue().onRangingReconfigureFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingReconfigureFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + + mRangingCbCaptor.getValue().onRangingStopped( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingStopped( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + + mRangingCbCaptor.getValue().onRangingStopFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingStopFailed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + + final RangingReport rangingReport = new RangingReport.Builder().build(); + mRangingCbCaptor.getValue().onRangingResult(sessionHandle, rangingReport); + verify(cb).onRangingResult(sessionHandle, rangingReport); + + mRangingCbCaptor.getValue().onRangingClosed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + verify(cb).onRangingClosed( + sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters); + } + + @Test + public void testHandleClientDeath() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); + final PersistableBundle parameters = new PersistableBundle(); + final IBinder cbBinder = mock(IBinder.class); + when(cb.asBinder()).thenReturn(cbBinder); + + mUwbServiceImpl.openRanging(sessionHandle, cb, parameters); + + verify(mVendorService).openRanging( + eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters)); + assertThat(mRangingCbCaptor.getValue()).isNotNull(); + + verify(cbBinder).linkToDeath(mClientDeathCaptor.capture(), anyInt()); + assertThat(mClientDeathCaptor.getValue()).isNotNull(); + + clearInvocations(cb); + + // Invoke cb, ensure it reaches the client. + mRangingCbCaptor.getValue().onRangingOpened(sessionHandle); + verify(cb).onRangingOpened(sessionHandle); + + // Trigger client death and ensure the session is stopped. + mClientDeathCaptor.getValue().binderDied(); + verify(mVendorService).stopRanging(sessionHandle); + verify(mVendorService).closeRanging(sessionHandle); + + // Invoke cb, it should be ignored. + mRangingCbCaptor.getValue().onRangingStarted(sessionHandle, parameters); + verify(cb, never()).onRangingStarted(any(), any()); + } + + @Test + public void testHandleVendorServiceDeath() throws Exception { + final SessionHandle sessionHandle = new SessionHandle(5); + final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); + final PersistableBundle parameters = new PersistableBundle(); + final IBinder cbBinder = mock(IBinder.class); + when(cb.asBinder()).thenReturn(cbBinder); + + mUwbServiceImpl.openRanging(sessionHandle, cb, parameters); + + verify(mVendorServiceBinder).linkToDeath(mVendorServiceDeathCaptor.capture(), anyInt()); + assertThat(mVendorServiceDeathCaptor.getValue()).isNotNull(); + + verify(mVendorService).openRanging( + eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters)); + assertThat(mRangingCbCaptor.getValue()).isNotNull(); + + clearInvocations(cb); + + // Invoke cb, ensure it reaches the client. + mRangingCbCaptor.getValue().onRangingOpened(sessionHandle); + verify(cb).onRangingOpened(sessionHandle); + + // Trigger vendor service death and ensure that the client is informed of session end. + mVendorServiceDeathCaptor.getValue().binderDied(); + verify(cb).onRangingClosed( + eq(sessionHandle), eq(RangingSession.Callback.REASON_UNKNOWN), + argThat((p) -> p.isEmpty())); + } +} diff --git a/services/uwb/Android.bp b/services/uwb/Android.bp new file mode 100644 index 0000000000000..da30d43a45369 --- /dev/null +++ b/services/uwb/Android.bp @@ -0,0 +1,26 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.uwb-sources", + srcs: ["java/**/*.java"], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.uwb", + defaults: ["platform_service_defaults"], + srcs: [ + ":services.uwb-sources", + ], + libs: [ + "services.core", + ], +} diff --git a/services/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java new file mode 100644 index 0000000000000..00c0acabcb3bc --- /dev/null +++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java @@ -0,0 +1,48 @@ +/* + * 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 android.annotation.NonNull; +import android.content.Context; +import android.os.IBinder; +import android.os.ServiceManager; +import android.uwb.IUwbAdapter; + + +/** + * To be used for dependency injection (especially helps mocking static dependencies). + */ +public class UwbInjector { + private static final String TAG = "UwbInjector"; + + private static final String VENDOR_SERVICE_NAME = "uwb_vendor"; + + private final Context mContext; + + public UwbInjector(@NonNull Context context) { + mContext = context; + } + + /** + * @return Returns the vendor service handle. + */ + public IUwbAdapter getVendorService() { + IBinder b = ServiceManager.getService(VENDOR_SERVICE_NAME); + if (b == null) return null; + return IUwbAdapter.Stub.asInterface(b); + } +} diff --git a/services/uwb/java/com/android/server/uwb/UwbService.java b/services/uwb/java/com/android/server/uwb/UwbService.java new file mode 100644 index 0000000000000..4bb280f75ed12 --- /dev/null +++ b/services/uwb/java/com/android/server/uwb/UwbService.java @@ -0,0 +1,42 @@ +/* + * 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 android.content.Context; +import android.util.Log; + +import com.android.server.SystemService; + +/** + * Uwb System service. + */ +public class UwbService extends SystemService { + private static final String TAG = "UwbService"; + + private final UwbServiceImpl mImpl; + + public UwbService(Context context) { + super(context); + mImpl = new UwbServiceImpl(context, new UwbInjector(context)); + } + + @Override + public void onStart() { + Log.i(TAG, "Registering " + Context.UWB_SERVICE); + publishBinderService(Context.UWB_SERVICE, mImpl); + } +} diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java new file mode 100644 index 0000000000000..70bd20e0014e5 --- /dev/null +++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java @@ -0,0 +1,288 @@ +/* + * 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 android.annotation.NonNull; +import android.content.Context; +import android.os.IBinder; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.uwb.IUwbAdapter; +import android.uwb.IUwbAdapterStateCallbacks; +import android.uwb.IUwbRangingCallbacks; +import android.uwb.RangingReport; +import android.uwb.RangingSession; +import android.uwb.SessionHandle; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Map; + +/** + * Implementation of {@link android.uwb.IUwbAdapter} binder service. + */ +public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRecipient{ + private static final String TAG = "UwbServiceImpl"; + + private final Context mContext; + private final UwbInjector mUwbInjector; + /** + * Map for storing the callbacks wrapper for each session. + */ + @GuardedBy("mCallbacksMap") + private final Map mCallbacksMap = new ArrayMap<>(); + + /** + * Used for caching the vendor implementation of {@link IUwbAdapter} interface. + */ + private IUwbAdapter mVendorUwbAdapter; + + /** + * Wrapper for callback registered with vendor service. This wrapper is needed for performing + * permission check before sending the callback to the external app. + * + * Access to these callbacks are synchronized. + */ + private class UwbRangingCallbacksWrapper extends IUwbRangingCallbacks.Stub + implements IBinder.DeathRecipient{ + private final SessionHandle mSessionHandle; + private final IUwbRangingCallbacks mExternalCb; + private boolean mIsValid; + + UwbRangingCallbacksWrapper(@NonNull SessionHandle sessionHandle, + @NonNull IUwbRangingCallbacks externalCb) { + mSessionHandle = sessionHandle; + mExternalCb = externalCb; + mIsValid = true; + + // Link to death for external callback. + linkToDeath(); + } + + private void linkToDeath() { + IBinder binder = mExternalCb.asBinder(); + try { + binder.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(TAG, "Unable to link to client death event.", e); + } + } + + private void removeClientAndUnlinkToDeath() { + // Remove from the map. + synchronized (mCallbacksMap) { + mCallbacksMap.remove(mSessionHandle); + } + IBinder binder = mExternalCb.asBinder(); + binder.unlinkToDeath(this, 0); + mIsValid = false; + } + + + @Override + public synchronized void onRangingOpened(SessionHandle sessionHandle) + throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingOpened(sessionHandle); + } + + @Override + public synchronized void onRangingOpenFailed(SessionHandle sessionHandle, + int reason, PersistableBundle parameters) throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingOpenFailed(sessionHandle, reason, parameters); + } + + @Override + public synchronized void onRangingStarted(SessionHandle sessionHandle, + PersistableBundle parameters) + throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingStarted(sessionHandle, parameters); + } + + @Override + public synchronized void onRangingStartFailed(SessionHandle sessionHandle, + int reason, PersistableBundle parameters) throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingStartFailed(sessionHandle, reason, parameters); + } + + @Override + public synchronized void onRangingReconfigured(SessionHandle sessionHandle, + PersistableBundle parameters) + throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingReconfigured(sessionHandle, parameters); + } + + @Override + public synchronized void onRangingReconfigureFailed(SessionHandle sessionHandle, + int reason, PersistableBundle parameters) throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingReconfigureFailed(sessionHandle, reason, parameters); + } + + @Override + public synchronized void onRangingStopped(SessionHandle sessionHandle, int reason, + PersistableBundle parameters) + throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingStopped(sessionHandle, reason, parameters); + } + + @Override + public synchronized void onRangingStopFailed(SessionHandle sessionHandle, int reason, + PersistableBundle parameters) throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingStopFailed(sessionHandle, reason, parameters); + } + + @Override + public synchronized void onRangingClosed(SessionHandle sessionHandle, int reason, + PersistableBundle parameters) throws RemoteException { + if (!mIsValid) return; + mExternalCb.onRangingClosed(sessionHandle, reason, parameters); + removeClientAndUnlinkToDeath(); + } + + @Override + public synchronized void onRangingResult(SessionHandle sessionHandle, + RangingReport rangingReport) + throws RemoteException { + if (!mIsValid) return; + // TODO: Perform permission checks and noteOp. + mExternalCb.onRangingResult(sessionHandle, rangingReport); + } + + @Override + public synchronized void binderDied() { + if (!mIsValid) return; + Log.i(TAG, "Client died: ending session: " + mSessionHandle); + try { + removeClientAndUnlinkToDeath(); + stopRanging(mSessionHandle); + closeRanging(mSessionHandle); + } catch (RemoteException e) { + Log.e(TAG, "Remote exception while handling client death", e); + } + } + } + + private void linkToVendorServiceDeath() { + IBinder binder = mVendorUwbAdapter.asBinder(); + try { + binder.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(TAG, "Unable to link to vendor service death event.", e); + } + } + + @Override + public void binderDied() { + Log.i(TAG, "Vendor service died: sending session close callbacks"); + synchronized (mCallbacksMap) { + for (Map.Entry e : mCallbacksMap.entrySet()) { + try { + e.getValue().mExternalCb.onRangingClosed( + e.getKey(), RangingSession.Callback.REASON_UNKNOWN, + new PersistableBundle()); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to send session close callback " + e.getKey(), ex); + } + } + // Clear all sessions. + mCallbacksMap.clear(); + } + mVendorUwbAdapter = null; + } + + private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException { + if (mVendorUwbAdapter != null) return mVendorUwbAdapter; + mVendorUwbAdapter = mUwbInjector.getVendorService(); + if (mVendorUwbAdapter == null) { + throw new IllegalStateException("No vendor service found!"); + } + Log.i(TAG, "Retrieved vendor service"); + linkToVendorServiceDeath(); + return mVendorUwbAdapter; + } + + UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) { + mContext = context; + mUwbInjector = uwbInjector; + } + + @Override + public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks) + throws RemoteException { + getVendorUwbAdapter().registerAdapterStateCallbacks(adapterStateCallbacks); + } + + @Override + public void unregisterAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks) + throws RemoteException { + getVendorUwbAdapter().unregisterAdapterStateCallbacks(adapterStateCallbacks); + } + + @Override + public long getTimestampResolutionNanos() throws RemoteException { + return getVendorUwbAdapter().getTimestampResolutionNanos(); + } + + @Override + public PersistableBundle getSpecificationInfo() throws RemoteException { + return getVendorUwbAdapter().getSpecificationInfo(); + } + + @Override + public void openRanging(SessionHandle sessionHandle, IUwbRangingCallbacks rangingCallbacks, + PersistableBundle parameters) throws RemoteException { + UwbRangingCallbacksWrapper wrapperCb = + new UwbRangingCallbacksWrapper(sessionHandle, rangingCallbacks); + synchronized (mCallbacksMap) { + mCallbacksMap.put(sessionHandle, wrapperCb); + } + getVendorUwbAdapter().openRanging(sessionHandle, wrapperCb, parameters); + } + + @Override + public void startRanging(SessionHandle sessionHandle, PersistableBundle parameters) + throws RemoteException { + // TODO: Perform permission checks. + getVendorUwbAdapter().startRanging(sessionHandle, parameters); + } + + @Override + public void reconfigureRanging(SessionHandle sessionHandle, PersistableBundle parameters) + throws RemoteException { + getVendorUwbAdapter().reconfigureRanging(sessionHandle, parameters); + } + + @Override + public void stopRanging(SessionHandle sessionHandle) throws RemoteException { + getVendorUwbAdapter().stopRanging(sessionHandle); + } + + @Override + public void closeRanging(SessionHandle sessionHandle) throws RemoteException { + getVendorUwbAdapter().closeRanging(sessionHandle); + } + +}