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