1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.ranging; 18 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 20 21 import android.app.ActivityManager; 22 import android.content.AttributionSource; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.os.RemoteException; 28 import android.ranging.IRangingCallbacks; 29 import android.ranging.IRangingCapabilitiesCallback; 30 import android.ranging.RangingConfig; 31 import android.ranging.RangingData; 32 import android.ranging.RangingDevice; 33 import android.ranging.RangingPreference; 34 import android.ranging.RangingSession.Callback; 35 import android.ranging.SessionHandle; 36 import android.ranging.oob.IOobSendDataListener; 37 import android.ranging.oob.OobHandle; 38 import android.ranging.oob.OobInitiatorRangingConfig; 39 import android.ranging.oob.OobResponderRangingConfig; 40 import android.ranging.raw.RawInitiatorRangingConfig; 41 import android.ranging.raw.RawResponderRangingConfig; 42 import android.util.Log; 43 44 import androidx.annotation.NonNull; 45 46 import com.android.server.ranging.RangingUtils.InternalReason; 47 import com.android.server.ranging.RangingUtils.StateMachine; 48 import com.android.server.ranging.metrics.SessionMetricsLogger; 49 import com.android.server.ranging.session.OobInitiatorRangingSession; 50 import com.android.server.ranging.session.OobResponderRangingSession; 51 import com.android.server.ranging.session.RangingSession; 52 import com.android.server.ranging.session.RangingSessionConfig; 53 import com.android.server.ranging.session.RangingSessionConfig.TechnologyConfig; 54 import com.android.server.ranging.session.RawInitiatorRangingSession; 55 import com.android.server.ranging.session.RawResponderRangingSession; 56 57 import com.google.common.collect.ImmutableSet; 58 import com.google.common.util.concurrent.ListeningExecutorService; 59 import com.google.common.util.concurrent.MoreExecutors; 60 61 import java.io.FileDescriptor; 62 import java.io.PrintWriter; 63 import java.util.ArrayList; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Set; 67 import java.util.concurrent.ConcurrentHashMap; 68 import java.util.concurrent.Executors; 69 import java.util.concurrent.ScheduledExecutorService; 70 71 public final class RangingServiceManager implements ActivityManager.OnUidImportanceListener{ 72 private static final String TAG = RangingServiceManager.class.getSimpleName(); 73 74 public enum RangingTask { 75 TASK_START_RANGING(1), 76 TASK_STOP_RANGING(2), 77 TASK_ADD_DEVICE(3), 78 TASK_REMOVE_DEVICE(4), 79 TASK_RECONFIGURE_INTERVAL(5); 80 81 private final int mVal; 82 RangingTask(int val)83 RangingTask(int val) { 84 this.mVal = val; 85 } 86 getValue()87 public int getValue() { 88 return mVal; 89 } 90 fromValue(int code)91 public static RangingTask fromValue(int code) { 92 for (RangingTask task : RangingTask.values()) { 93 if (task.getValue() == code) { 94 return task; 95 } 96 } 97 throw new IllegalArgumentException("Unknown task code: " + code); 98 } 99 } 100 101 private final RangingInjector mRangingInjector; 102 private final ListeningExecutorService mAdapterExecutor; 103 private final ScheduledExecutorService mOobExecutor; 104 private final RangingTaskManager mRangingTaskManager; 105 private final Map<SessionHandle, RangingSession> mSessions = new ConcurrentHashMap<>(); 106 final ConcurrentHashMap<Integer, List<RangingSession>> mNonPrivilegedUidToSessionsTable = 107 new ConcurrentHashMap<>(); 108 109 private final ActivityManager mActivityManager; 110 RangingServiceManager(RangingInjector rangingInjector, ActivityManager activityManager, Looper looper)111 public RangingServiceManager(RangingInjector rangingInjector, ActivityManager activityManager, 112 Looper looper) { 113 mRangingInjector = rangingInjector; 114 mActivityManager = activityManager; 115 mAdapterExecutor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 116 mOobExecutor = Executors.newSingleThreadScheduledExecutor(); 117 mRangingTaskManager = new RangingTaskManager(looper); 118 registerUidImportanceTransitions(); 119 } 120 121 @Override onUidImportance(int uid, int importance)122 public void onUidImportance(int uid, int importance) { 123 synchronized (mNonPrivilegedUidToSessionsTable) { 124 List<RangingSession> rangingSessions = mNonPrivilegedUidToSessionsTable.get(uid); 125 126 if (rangingSessions == null) { 127 return; 128 } 129 130 if (RangingInjector.isNonExistentAppOrService(importance)) { 131 mNonPrivilegedUidToSessionsTable.remove(uid); 132 return; 133 } 134 135 boolean isForeground = RangingInjector.isForegroundAppOrServiceImportance(importance); 136 for (RangingSession session : rangingSessions) { 137 mRangingTaskManager.post( 138 ()->session.appForegroundStateUpdated(isForeground)); 139 } 140 } 141 } 142 registerUidImportanceTransitions()143 private void registerUidImportanceTransitions() { 144 mActivityManager.addOnUidImportanceListener(this, IMPORTANCE_FOREGROUND_SERVICE); 145 } 146 147 registerCapabilitiesCallback(IRangingCapabilitiesCallback capabilitiesCallback)148 public void registerCapabilitiesCallback(IRangingCapabilitiesCallback capabilitiesCallback) { 149 Log.w(TAG, "Registering ranging capabilities callback"); 150 mRangingInjector 151 .getCapabilitiesProvider() 152 .registerCapabilitiesCallback(capabilitiesCallback); 153 } 154 unregisterCapabilitiesCallback(IRangingCapabilitiesCallback capabilitiesCallback)155 public void unregisterCapabilitiesCallback(IRangingCapabilitiesCallback capabilitiesCallback) { 156 mRangingInjector 157 .getCapabilitiesProvider() 158 .unregisterCapabilitiesCallback(capabilitiesCallback); 159 } 160 startRanging( AttributionSource attributionSource, SessionHandle handle, RangingPreference preference, IRangingCallbacks callbacks )161 public void startRanging( 162 AttributionSource attributionSource, SessionHandle handle, RangingPreference preference, 163 IRangingCallbacks callbacks 164 ) { 165 RangingTaskManager.StartRangingArgs args = new RangingTaskManager.StartRangingArgs( 166 attributionSource, handle, preference, callbacks); 167 mRangingTaskManager.enqueueTask(RangingTask.TASK_START_RANGING, args); 168 } 169 addRawPeer(SessionHandle handle, RawResponderRangingConfig params)170 public void addRawPeer(SessionHandle handle, RawResponderRangingConfig params) { 171 if (!mSessions.containsKey(handle)) { 172 Log.e(TAG, "Failed to add peer. Ranging session not found"); 173 return; 174 } 175 DynamicPeer peer = new DynamicPeer(params, 176 mSessions.get(handle), null /* Ranging device is in params*/); 177 mRangingTaskManager.enqueueTask(RangingTask.TASK_ADD_DEVICE, peer); 178 } 179 removePeer(SessionHandle handle, RangingDevice device)180 public void removePeer(SessionHandle handle, RangingDevice device) { 181 if (!mSessions.containsKey(handle)) { 182 Log.e(TAG, "Failed to remove peer. Ranging session not found"); 183 return; 184 } 185 DynamicPeer peer = new DynamicPeer(null /* params not needed*/, mSessions.get(handle), 186 device); 187 mRangingTaskManager.enqueueTask(RangingTask.TASK_REMOVE_DEVICE, peer); 188 } 189 reconfigureInterval(SessionHandle handle, int intervalSkipCount)190 public void reconfigureInterval(SessionHandle handle, int intervalSkipCount) { 191 if (!mSessions.containsKey(handle)) { 192 Log.e(TAG, "Failed to reconfigure ranging interval. Ranging session not found"); 193 } 194 mRangingTaskManager.enqueueTask(RangingTask.TASK_RECONFIGURE_INTERVAL, 195 mSessions.get(handle), intervalSkipCount); 196 } 197 stopRanging(SessionHandle handle)198 public void stopRanging(SessionHandle handle) { 199 RangingSession session = mSessions.get(handle); 200 if (session == null) { 201 Log.e(TAG, "stopRanging for nonexistent session"); 202 return; 203 } 204 mRangingTaskManager.enqueueTask(RangingTask.TASK_STOP_RANGING, session); 205 } 206 207 /** 208 * Received data from the peer device. 209 * 210 * @param oobHandle unique session/device pair identifier. 211 * @param data payload 212 */ oobDataReceived(OobHandle oobHandle, byte[] data)213 public void oobDataReceived(OobHandle oobHandle, byte[] data) { 214 mRangingInjector.getOobController().handleOobDataReceived(oobHandle, data); 215 } 216 217 /** 218 * Device disconnected from the OOB channel. 219 * 220 * @param oobHandle unique session/device pair identifier. 221 */ deviceOobDisconnected(OobHandle oobHandle)222 public void deviceOobDisconnected(OobHandle oobHandle) { 223 mRangingInjector.getOobController().handleOobDeviceDisconnected(oobHandle); 224 } 225 226 /** 227 * Device reconnected to the OOB channe:l 228 * 229 * @param oobHandle unique session/device pair identifier. 230 */ deviceOobReconnected(OobHandle oobHandle)231 public void deviceOobReconnected(OobHandle oobHandle) { 232 mRangingInjector.getOobController().handleOobDeviceReconnected(oobHandle); 233 } 234 235 /** 236 * Device closed the OOB channel. 237 * 238 * @param oobHandle unique session/device pair identifier. 239 */ deviceOobClosed(OobHandle oobHandle)240 public void deviceOobClosed(OobHandle oobHandle) { 241 mRangingInjector.getOobController().handleOobClosed(oobHandle); 242 } 243 244 /** 245 * Register send data listener. 246 * 247 * @param oobDataSender listener for sending the data via OOB. 248 */ registerOobSendDataListener(IOobSendDataListener oobDataSender)249 public void registerOobSendDataListener(IOobSendDataListener oobDataSender) { 250 mRangingInjector.getOobController().registerDataSender(oobDataSender); 251 } 252 253 /** 254 * Listens for peer-specific events within a session and translates them to 255 * {@link IRangingCallbacks} calls. 256 */ 257 public class SessionListener implements IBinder.DeathRecipient { 258 private final SessionHandle mSessionHandle; 259 private final IRangingCallbacks mRangingCallbacks; 260 private final SessionMetricsLogger mMetricsLogger; 261 private final StateMachine<State> mStateMachine; 262 SessionListener( SessionHandle sessionHandle, IRangingCallbacks callbacks, SessionMetricsLogger metricsLogger )263 SessionListener( 264 SessionHandle sessionHandle, IRangingCallbacks callbacks, 265 SessionMetricsLogger metricsLogger 266 ) { 267 mSessionHandle = sessionHandle; 268 mRangingCallbacks = callbacks; 269 mMetricsLogger = metricsLogger; 270 mStateMachine = new StateMachine<>(State.CLOSED); 271 try { 272 mRangingCallbacks.asBinder().linkToDeath(this, 0); 273 } catch (RemoteException e) { 274 Log.e(TAG, "Failed to link to death: " + sessionHandle, e); 275 stopRanging(mSessionHandle); 276 } 277 } 278 279 @Override binderDied()280 public void binderDied() { 281 Log.i(TAG, "binderDied : Stopping session: " + mSessionHandle); 282 stopRanging(mSessionHandle); 283 } 284 onConfigurationComplete( @onNull ImmutableSet<TechnologyConfig> configs )285 public synchronized void onConfigurationComplete( 286 @NonNull ImmutableSet<TechnologyConfig> configs 287 ) { 288 if (mStateMachine.transition(State.CLOSED, State.CONFIGURED)) { 289 mMetricsLogger.logSessionConfigured(configs.size()); 290 } 291 } 292 onSessionOpened()293 public synchronized void onSessionOpened() { 294 if (mStateMachine.transition(State.CONFIGURED, State.ACTIVE)) { 295 mMetricsLogger.logSessionStarted(); 296 try { 297 mRangingCallbacks.onOpened(mSessionHandle); 298 } catch (RemoteException e) { 299 Log.e(TAG, "onOpened callback failed: " + e); 300 } 301 } 302 } 303 onTechnologyStarted( @onNull RangingTechnology technology, @NonNull Set<RangingDevice> peers )304 public synchronized void onTechnologyStarted( 305 @NonNull RangingTechnology technology, @NonNull Set<RangingDevice> peers 306 ) { 307 Log.v(TAG, "onTechnologyStarted " + technology + " for " + peers.size() + " peer(s)"); 308 // Enforce that the session is already opened. 309 onSessionOpened(); 310 mMetricsLogger.logTechnologyStarted(technology, peers.size()); 311 peers.forEach((peer) -> { 312 try { 313 mRangingCallbacks.onStarted(mSessionHandle, peer, technology.getValue()); 314 } catch (RemoteException e) { 315 Log.e(TAG, "onTechnologyStarted callback failed: " + e); 316 } 317 }); 318 } 319 onTechnologyStopped( @onNull RangingTechnology technology, @NonNull Set<RangingDevice> peers, @InternalReason int reason )320 public synchronized void onTechnologyStopped( 321 @NonNull RangingTechnology technology, @NonNull Set<RangingDevice> peers, 322 @InternalReason int reason 323 ) { 324 Log.v(TAG, "onTechnologyStopped " + technology + " for " + peers.size() + " peer(s)"); 325 mMetricsLogger.logTechnologyStopped(technology, peers.size(), reason); 326 peers.forEach((peer) -> { 327 try { 328 mRangingCallbacks.onStopped(mSessionHandle, peer, technology.getValue()); 329 } catch (RemoteException e) { 330 Log.e(TAG, "onTechnologyStopped callback failed: " + e); 331 } 332 }); 333 } 334 onResults( @onNull RangingDevice peer, @NonNull RangingData data )335 public void onResults( 336 @NonNull RangingDevice peer, @NonNull RangingData data 337 ) { 338 try { 339 mRangingCallbacks.onResults(mSessionHandle, peer, data); 340 } catch (RemoteException e) { 341 Log.e(TAG, "onData callback failed: " + e); 342 } 343 } 344 onSessionClosed(@nternalReason int reason)345 public synchronized void onSessionClosed(@InternalReason int reason) { 346 Log.v(TAG, "onSessionClosed reason " + reason); 347 mSessions.remove(mSessionHandle).close(); 348 mMetricsLogger.logSessionClosed(reason); 349 if (mStateMachine.getAndSet(State.CLOSED) == State.ACTIVE) { 350 try { 351 mRangingCallbacks.onClosed(mSessionHandle, convertReason(reason)); 352 } catch (RemoteException e) { 353 Log.e(TAG, "onClosed callback failed: " + e); 354 } 355 } else { 356 try { 357 mRangingCallbacks.onOpenFailed(mSessionHandle, convertReason(reason)); 358 } catch (RemoteException e) { 359 Log.e(TAG, "onOpenFailed callback failed: " + e); 360 } 361 } 362 } 363 convertReason(@nternalReason int reason)364 private @Callback.Reason int convertReason(@InternalReason int reason) { 365 return switch (reason) { 366 case InternalReason.UNKNOWN, InternalReason.LOCAL_REQUEST, 367 InternalReason.REMOTE_REQUEST, InternalReason.UNSUPPORTED, 368 InternalReason.SYSTEM_POLICY, InternalReason.NO_PEERS_FOUND -> reason; 369 case InternalReason.INTERNAL_ERROR -> Callback.REASON_UNKNOWN; 370 case InternalReason.BACKGROUND_RANGING_POLICY -> Callback.REASON_SYSTEM_POLICY; 371 case InternalReason.PEER_CAPABILITIES_MISMATCH -> Callback.REASON_UNSUPPORTED; 372 default -> Callback.REASON_UNKNOWN; 373 }; 374 } 375 376 private enum State { 377 CLOSED, 378 CONFIGURED, 379 ACTIVE 380 } 381 } 382 383 private class RangingTaskManager extends Handler { RangingTaskManager(Looper looper)384 RangingTaskManager(Looper looper) { 385 super(looper); 386 } 387 enqueueTask(RangingTask task, Object obj)388 public void enqueueTask(RangingTask task, Object obj) { 389 Message msg = mRangingTaskManager.obtainMessage(); 390 msg.what = task.getValue(); 391 msg.obj = obj; 392 this.sendMessage(msg); 393 } 394 enqueueTask(RangingTask task, Object obj, int arg1)395 public void enqueueTask(RangingTask task, Object obj, int arg1) { 396 Message msg = mRangingTaskManager.obtainMessage(); 397 msg.what = task.getValue(); 398 msg.obj = obj; 399 msg.arg1 = arg1; 400 this.sendMessage(msg); 401 } 402 403 @Override handleMessage(Message msg)404 public void handleMessage(Message msg) { 405 RangingTask task = RangingTask.fromValue(msg.what); 406 switch (task) { 407 case TASK_START_RANGING -> handleStartRanging((StartRangingArgs) msg.obj); 408 case TASK_STOP_RANGING -> { 409 RangingSession rangingSession = (RangingSession) msg.obj; 410 rangingSession.stop(); 411 } 412 case TASK_ADD_DEVICE -> { 413 DynamicPeer peer = (DynamicPeer) msg.obj; 414 peer.mSession.addPeer(peer.mParams); 415 } 416 case TASK_REMOVE_DEVICE -> { 417 DynamicPeer peer = (DynamicPeer) msg.obj; 418 peer.mSession.removePeer(peer.mRangingDevice); 419 } 420 case TASK_RECONFIGURE_INTERVAL -> { 421 RangingSession session = (RangingSession) msg.obj; 422 session.reconfigureInterval(msg.arg1); 423 } 424 } 425 } 426 StartRangingArgs( AttributionSource attributionSource, SessionHandle handle, RangingPreference preference, IRangingCallbacks callbacks )427 public record StartRangingArgs( 428 AttributionSource attributionSource, 429 SessionHandle handle, 430 RangingPreference preference, 431 IRangingCallbacks callbacks 432 ) { 433 } 434 handleStartRanging(StartRangingArgs args)435 public void handleStartRanging(StartRangingArgs args) { 436 RangingSessionConfig config = new RangingSessionConfig.Builder() 437 .setDeviceRole(args.preference.getDeviceRole()) 438 .setSessionConfig(args.preference().getSessionConfig()) 439 .build(); 440 441 RangingConfig baseParams = args.preference.getRangingParams(); 442 SessionListener listener = new SessionListener( 443 args.handle, args.callbacks, 444 SessionMetricsLogger.startLogging( 445 args.handle, config.getDeviceRole(), baseParams.getRangingSessionType(), 446 args.attributionSource, mRangingInjector)); 447 448 switch (baseParams) { 449 case RawInitiatorRangingConfig params -> startSession(params, args, 450 new RawInitiatorRangingSession(args.attributionSource, args.handle, 451 mRangingInjector, config, listener, mAdapterExecutor)); 452 case RawResponderRangingConfig params -> startSession(params, args, 453 new RawResponderRangingSession(args.attributionSource, args.handle, 454 mRangingInjector, config, listener, mAdapterExecutor)); 455 case OobInitiatorRangingConfig params -> startSession(params, args, 456 new OobInitiatorRangingSession(args.attributionSource, args.handle, 457 mRangingInjector, config, listener, mAdapterExecutor, 458 mOobExecutor)); 459 case OobResponderRangingConfig params -> startSession(params, args, 460 new OobResponderRangingSession(args.attributionSource, args.handle, 461 mRangingInjector, config, listener, mAdapterExecutor, 462 mOobExecutor)); 463 default -> { 464 Log.e(TAG, "Unknown configuration object " + baseParams.getClass()); 465 listener.onSessionClosed(InternalReason.INTERNAL_ERROR); 466 } 467 } 468 } 469 } 470 startSession( RangingConfig params, RangingTaskManager.StartRangingArgs args, RangingSession rangingSession )471 public void startSession( 472 RangingConfig params, 473 RangingTaskManager.StartRangingArgs args, 474 RangingSession rangingSession 475 ) { 476 AttributionSource attributionSource = mRangingInjector 477 .getAnyNonPrivilegedAppInAttributionSource(args.attributionSource); 478 if (attributionSource != null) { 479 synchronized (mNonPrivilegedUidToSessionsTable) { 480 List<RangingSession> session = mNonPrivilegedUidToSessionsTable.computeIfAbsent( 481 attributionSource.getUid(), v -> new ArrayList<>()); 482 session.add(rangingSession); 483 } 484 } 485 mSessions.put(args.handle, rangingSession); 486 rangingSession.start(params); 487 } 488 489 public static final class DynamicPeer { 490 public final RangingDevice mRangingDevice; 491 public final RawResponderRangingConfig mParams; 492 public final RangingSession mSession; 493 DynamicPeer(RawResponderRangingConfig params, RangingSession session, RangingDevice device)494 public DynamicPeer(RawResponderRangingConfig params, RangingSession session, 495 RangingDevice device) { 496 mParams = params; 497 mSession = session; 498 mRangingDevice = device; 499 } 500 } 501 dump(FileDescriptor fd, PrintWriter pw, String[] args)502 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 503 pw.println("---- Dump of RangingServiceManager ----"); 504 for (RangingSession session : mSessions.values()) { 505 session.dump(fd, pw, args); 506 } 507 pw.println("---- Dump of RangingServiceManager ----"); 508 mRangingInjector.getCapabilitiesProvider().dump(fd, pw, args); 509 } 510 } 511