• 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 package com.android.bluetooth.le_scan;
17 
18 import static com.android.bluetooth.Utils.getSystemClock;
19 import static com.android.bluetooth.util.AttributionSourceUtil.getLastAttributionTag;
20 
21 import android.annotation.Nullable;
22 import android.app.PendingIntent;
23 import android.bluetooth.le.IScannerCallback;
24 import android.content.AttributionSource;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.os.IInterface;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.os.WorkSource;
31 import android.util.Log;
32 
33 import com.android.bluetooth.btservice.AdapterService;
34 
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.NoSuchElementException;
39 import java.util.UUID;
40 import java.util.concurrent.ConcurrentLinkedQueue;
41 import java.util.function.BiConsumer;
42 import java.util.function.Predicate;
43 import java.util.stream.Collectors;
44 
45 /** List of our registered scanners. */
46 public class ScannerMap {
47     private static final String TAG = ScannerMap.class.getSimpleName();
48 
49     /** Internal map to keep track of logging information by app name */
50     private final HashMap<Integer, AppScanStats> mAppScanStatsMap = new HashMap<>();
51 
52     private final ConcurrentLinkedQueue<ScannerApp> mApps = new ConcurrentLinkedQueue<>();
53 
54     /** Add an entry to the application context list with a callback. */
add( UUID uuid, AttributionSource source, WorkSource workSource, IScannerCallback callback, AdapterService adapterService, ScanController scanController)55     ScannerApp add(
56             UUID uuid,
57             AttributionSource source,
58             WorkSource workSource,
59             IScannerCallback callback,
60             AdapterService adapterService,
61             ScanController scanController) {
62         return add(uuid, source, workSource, callback, null, adapterService, scanController);
63     }
64 
65     /** Add an entry to the application context list with a pending intent. */
add( UUID uuid, AttributionSource source, ScanController.PendingIntentInfo piInfo, AdapterService adapterService, ScanController scanController)66     ScannerApp add(
67             UUID uuid,
68             AttributionSource source,
69             ScanController.PendingIntentInfo piInfo,
70             AdapterService adapterService,
71             ScanController scanController) {
72         return add(uuid, source, null, null, piInfo, adapterService, scanController);
73     }
74 
add( UUID uuid, AttributionSource source, @Nullable WorkSource workSource, @Nullable IScannerCallback callback, @Nullable ScanController.PendingIntentInfo piInfo, AdapterService adapterService, ScanController scanController)75     private ScannerApp add(
76             UUID uuid,
77             AttributionSource source,
78             @Nullable WorkSource workSource,
79             @Nullable IScannerCallback callback,
80             @Nullable ScanController.PendingIntentInfo piInfo,
81             AdapterService adapterService,
82             ScanController scanController) {
83         int appUid;
84         String appName = null;
85         if (piInfo != null) {
86             appUid = piInfo.callingUid();
87             appName = piInfo.callingPackage();
88         } else {
89             appUid = Binder.getCallingUid();
90             appName = adapterService.getPackageManager().getNameForUid(appUid);
91         }
92         if (appName == null) {
93             // Assign an app name if one isn't found
94             appName = "Unknown App (UID: " + appUid + ")";
95         }
96         AppScanStats appScanStats = mAppScanStatsMap.get(appUid);
97         if (appScanStats == null) {
98             appScanStats =
99                     new AppScanStats(
100                             appName,
101                             workSource,
102                             this,
103                             adapterService,
104                             scanController,
105                             getSystemClock());
106             mAppScanStatsMap.put(appUid, appScanStats);
107         }
108         ScannerApp app =
109                 new ScannerApp(
110                         uuid,
111                         getLastAttributionTag(source),
112                         callback,
113                         piInfo,
114                         appName,
115                         appScanStats);
116         mApps.add(app);
117         appScanStats.isRegistered = true;
118         return app;
119     }
120 
121     /** Remove the context for a given application ID. */
remove(int id)122     void remove(int id) {
123         Iterator<ScannerApp> i = mApps.iterator();
124         while (i.hasNext()) {
125             ScannerApp entry = i.next();
126             if (entry.mId == id) {
127                 entry.cleanup();
128                 i.remove();
129                 break;
130             }
131         }
132     }
133 
134     /** Erases all application context entries. */
clear()135     public void clear() {
136         for (ScannerApp entry : mApps) {
137             entry.cleanup();
138         }
139         mApps.clear();
140     }
141 
142     /** Get Logging info by application UID */
getAppScanStatsByUid(int uid)143     AppScanStats getAppScanStatsByUid(int uid) {
144         return mAppScanStatsMap.get(uid);
145     }
146 
147     /** Get Logging info by ID */
getAppScanStatsById(int id)148     AppScanStats getAppScanStatsById(int id) {
149         ScannerApp temp = (ScannerApp) getById(id);
150         if (temp != null) {
151             return temp.mAppScanStats;
152         }
153         return null;
154     }
155 
156     /** Get an application context by ID. */
getById(int id)157     ScannerApp getById(int id) {
158         ScannerApp app = getAppByPredicate(entry -> entry.mId == id);
159         if (app == null) {
160             Log.e(TAG, "Context not found for ID " + id);
161         }
162         return app;
163     }
164 
165     /** Get an application context by UUID. */
getByUuid(UUID uuid)166     ScannerApp getByUuid(UUID uuid) {
167         ScannerApp app = getAppByPredicate(entry -> entry.mUuid.equals(uuid));
168         if (app == null) {
169             Log.e(TAG, "Context not found for UUID " + uuid);
170         }
171         return app;
172     }
173 
174     /** Get application contexts by the calling app's name. */
getByName(String name)175     List<ScannerApp> getByName(String name) {
176         return mApps.stream()
177                 .filter(app -> app.mName.equals(name))
178                 .collect(Collectors.toUnmodifiableList());
179     }
180 
181     /** Get an application context by the pending intent info object's intent. */
getByPendingIntentInfo(PendingIntent intent)182     ScannerApp getByPendingIntentInfo(PendingIntent intent) {
183         ScannerApp app =
184                 getAppByPredicate(
185                         entry -> entry.mInfo != null && entry.mInfo.intent().equals(intent));
186         if (app == null) {
187             Log.e(TAG, "Context not found for intent " + intent);
188         }
189         return app;
190     }
191 
getAppByPredicate(Predicate<ScannerApp> predicate)192     private ScannerApp getAppByPredicate(Predicate<ScannerApp> predicate) {
193         // Intentionally using a for-loop over a stream for performance.
194         for (ScannerApp app : mApps) {
195             if (predicate.test(app)) {
196                 return app;
197             }
198         }
199         return null;
200     }
201 
202     /** Logs debug information. */
dump(StringBuilder sb)203     public void dump(StringBuilder sb) {
204         sb.append("  Entries: ").append(mAppScanStatsMap.size()).append("\n\n");
205         for (AppScanStats appScanStats : mAppScanStatsMap.values()) {
206             appScanStats.dumpToString(sb);
207         }
208     }
209 
210     /** Logs all apps for debugging. */
dumpApps(StringBuilder sb, BiConsumer<StringBuilder, String> bf)211     public void dumpApps(StringBuilder sb, BiConsumer<StringBuilder, String> bf) {
212         for (ScannerApp entry : mApps) {
213             bf.accept(
214                     sb,
215                     "    app_if: "
216                             + entry.mId
217                             + ", appName: "
218                             + entry.mName
219                             + (entry.mAttributionTag == null
220                                     ? ""
221                                     : ", tag: " + entry.mAttributionTag));
222         }
223     }
224 
225     public static class ScannerApp {
226         /** Context information */
227         @Nullable ScanController.PendingIntentInfo mInfo;
228 
229         /** Statistics for this app */
230         AppScanStats mAppScanStats;
231 
232         /** The UUID of the application */
233         final UUID mUuid;
234 
235         /** The package name of the application */
236         final String mName;
237 
238         /** The last attribution tag in the attribution source chain */
239         @Nullable final String mAttributionTag;
240 
241         /** Application callbacks */
242         @Nullable IScannerCallback mCallback;
243 
244         /** The id of the application */
245         int mId;
246 
247         /** Whether the calling app has location permission */
248         boolean mHasLocationPermission;
249 
250         /** The user handle of the app that started the scan */
251         @Nullable UserHandle mUserHandle;
252 
253         /** Whether the calling app has the network settings permission */
254         boolean mHasNetworkSettingsPermission;
255 
256         /** Whether the calling app has the network setup wizard permission */
257         boolean mHasNetworkSetupWizardPermission;
258 
259         /** Whether the calling app has the network setup wizard permission */
260         boolean mHasScanWithoutLocationPermission;
261 
262         /** Whether the calling app has disavowed the use of bluetooth for location */
263         boolean mHasDisavowedLocation;
264 
265         boolean mEligibleForSanitizedExposureNotification;
266 
267         @Nullable List<String> mAssociatedDevices;
268 
269         /** Death recipient */
270         @Nullable private IBinder.DeathRecipient mDeathRecipient;
271 
272         /** Creates a new app context. */
ScannerApp( UUID uuid, @Nullable String attributionTag, @Nullable IScannerCallback callback, @Nullable ScanController.PendingIntentInfo info, String name, AppScanStats appScanStats)273         ScannerApp(
274                 UUID uuid,
275                 @Nullable String attributionTag,
276                 @Nullable IScannerCallback callback,
277                 @Nullable ScanController.PendingIntentInfo info,
278                 String name,
279                 AppScanStats appScanStats) {
280             this.mUuid = uuid;
281             this.mAttributionTag = attributionTag;
282             this.mCallback = callback;
283             this.mName = name;
284             this.mInfo = info;
285             this.mAppScanStats = appScanStats;
286         }
287 
288         /** Link death recipient */
linkToDeath(IBinder.DeathRecipient deathRecipient)289         void linkToDeath(IBinder.DeathRecipient deathRecipient) {
290             // It might not be a binder object
291             if (mCallback == null) {
292                 return;
293             }
294             try {
295                 IBinder binder = ((IInterface) mCallback).asBinder();
296                 binder.linkToDeath(deathRecipient, 0);
297                 mDeathRecipient = deathRecipient;
298             } catch (RemoteException e) {
299                 Log.e(TAG, "Unable to link deathRecipient for app id " + mId);
300             }
301         }
302 
303         /** Unlink death recipient */
cleanup()304         void cleanup() {
305             if (mDeathRecipient != null) {
306                 try {
307                     IBinder binder = ((IInterface) mCallback).asBinder();
308                     binder.unlinkToDeath(mDeathRecipient, 0);
309                 } catch (NoSuchElementException e) {
310                     Log.e(TAG, "Unable to unlink deathRecipient for app id " + mId);
311                 }
312             }
313             mAppScanStats.isRegistered = false;
314         }
315     }
316 }
317