• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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