blob: 8d2c80a5677b4f45159400d6035ef420eb4af9f5 [file] [log] [blame]
/*
* 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.AttributionSource;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.uwb.AdapterState;
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<SessionHandle, UwbRangingCallbacksWrapper> 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 AttributionSource mAttributionSource;
private final SessionHandle mSessionHandle;
private final IUwbRangingCallbacks mExternalCb;
private boolean mIsValid;
UwbRangingCallbacksWrapper(@NonNull AttributionSource attributionSource,
@NonNull SessionHandle sessionHandle,
@NonNull IUwbRangingCallbacks externalCb) {
mAttributionSource = attributionSource;
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;
boolean permissionGranted = Binder.withCleanCallingIdentity(
() -> mUwbInjector.checkUwbRangingPermissionForDataDelivery(
mAttributionSource, "uwb ranging result"));
if (!permissionGranted) {
Log.e(TAG, "Not delivering ranging result because of permission denial"
+ mSessionHandle);
return;
}
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<SessionHandle, UwbRangingCallbacksWrapper> 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, RemoteException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
long token = Binder.clearCallingIdentity();
try {
mVendorUwbAdapter.setEnabled(mUwbInjector.isPersistedUwbStateEnabled());
} finally {
Binder.restoreCallingIdentity(token);
}
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
mContext = context;
mUwbInjector = uwbInjector;
}
private void enforceUwbPrivilegedPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.UWB_PRIVILEGED,
"UwbService");
}
@Override
public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().registerAdapterStateCallbacks(adapterStateCallbacks);
}
@Override
public void unregisterAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().unregisterAdapterStateCallbacks(adapterStateCallbacks);
}
@Override
public long getTimestampResolutionNanos() throws RemoteException {
enforceUwbPrivilegedPermission();
return getVendorUwbAdapter().getTimestampResolutionNanos();
}
@Override
public PersistableBundle getSpecificationInfo() throws RemoteException {
enforceUwbPrivilegedPermission();
return getVendorUwbAdapter().getSpecificationInfo();
}
@Override
public void openRanging(AttributionSource attributionSource,
SessionHandle sessionHandle, IUwbRangingCallbacks rangingCallbacks,
PersistableBundle parameters) throws RemoteException {
enforceUwbPrivilegedPermission();
mUwbInjector.enforceUwbRangingPermissionForPreflight(attributionSource);
UwbRangingCallbacksWrapper wrapperCb =
new UwbRangingCallbacksWrapper(attributionSource, sessionHandle, rangingCallbacks);
synchronized (mCallbacksMap) {
mCallbacksMap.put(sessionHandle, wrapperCb);
}
getVendorUwbAdapter().openRanging(attributionSource, sessionHandle, wrapperCb, parameters);
}
@Override
public void startRanging(SessionHandle sessionHandle, PersistableBundle parameters)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().startRanging(sessionHandle, parameters);
}
@Override
public void reconfigureRanging(SessionHandle sessionHandle, PersistableBundle parameters)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().reconfigureRanging(sessionHandle, parameters);
}
@Override
public void stopRanging(SessionHandle sessionHandle) throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().stopRanging(sessionHandle);
}
@Override
public void closeRanging(SessionHandle sessionHandle) throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().closeRanging(sessionHandle);
}
@Override
public synchronized int getAdapterState() throws RemoteException {
return getVendorUwbAdapter().getAdapterState();
}
@Override
public synchronized void setEnabled(boolean enabled) throws RemoteException {
persistUwbState(enabled);
getVendorUwbAdapter().setEnabled(enabled);
}
private void persistUwbState(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
}
}