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