1 /* 2 * Copyright (C) 2014 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; 18 19 import android.Manifest.permission; 20 import android.annotation.Nullable; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.ServiceConnection; 28 import android.content.pm.PackageManager; 29 import android.database.ContentObserver; 30 import android.location.LocationManager; 31 import android.net.INetworkRecommendationProvider; 32 import android.net.INetworkScoreCache; 33 import android.net.INetworkScoreService; 34 import android.net.NetworkKey; 35 import android.net.NetworkScoreManager; 36 import android.net.NetworkScorerAppData; 37 import android.net.ScoredNetwork; 38 import android.net.Uri; 39 import android.net.wifi.ScanResult; 40 import android.net.wifi.WifiInfo; 41 import android.net.wifi.WifiManager; 42 import android.net.wifi.WifiScanner; 43 import android.os.Binder; 44 import android.os.Build; 45 import android.os.Handler; 46 import android.os.IBinder; 47 import android.os.Looper; 48 import android.os.Message; 49 import android.os.RemoteCallbackList; 50 import android.os.RemoteException; 51 import android.os.UserHandle; 52 import android.provider.Settings.Global; 53 import android.text.TextUtils; 54 import android.util.ArrayMap; 55 import android.util.ArraySet; 56 import android.util.Log; 57 58 import com.android.internal.annotations.GuardedBy; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.content.PackageMonitor; 61 import com.android.internal.util.DumpUtils; 62 import com.android.server.pm.permission.LegacyPermissionManagerInternal; 63 64 import java.io.FileDescriptor; 65 import java.io.PrintWriter; 66 import java.util.ArrayList; 67 import java.util.Collection; 68 import java.util.Collections; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Set; 72 import java.util.function.BiConsumer; 73 import java.util.function.Function; 74 import java.util.function.Supplier; 75 import java.util.function.UnaryOperator; 76 77 /** 78 * Backing service for {@link android.net.NetworkScoreManager}. 79 * @hide 80 */ 81 public class NetworkScoreService extends INetworkScoreService.Stub { 82 private static final String TAG = "NetworkScoreService"; 83 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG); 84 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE); 85 86 private final Context mContext; 87 private final NetworkScorerAppManager mNetworkScorerAppManager; 88 @GuardedBy("mScoreCaches") 89 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches; 90 /** Lock used to update mPackageMonitor when scorer package changes occur. */ 91 private final Object mPackageMonitorLock = new Object(); 92 private final Object mServiceConnectionLock = new Object(); 93 private final Handler mHandler; 94 private final DispatchingContentObserver mRecommendationSettingsObserver; 95 private final ContentObserver mUseOpenWifiPackageObserver; 96 private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer; 97 98 @GuardedBy("mPackageMonitorLock") 99 private NetworkScorerPackageMonitor mPackageMonitor; 100 @GuardedBy("mServiceConnectionLock") 101 private ScoringServiceConnection mServiceConnection; 102 103 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { 104 @Override 105 public void onReceive(Context context, Intent intent) { 106 final String action = intent.getAction(); 107 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 108 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId); 109 if (userId == UserHandle.USER_NULL) return; 110 111 if (Intent.ACTION_USER_UNLOCKED.equals(action)) { 112 onUserUnlocked(userId); 113 } 114 } 115 }; 116 117 private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() { 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 final String action = intent.getAction(); 121 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) { 122 refreshBinding(); 123 } 124 } 125 }; 126 127 public static final class Lifecycle extends SystemService { 128 private final NetworkScoreService mService; 129 Lifecycle(Context context)130 public Lifecycle(Context context) { 131 super(context); 132 mService = new NetworkScoreService(context); 133 } 134 135 @Override onStart()136 public void onStart() { 137 Log.i(TAG, "Registering " + Context.NETWORK_SCORE_SERVICE); 138 publishBinderService(Context.NETWORK_SCORE_SERVICE, mService); 139 } 140 141 @Override onBootPhase(int phase)142 public void onBootPhase(int phase) { 143 if (phase == PHASE_SYSTEM_SERVICES_READY) { 144 mService.systemReady(); 145 } else if (phase == PHASE_BOOT_COMPLETED) { 146 mService.systemRunning(); 147 } 148 } 149 } 150 151 /** 152 * Clears scores when the active scorer package is no longer valid and 153 * manages the service connection. 154 */ 155 private class NetworkScorerPackageMonitor extends PackageMonitor { 156 final String mPackageToWatch; 157 NetworkScorerPackageMonitor(String packageToWatch)158 private NetworkScorerPackageMonitor(String packageToWatch) { 159 mPackageToWatch = packageToWatch; 160 } 161 162 @Override onPackageAdded(String packageName, int uid)163 public void onPackageAdded(String packageName, int uid) { 164 evaluateBinding(packageName, true /* forceUnbind */); 165 } 166 167 @Override onPackageRemoved(String packageName, int uid)168 public void onPackageRemoved(String packageName, int uid) { 169 evaluateBinding(packageName, true /* forceUnbind */); 170 } 171 172 @Override onPackageModified(String packageName)173 public void onPackageModified(String packageName) { 174 evaluateBinding(packageName, false /* forceUnbind */); 175 } 176 177 @Override onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)178 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 179 if (doit) { // "doit" means the force stop happened instead of just being queried for. 180 for (String packageName : packages) { 181 evaluateBinding(packageName, true /* forceUnbind */); 182 } 183 } 184 return super.onHandleForceStop(intent, packages, uid, doit); 185 } 186 187 @Override onPackageUpdateFinished(String packageName, int uid)188 public void onPackageUpdateFinished(String packageName, int uid) { 189 evaluateBinding(packageName, true /* forceUnbind */); 190 } 191 evaluateBinding(String changedPackageName, boolean forceUnbind)192 private void evaluateBinding(String changedPackageName, boolean forceUnbind) { 193 if (!mPackageToWatch.equals(changedPackageName)) { 194 // Early exit when we don't care about the package that has changed. 195 return; 196 } 197 198 if (DBG) { 199 Log.d(TAG, "Evaluating binding for: " + changedPackageName 200 + ", forceUnbind=" + forceUnbind); 201 } 202 203 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); 204 if (activeScorer == null) { 205 // Package change has invalidated a scorer, this will also unbind any service 206 // connection. 207 if (DBG) Log.d(TAG, "No active scorers available."); 208 refreshBinding(); 209 } else { // The scoring service changed in some way. 210 if (forceUnbind) { 211 unbindFromScoringServiceIfNeeded(); 212 } 213 if (DBG) { 214 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent() 215 + " if needed."); 216 } 217 bindToScoringServiceIfNeeded(activeScorer); 218 } 219 } 220 } 221 222 /** 223 * Dispatches observed content changes to a handler for further processing. 224 */ 225 @VisibleForTesting 226 public static class DispatchingContentObserver extends ContentObserver { 227 final private Map<Uri, Integer> mUriEventMap; 228 final private Context mContext; 229 final private Handler mHandler; 230 DispatchingContentObserver(Context context, Handler handler)231 public DispatchingContentObserver(Context context, Handler handler) { 232 super(handler); 233 mContext = context; 234 mHandler = handler; 235 mUriEventMap = new ArrayMap<>(); 236 } 237 observe(Uri uri, int what)238 void observe(Uri uri, int what) { 239 mUriEventMap.put(uri, what); 240 final ContentResolver resolver = mContext.getContentResolver(); 241 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this); 242 } 243 244 @Override onChange(boolean selfChange)245 public void onChange(boolean selfChange) { 246 onChange(selfChange, null); 247 } 248 249 @Override onChange(boolean selfChange, Uri uri)250 public void onChange(boolean selfChange, Uri uri) { 251 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri)); 252 final Integer what = mUriEventMap.get(uri); 253 if (what != null) { 254 mHandler.obtainMessage(what).sendToTarget(); 255 } else { 256 Log.w(TAG, "No matching event to send for URI = " + uri); 257 } 258 } 259 } 260 NetworkScoreService(Context context)261 public NetworkScoreService(Context context) { 262 this(context, new NetworkScorerAppManager(context), 263 ScoringServiceConnection::new, Looper.myLooper()); 264 } 265 266 @VisibleForTesting NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager, Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer, Looper looper)267 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager, 268 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer, 269 Looper looper) { 270 mContext = context; 271 mNetworkScorerAppManager = networkScoreAppManager; 272 mScoreCaches = new ArrayMap<>(); 273 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); 274 // TODO: Need to update when we support per-user scorers. http://b/23422763 275 mContext.registerReceiverAsUser( 276 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/, 277 null /* scheduler */); 278 mHandler = new ServiceHandler(looper); 279 IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION); 280 mContext.registerReceiverAsUser( 281 mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter, 282 null /* broadcastPermission*/, mHandler); 283 mRecommendationSettingsObserver = new DispatchingContentObserver(context, mHandler); 284 mServiceConnProducer = serviceConnProducer; 285 mUseOpenWifiPackageObserver = new ContentObserver(mHandler) { 286 @Override 287 public void onChange(boolean selfChange, Uri uri, int userId) { 288 Uri useOpenWifiPkgUri = Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE); 289 if (useOpenWifiPkgUri.equals(uri)) { 290 String useOpenWifiPackage = Global.getString(mContext.getContentResolver(), 291 Global.USE_OPEN_WIFI_PACKAGE); 292 if (!TextUtils.isEmpty(useOpenWifiPackage)) { 293 LocalServices.getService(LegacyPermissionManagerInternal.class) 294 .grantDefaultPermissionsToDefaultUseOpenWifiApp(useOpenWifiPackage, 295 userId); 296 } 297 } 298 } 299 }; 300 mContext.getContentResolver().registerContentObserver( 301 Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE), 302 false /*notifyForDescendants*/, 303 mUseOpenWifiPackageObserver); 304 // Set a callback for the package manager to query the use open wifi app. 305 LocalServices.getService(LegacyPermissionManagerInternal.class) 306 .setUseOpenWifiAppPackagesProvider((userId) -> { 307 String useOpenWifiPackage = Global.getString(mContext.getContentResolver(), 308 Global.USE_OPEN_WIFI_PACKAGE); 309 if (!TextUtils.isEmpty(useOpenWifiPackage)) { 310 return new String[]{useOpenWifiPackage}; 311 } 312 return null; 313 }); 314 } 315 316 /** Called when the system is ready to run third-party code but before it actually does so. */ systemReady()317 void systemReady() { 318 if (DBG) Log.d(TAG, "systemReady"); 319 registerRecommendationSettingsObserver(); 320 } 321 322 /** Called when the system is ready for us to start third-party code. */ systemRunning()323 void systemRunning() { 324 if (DBG) Log.d(TAG, "systemRunning"); 325 } 326 327 @VisibleForTesting onUserUnlocked(int userId)328 void onUserUnlocked(int userId) { 329 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")"); 330 refreshBinding(); 331 } 332 refreshBinding()333 private void refreshBinding() { 334 if (DBG) Log.d(TAG, "refreshBinding()"); 335 // Make sure the scorer is up-to-date 336 mNetworkScorerAppManager.updateState(); 337 mNetworkScorerAppManager.migrateNetworkScorerAppSettingIfNeeded(); 338 registerPackageMonitorIfNeeded(); 339 bindToScoringServiceIfNeeded(); 340 } 341 registerRecommendationSettingsObserver()342 private void registerRecommendationSettingsObserver() { 343 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE); 344 mRecommendationSettingsObserver.observe(packageNameUri, 345 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED); 346 347 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); 348 mRecommendationSettingsObserver.observe(settingUri, 349 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED); 350 } 351 352 /** 353 * Ensures the package manager is registered to monitor the current active scorer. 354 * If a discrepancy is found any previous monitor will be cleaned up 355 * and a new monitor will be created. 356 * 357 * This method is idempotent. 358 */ registerPackageMonitorIfNeeded()359 private void registerPackageMonitorIfNeeded() { 360 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()"); 361 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer(); 362 synchronized (mPackageMonitorLock) { 363 // Unregister the current monitor if needed. 364 if (mPackageMonitor != null && (appData == null 365 || !appData.getRecommendationServicePackageName().equals( 366 mPackageMonitor.mPackageToWatch))) { 367 if (DBG) { 368 Log.d(TAG, "Unregistering package monitor for " 369 + mPackageMonitor.mPackageToWatch); 370 } 371 mPackageMonitor.unregister(); 372 mPackageMonitor = null; 373 } 374 375 // Create and register the monitor if a scorer is active. 376 if (appData != null && mPackageMonitor == null) { 377 mPackageMonitor = new NetworkScorerPackageMonitor( 378 appData.getRecommendationServicePackageName()); 379 // TODO: Need to update when we support per-user scorers. http://b/23422763 380 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM, 381 false /* externalStorage */); 382 if (DBG) { 383 Log.d(TAG, "Registered package monitor for " 384 + mPackageMonitor.mPackageToWatch); 385 } 386 } 387 } 388 } 389 bindToScoringServiceIfNeeded()390 private void bindToScoringServiceIfNeeded() { 391 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded"); 392 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer(); 393 bindToScoringServiceIfNeeded(scorerData); 394 } 395 396 /** 397 * Ensures the service connection is bound to the current active scorer. 398 * If a discrepancy is found any previous connection will be cleaned up 399 * and a new connection will be created. 400 * 401 * This method is idempotent. 402 */ bindToScoringServiceIfNeeded(NetworkScorerAppData appData)403 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) { 404 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")"); 405 if (appData != null) { 406 synchronized (mServiceConnectionLock) { 407 // If we're connected to a different component then drop it. 408 if (mServiceConnection != null 409 && !mServiceConnection.getAppData().equals(appData)) { 410 unbindFromScoringServiceIfNeeded(); 411 } 412 413 // If we're not connected at all then create a new connection. 414 if (mServiceConnection == null) { 415 mServiceConnection = mServiceConnProducer.apply(appData); 416 } 417 418 // Make sure the connection is connected (idempotent) 419 mServiceConnection.bind(mContext); 420 } 421 } else { // otherwise make sure it isn't bound. 422 unbindFromScoringServiceIfNeeded(); 423 } 424 } 425 unbindFromScoringServiceIfNeeded()426 private void unbindFromScoringServiceIfNeeded() { 427 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded"); 428 synchronized (mServiceConnectionLock) { 429 if (mServiceConnection != null) { 430 mServiceConnection.unbind(mContext); 431 if (DBG) Log.d(TAG, "Disconnected from: " 432 + mServiceConnection.getAppData().getRecommendationServiceComponent()); 433 } 434 mServiceConnection = null; 435 } 436 clearInternal(); 437 } 438 439 @Override updateScores(ScoredNetwork[] networks)440 public boolean updateScores(ScoredNetwork[] networks) { 441 if (!isCallerActiveScorer(getCallingUid())) { 442 throw new SecurityException("Caller with UID " + getCallingUid() + 443 " is not the active scorer."); 444 } 445 446 final long token = Binder.clearCallingIdentity(); 447 try { 448 // Separate networks by type. 449 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>(); 450 for (ScoredNetwork network : networks) { 451 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type); 452 if (networkList == null) { 453 networkList = new ArrayList<>(); 454 networksByType.put(network.networkKey.type, networkList); 455 } 456 networkList.add(network); 457 } 458 459 // Pass the scores of each type down to the appropriate network scorer. 460 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) { 461 final RemoteCallbackList<INetworkScoreCache> callbackList; 462 final boolean isEmpty; 463 synchronized (mScoreCaches) { 464 callbackList = mScoreCaches.get(entry.getKey()); 465 isEmpty = callbackList == null 466 || callbackList.getRegisteredCallbackCount() == 0; 467 } 468 469 if (isEmpty) { 470 if (Log.isLoggable(TAG, Log.VERBOSE)) { 471 Log.v(TAG, "No scorer registered for type " + entry.getKey() 472 + ", discarding"); 473 } 474 continue; 475 } 476 477 final BiConsumer<INetworkScoreCache, Object> consumer = 478 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(), 479 entry.getKey()); 480 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList)); 481 } 482 483 return true; 484 } finally { 485 Binder.restoreCallingIdentity(token); 486 } 487 } 488 489 /** 490 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork} 491 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the 492 * accepted {@link INetworkScoreCache} implementation. 493 */ 494 @VisibleForTesting 495 static class FilteringCacheUpdatingConsumer 496 implements BiConsumer<INetworkScoreCache, Object> { 497 private final Context mContext; 498 private final List<ScoredNetwork> mScoredNetworkList; 499 private final int mNetworkType; 500 // TODO: 1/23/17 - Consider a Map if we implement more filters. 501 // These are created on-demand to defer the construction cost until 502 // an instance is actually needed. 503 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter; 504 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter; 505 create(Context context, List<ScoredNetwork> scoredNetworkList, int networkType)506 static FilteringCacheUpdatingConsumer create(Context context, 507 List<ScoredNetwork> scoredNetworkList, int networkType) { 508 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType, 509 null, null); 510 } 511 512 @VisibleForTesting FilteringCacheUpdatingConsumer(Context context, List<ScoredNetwork> scoredNetworkList, int networkType, UnaryOperator<List<ScoredNetwork>> currentNetworkFilter, UnaryOperator<List<ScoredNetwork>> scanResultsFilter)513 FilteringCacheUpdatingConsumer(Context context, 514 List<ScoredNetwork> scoredNetworkList, int networkType, 515 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter, 516 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) { 517 mContext = context; 518 mScoredNetworkList = scoredNetworkList; 519 mNetworkType = networkType; 520 mCurrentNetworkFilter = currentNetworkFilter; 521 mScanResultsFilter = scanResultsFilter; 522 } 523 524 @Override accept(INetworkScoreCache networkScoreCache, Object cookie)525 public void accept(INetworkScoreCache networkScoreCache, Object cookie) { 526 int filterType = NetworkScoreManager.SCORE_FILTER_NONE; 527 if (cookie instanceof Integer) { 528 filterType = (Integer) cookie; 529 } 530 531 try { 532 final List<ScoredNetwork> filteredNetworkList = 533 filterScores(mScoredNetworkList, filterType); 534 if (!filteredNetworkList.isEmpty()) { 535 networkScoreCache.updateScores(filteredNetworkList); 536 } 537 } catch (RemoteException e) { 538 if (VERBOSE) { 539 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e); 540 } 541 } 542 } 543 544 /** 545 * Applies the appropriate filter and returns the filtered results. 546 */ filterScores(List<ScoredNetwork> scoredNetworkList, int filterType)547 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList, 548 int filterType) { 549 switch (filterType) { 550 case NetworkScoreManager.SCORE_FILTER_NONE: 551 return scoredNetworkList; 552 553 case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK: 554 if (mCurrentNetworkFilter == null) { 555 mCurrentNetworkFilter = 556 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext)); 557 } 558 return mCurrentNetworkFilter.apply(scoredNetworkList); 559 560 case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS: 561 if (mScanResultsFilter == null) { 562 mScanResultsFilter = new ScanResultsScoreCacheFilter( 563 new ScanResultsSupplier(mContext)); 564 } 565 return mScanResultsFilter.apply(scoredNetworkList); 566 567 default: 568 Log.w(TAG, "Unknown filter type: " + filterType); 569 return scoredNetworkList; 570 } 571 } 572 } 573 574 /** 575 * Helper class that improves the testability of the cache filter Functions. 576 */ 577 private static class WifiInfoSupplier implements Supplier<WifiInfo> { 578 private final Context mContext; 579 WifiInfoSupplier(Context context)580 WifiInfoSupplier(Context context) { 581 mContext = context; 582 } 583 584 @Override get()585 public WifiInfo get() { 586 WifiManager wifiManager = mContext.getSystemService(WifiManager.class); 587 if (wifiManager != null) { 588 return wifiManager.getConnectionInfo(); 589 } 590 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo."); 591 return null; 592 } 593 } 594 595 /** 596 * Helper class that improves the testability of the cache filter Functions. 597 */ 598 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> { 599 private final Context mContext; 600 ScanResultsSupplier(Context context)601 ScanResultsSupplier(Context context) { 602 mContext = context; 603 } 604 605 @Override get()606 public List<ScanResult> get() { 607 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class); 608 if (wifiScanner != null) { 609 return wifiScanner.getSingleScanResults(); 610 } 611 Log.w(TAG, "WifiScanner is null, failed to return scan results."); 612 return Collections.emptyList(); 613 } 614 } 615 616 /** 617 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the 618 * {@link ScoredNetwork} associated with the current network. If no network is connected the 619 * returned list will be empty. 620 * <p> 621 * Note: this filter performs some internal caching for consistency and performance. The 622 * current network is determined at construction time and never changed. Also, the 623 * last filtered list is saved so if the same input is provided multiple times in a row 624 * the computation is only done once. 625 */ 626 @VisibleForTesting 627 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> { 628 private final NetworkKey mCurrentNetwork; 629 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier)630 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) { 631 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get()); 632 } 633 634 @Override apply(List<ScoredNetwork> scoredNetworks)635 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) { 636 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) { 637 return Collections.emptyList(); 638 } 639 640 for (int i = 0; i < scoredNetworks.size(); i++) { 641 final ScoredNetwork scoredNetwork = scoredNetworks.get(i); 642 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) { 643 return Collections.singletonList(scoredNetwork); 644 } 645 } 646 647 return Collections.emptyList(); 648 } 649 } 650 651 /** 652 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the 653 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s. 654 * If there are no {@link ScanResult}s the returned list will be empty. 655 * <p> 656 * Note: this filter performs some internal caching for consistency and performance. The 657 * current set of ScanResults is determined at construction time and never changed. 658 * Also, the last filtered list is saved so if the same input is provided multiple 659 * times in a row the computation is only done once. 660 */ 661 @VisibleForTesting 662 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> { 663 private final Set<NetworkKey> mScanResultKeys; 664 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier)665 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) { 666 List<ScanResult> scanResults = resultsSupplier.get(); 667 final int size = scanResults.size(); 668 mScanResultKeys = new ArraySet<>(size); 669 for (int i = 0; i < size; i++) { 670 ScanResult scanResult = scanResults.get(i); 671 NetworkKey key = NetworkKey.createFromScanResult(scanResult); 672 if (key != null) { 673 mScanResultKeys.add(key); 674 } 675 } 676 } 677 678 @Override apply(List<ScoredNetwork> scoredNetworks)679 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) { 680 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) { 681 return Collections.emptyList(); 682 } 683 684 List<ScoredNetwork> filteredScores = new ArrayList<>(); 685 for (int i = 0; i < scoredNetworks.size(); i++) { 686 final ScoredNetwork scoredNetwork = scoredNetworks.get(i); 687 if (mScanResultKeys.contains(scoredNetwork.networkKey)) { 688 filteredScores.add(scoredNetwork); 689 } 690 } 691 692 return filteredScores; 693 } 694 } 695 696 @Override clearScores()697 public boolean clearScores() { 698 // Only the active scorer or the system should be allowed to flush all scores. 699 enforceSystemOrIsActiveScorer(getCallingUid()); 700 final long token = Binder.clearCallingIdentity(); 701 try { 702 clearInternal(); 703 return true; 704 } finally { 705 Binder.restoreCallingIdentity(token); 706 } 707 } 708 709 @Override setActiveScorer(String packageName)710 public boolean setActiveScorer(String packageName) { 711 enforceSystemOrHasScoreNetworks(); 712 return mNetworkScorerAppManager.setActiveScorer(packageName); 713 } 714 715 /** 716 * Determine whether the application with the given UID is the enabled scorer. 717 * 718 * @param callingUid the UID to check 719 * @return true if the provided UID is the active scorer, false otherwise. 720 */ 721 @Override isCallerActiveScorer(int callingUid)722 public boolean isCallerActiveScorer(int callingUid) { 723 synchronized (mServiceConnectionLock) { 724 return mServiceConnection != null 725 && mServiceConnection.getAppData().packageUid == callingUid; 726 } 727 } 728 enforceSystemOnly()729 private void enforceSystemOnly() throws SecurityException { 730 // REQUEST_NETWORK_SCORES is a signature only permission. 731 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, 732 "Caller must be granted REQUEST_NETWORK_SCORES."); 733 } 734 enforceSystemOrHasScoreNetworks()735 private void enforceSystemOrHasScoreNetworks() throws SecurityException { 736 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) 737 != PackageManager.PERMISSION_GRANTED 738 && mContext.checkCallingOrSelfPermission(permission.SCORE_NETWORKS) 739 != PackageManager.PERMISSION_GRANTED) { 740 throw new SecurityException( 741 "Caller is neither the system process or a network scorer."); 742 } 743 } 744 enforceSystemOrIsActiveScorer(int callingUid)745 private void enforceSystemOrIsActiveScorer(int callingUid) throws SecurityException { 746 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) 747 != PackageManager.PERMISSION_GRANTED 748 && !isCallerActiveScorer(callingUid)) { 749 throw new SecurityException( 750 "Caller is neither the system process or the active network scorer."); 751 } 752 } 753 754 /** 755 * Obtain the package name of the current active network scorer. 756 * 757 * @return the full package name of the current active scorer, or null if there is no active 758 * scorer. 759 */ 760 @Override getActiveScorerPackage()761 public String getActiveScorerPackage() { 762 enforceSystemOrHasScoreNetworks(); 763 NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer(); 764 if (appData == null) { 765 return null; 766 } 767 return appData.getRecommendationServicePackageName(); 768 } 769 770 /** 771 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer. 772 */ 773 @Override getActiveScorer()774 public NetworkScorerAppData getActiveScorer() { 775 // Only the system can access this data. 776 enforceSystemOnly(); 777 return mNetworkScorerAppManager.getActiveScorer(); 778 } 779 780 /** 781 * Returns the list of available scorer apps. The list will be empty if there are 782 * no valid scorers. 783 */ 784 @Override getAllValidScorers()785 public List<NetworkScorerAppData> getAllValidScorers() { 786 // Only the system can access this data. 787 enforceSystemOnly(); 788 return mNetworkScorerAppManager.getAllValidScorers(); 789 } 790 791 @Override disableScoring()792 public void disableScoring() { 793 // Only the active scorer or the system should be allowed to disable scoring. 794 enforceSystemOrIsActiveScorer(getCallingUid()); 795 // no-op for now but we could write to the setting if needed. 796 } 797 798 /** Clear scores. Callers are responsible for checking permissions as appropriate. */ clearInternal()799 private void clearInternal() { 800 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() { 801 @Override 802 public void accept(INetworkScoreCache networkScoreCache, Object cookie) { 803 try { 804 networkScoreCache.clearScores(); 805 } catch (RemoteException e) { 806 if (Log.isLoggable(TAG, Log.VERBOSE)) { 807 Log.v(TAG, "Unable to clear scores", e); 808 } 809 } 810 } 811 }, getScoreCacheLists()); 812 } 813 814 @Override registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache, int filterType)815 public void registerNetworkScoreCache(int networkType, 816 INetworkScoreCache scoreCache, 817 int filterType) { 818 enforceSystemOnly(); 819 final long token = Binder.clearCallingIdentity(); 820 try { 821 synchronized (mScoreCaches) { 822 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType); 823 if (callbackList == null) { 824 callbackList = new RemoteCallbackList<>(); 825 mScoreCaches.put(networkType, callbackList); 826 } 827 if (!callbackList.register(scoreCache, filterType)) { 828 if (callbackList.getRegisteredCallbackCount() == 0) { 829 mScoreCaches.remove(networkType); 830 } 831 if (Log.isLoggable(TAG, Log.VERBOSE)) { 832 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType); 833 } 834 } 835 } 836 } finally { 837 Binder.restoreCallingIdentity(token); 838 } 839 } 840 841 @Override unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache)842 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { 843 enforceSystemOnly(); 844 final long token = Binder.clearCallingIdentity(); 845 try { 846 synchronized (mScoreCaches) { 847 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType); 848 if (callbackList == null || !callbackList.unregister(scoreCache)) { 849 if (Log.isLoggable(TAG, Log.VERBOSE)) { 850 Log.v(TAG, "Unable to unregister NetworkScoreCache for type " 851 + networkType); 852 } 853 } else if (callbackList.getRegisteredCallbackCount() == 0) { 854 mScoreCaches.remove(networkType); 855 } 856 } 857 } finally { 858 Binder.restoreCallingIdentity(token); 859 } 860 } 861 862 @Override requestScores(NetworkKey[] networks)863 public boolean requestScores(NetworkKey[] networks) { 864 enforceSystemOnly(); 865 final long token = Binder.clearCallingIdentity(); 866 try { 867 final INetworkRecommendationProvider provider = getRecommendationProvider(); 868 if (provider != null) { 869 try { 870 provider.requestScores(networks); 871 // TODO: 12/15/16 - Consider pushing null scores into the cache to 872 // prevent repeated requests for the same scores. 873 return true; 874 } catch (RemoteException e) { 875 Log.w(TAG, "Failed to request scores.", e); 876 // TODO: 12/15/16 - Keep track of failures. 877 } 878 } 879 return false; 880 } finally { 881 Binder.restoreCallingIdentity(token); 882 } 883 } 884 885 @Override dump(final FileDescriptor fd, final PrintWriter writer, final String[] args)886 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) { 887 if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; 888 final long token = Binder.clearCallingIdentity(); 889 try { 890 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer(); 891 if (currentScorer == null) { 892 writer.println("Scoring is disabled."); 893 return; 894 } 895 writer.println("Current scorer: " + currentScorer); 896 897 synchronized (mServiceConnectionLock) { 898 if (mServiceConnection != null) { 899 mServiceConnection.dump(fd, writer, args); 900 } else { 901 writer.println("ScoringServiceConnection: null"); 902 } 903 } 904 writer.flush(); 905 } finally { 906 Binder.restoreCallingIdentity(token); 907 } 908 } 909 910 /** 911 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active. 912 * 913 * <p>May be used to perform an action on all score caches without potentially strange behavior 914 * if a new scorer is registered during that action's execution. 915 */ getScoreCacheLists()916 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() { 917 synchronized (mScoreCaches) { 918 return new ArrayList<>(mScoreCaches.values()); 919 } 920 } 921 sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer, Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists)922 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer, 923 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) { 924 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) { 925 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList 926 final int count = callbackList.beginBroadcast(); 927 try { 928 for (int i = 0; i < count; i++) { 929 consumer.accept(callbackList.getBroadcastItem(i), 930 callbackList.getBroadcastCookie(i)); 931 } 932 } finally { 933 callbackList.finishBroadcast(); 934 } 935 } 936 } 937 } 938 939 @Nullable getRecommendationProvider()940 private INetworkRecommendationProvider getRecommendationProvider() { 941 synchronized (mServiceConnectionLock) { 942 if (mServiceConnection != null) { 943 return mServiceConnection.getRecommendationProvider(); 944 } 945 } 946 return null; 947 } 948 949 // The class and methods need to be public for Mockito to work. 950 @VisibleForTesting 951 public static class ScoringServiceConnection implements ServiceConnection { 952 private final NetworkScorerAppData mAppData; 953 private volatile boolean mBound = false; 954 private volatile boolean mConnected = false; 955 private volatile INetworkRecommendationProvider mRecommendationProvider; 956 ScoringServiceConnection(NetworkScorerAppData appData)957 ScoringServiceConnection(NetworkScorerAppData appData) { 958 mAppData = appData; 959 } 960 961 @VisibleForTesting bind(Context context)962 public void bind(Context context) { 963 if (!mBound) { 964 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); 965 service.setComponent(mAppData.getRecommendationServiceComponent()); 966 mBound = context.bindServiceAsUser(service, this, 967 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 968 UserHandle.SYSTEM); 969 if (!mBound) { 970 Log.w(TAG, "Bind call failed for " + service); 971 context.unbindService(this); 972 } else { 973 if (DBG) Log.d(TAG, "ScoringServiceConnection bound."); 974 } 975 } 976 } 977 978 @VisibleForTesting unbind(Context context)979 public void unbind(Context context) { 980 try { 981 if (mBound) { 982 mBound = false; 983 context.unbindService(this); 984 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound."); 985 } 986 } catch (RuntimeException e) { 987 Log.e(TAG, "Unbind failed.", e); 988 } 989 990 mConnected = false; 991 mRecommendationProvider = null; 992 } 993 994 @VisibleForTesting getAppData()995 public NetworkScorerAppData getAppData() { 996 return mAppData; 997 } 998 999 @VisibleForTesting getRecommendationProvider()1000 public INetworkRecommendationProvider getRecommendationProvider() { 1001 return mRecommendationProvider; 1002 } 1003 1004 @VisibleForTesting getPackageName()1005 public String getPackageName() { 1006 return mAppData.getRecommendationServiceComponent().getPackageName(); 1007 } 1008 1009 @VisibleForTesting isAlive()1010 public boolean isAlive() { 1011 return mBound && mConnected; 1012 } 1013 1014 @Override onServiceConnected(ComponentName name, IBinder service)1015 public void onServiceConnected(ComponentName name, IBinder service) { 1016 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString()); 1017 mConnected = true; 1018 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service); 1019 } 1020 1021 @Override onServiceDisconnected(ComponentName name)1022 public void onServiceDisconnected(ComponentName name) { 1023 if (DBG) { 1024 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString()); 1025 } 1026 mConnected = false; 1027 mRecommendationProvider = null; 1028 } 1029 dump(FileDescriptor fd, PrintWriter writer, String[] args)1030 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 1031 writer.println("ScoringServiceConnection: " 1032 + mAppData.getRecommendationServiceComponent() 1033 + ", bound: " + mBound 1034 + ", connected: " + mConnected); 1035 } 1036 } 1037 1038 @VisibleForTesting 1039 public final class ServiceHandler extends Handler { 1040 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 1; 1041 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 2; 1042 ServiceHandler(Looper looper)1043 public ServiceHandler(Looper looper) { 1044 super(looper); 1045 } 1046 1047 @Override handleMessage(Message msg)1048 public void handleMessage(Message msg) { 1049 final int what = msg.what; 1050 switch (what) { 1051 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED: 1052 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED: 1053 refreshBinding(); 1054 break; 1055 1056 default: 1057 Log.w(TAG,"Unknown message: " + what); 1058 } 1059 } 1060 } 1061 } 1062