2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.server;
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.content.pm.PackageManagerInternal;
30 import android.database.ContentObserver;
31 import android.location.LocationManager;
32 import android.net.INetworkRecommendationProvider;
33 import android.net.INetworkScoreCache;
34 import android.net.INetworkScoreService;
35 import android.net.NetworkKey;
36 import android.net.NetworkScoreManager;
37 import android.net.NetworkScorerAppData;
38 import android.net.ScoredNetwork;
39 import android.net.Uri;
40 import android.net.wifi.ScanResult;
41 import android.net.wifi.WifiInfo;
42 import android.net.wifi.WifiManager;
43 import android.net.wifi.WifiScanner;
44 import android.os.Binder;
45 import android.os.Build;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.Looper;
49 import android.os.Message;
50 import android.os.RemoteCallbackList;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.provider.Settings.Global;
54 import android.text.TextUtils;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 import android.util.IntArray;
58 import android.util.Log;
60 import com.android.internal.annotations.GuardedBy;
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.internal.content.PackageMonitor;
63 import com.android.internal.os.TransferPipe;
64 import com.android.internal.telephony.SmsApplication;
65 import com.android.internal.util.DumpUtils;
67 import java.io.FileDescriptor;
68 import java.io.IOException;
69 import java.io.PrintWriter;
70 import java.util.ArrayList;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.List;
76 import java.util.function.BiConsumer;
77 import java.util.function.Function;
78 import java.util.function.Supplier;
79 import java.util.function.UnaryOperator;
82 * Backing service for {@link android.net.NetworkScoreManager}.
85 public 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);
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;
102 @GuardedBy("mPackageMonitorLock")
103 private NetworkScorerPackageMonitor mPackageMonitor;
104 @GuardedBy("mServiceConnectionLock")
105 private ScoringServiceConnection mServiceConnection;
107 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
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;
115 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
116 onUserUnlocked(userId);
121 private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
123 public void onReceive(Context context, Intent intent) {
124 final String action = intent.getAction();
125 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
131 public static final class Lifecycle extends SystemService {
132 private final NetworkScoreService mService;
134 public Lifecycle(Context context) {
136 mService = new NetworkScoreService(context);
140 public void onStart() {
141 Log.i(TAG, "Registering " + Context.NETWORK_SCORE_SERVICE);
142 publishBinderService(Context.NETWORK_SCORE_SERVICE, mService);
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();
156 * Clears scores when the active scorer package is no longer valid and
157 * manages the service connection.
159 private class NetworkScorerPackageMonitor extends PackageMonitor {
160 final String mPackageToWatch;
162 private NetworkScorerPackageMonitor(String packageToWatch) {
163 mPackageToWatch = packageToWatch;
167 public void onPackageAdded(String packageName, int uid) {
168 evaluateBinding(packageName, true /* forceUnbind */);
172 public void onPackageRemoved(String packageName, int uid) {
173 evaluateBinding(packageName, true /* forceUnbind */);
177 public void onPackageModified(String packageName) {
178 evaluateBinding(packageName, false /* forceUnbind */);
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 */);
188 return super.onHandleForceStop(intent, packages, uid, doit);
192 public void onPackageUpdateFinished(String packageName, int uid) {
193 evaluateBinding(packageName, true /* forceUnbind */);
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.
203 Log.d(TAG, "Evaluating binding for: " + changedPackageName
204 + ", forceUnbind=" + forceUnbind);
207 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
208 if (activeScorer == null) {
209 // Package change has invalidated a scorer, this will also unbind any service
211 if (DBG) Log.d(TAG, "No active scorers available.");
213 } else { // The scoring service changed in some way.
215 unbindFromScoringServiceIfNeeded();
218 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
221 bindToScoringServiceIfNeeded(activeScorer);
227 * Dispatches observed content changes to a handler for further processing.
230 public static class DispatchingContentObserver extends ContentObserver {
231 final private Map<Uri, Integer> mUriEventMap;
232 final private Context mContext;
233 final private Handler mHandler;
235 public DispatchingContentObserver(Context context, Handler handler) {
239 mUriEventMap = new ArrayMap<>();
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);
249 public void onChange(boolean selfChange) {
250 onChange(selfChange, null);
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);
258 mHandler.obtainMessage(what).sendToTarget();
260 Log.w(TAG, "No matching event to send for URI = " + uri);
265 public NetworkScoreService(Context context) {
266 this(context, new NetworkScorerAppManager(context),
267 ScoringServiceConnection::new, Looper.myLooper());
271 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
272 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer,
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) {
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,
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() {
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};
323 /** Called when the system is ready to run third-party code but before it actually does so. */
325 if (DBG) Log.d(TAG, "systemReady");
326 registerRecommendationSettingsObserver();
329 /** Called when the system is ready for us to start third-party code. */
330 void systemRunning() {
331 if (DBG) Log.d(TAG, "systemRunning");
335 void onUserUnlocked(int userId) {
336 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
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();
349 private void registerRecommendationSettingsObserver() {
350 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
351 mRecommendationSettingsObserver.observe(packageNameUri,
352 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
354 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
355 mRecommendationSettingsObserver.observe(settingUri,
356 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
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.
364 * This method is idempotent.
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))) {
375 Log.d(TAG, "Unregistering package monitor for "
376 + mPackageMonitor.mPackageToWatch);
378 mPackageMonitor.unregister();
379 mPackageMonitor = null;
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 */);
390 Log.d(TAG, "Registered package monitor for "
391 + mPackageMonitor.mPackageToWatch);
397 private void bindToScoringServiceIfNeeded() {
398 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
399 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
400 bindToScoringServiceIfNeeded(scorerData);
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.
408 * This method is idempotent.
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();
420 // If we're not connected at all then create a new connection.
421 if (mServiceConnection == null) {
422 mServiceConnection = mServiceConnProducer.apply(appData);
425 // Make sure the connection is connected (idempotent)
426 mServiceConnection.bind(mContext);
428 } else { // otherwise make sure it isn't bound.
429 unbindFromScoringServiceIfNeeded();
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());
441 mServiceConnection = null;
447 public boolean updateScores(ScoredNetwork[] networks) {
448 if (!isCallerActiveScorer(getCallingUid())) {
449 throw new SecurityException("Caller with UID " + getCallingUid() +
450 " is not the active scorer.");
453 final long token = Binder.clearCallingIdentity();
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);
463 networkList.add(network);
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;
477 if (Log.isLoggable(TAG, Log.VERBOSE)) {
478 Log.v(TAG, "No scorer registered for type " + entry.getKey()
484 final BiConsumer<INetworkScoreCache, Object> consumer =
485 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
487 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
492 Binder.restoreCallingIdentity(token);
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.
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;
513 static FilteringCacheUpdatingConsumer create(Context context,
514 List<ScoredNetwork> scoredNetworkList, int networkType) {
515 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
520 FilteringCacheUpdatingConsumer(Context context,
521 List<ScoredNetwork> scoredNetworkList, int networkType,
522 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
523 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
525 mScoredNetworkList = scoredNetworkList;
526 mNetworkType = networkType;
527 mCurrentNetworkFilter = currentNetworkFilter;
528 mScanResultsFilter = scanResultsFilter;
532 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
533 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
534 if (cookie instanceof Integer) {
535 filterType = (Integer) cookie;
539 final List<ScoredNetwork> filteredNetworkList =
540 filterScores(mScoredNetworkList, filterType);
541 if (!filteredNetworkList.isEmpty()) {
542 networkScoreCache.updateScores(filteredNetworkList);
544 } catch (RemoteException e) {
546 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
552 * Applies the appropriate filter and returns the filtered results.
554 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
556 switch (filterType) {
557 case NetworkScoreManager.CACHE_FILTER_NONE:
558 return scoredNetworkList;
560 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
561 if (mCurrentNetworkFilter == null) {
562 mCurrentNetworkFilter =
563 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
565 return mCurrentNetworkFilter.apply(scoredNetworkList);
567 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
568 if (mScanResultsFilter == null) {
569 mScanResultsFilter = new ScanResultsScoreCacheFilter(
570 new ScanResultsSupplier(mContext));
572 return mScanResultsFilter.apply(scoredNetworkList);
575 Log.w(TAG, "Unknown filter type: " + filterType);
576 return scoredNetworkList;
582 * Helper class that improves the testability of the cache filter Functions.
584 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
585 private final Context mContext;
587 WifiInfoSupplier(Context context) {
592 public WifiInfo get() {
593 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
594 if (wifiManager != null) {
595 return wifiManager.getConnectionInfo();
597 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
603 * Helper class that improves the testability of the cache filter Functions.
605 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
606 private final Context mContext;
608 ScanResultsSupplier(Context context) {
613 public List<ScanResult> get() {
614 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
615 if (wifiScanner != null) {
616 return wifiScanner.getSingleScanResults();
618 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
619 return Collections.emptyList();
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.
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.
634 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
635 private final NetworkKey mCurrentNetwork;
637 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
638 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
642 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
643 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
644 return Collections.emptyList();
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);
654 return Collections.emptyList();
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.
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.
669 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
670 private final Set<NetworkKey> mScanResultKeys;
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);
680 mScanResultKeys.add(key);
686 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
687 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
688 return Collections.emptyList();
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);
699 return filteredScores;
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();
712 Binder.restoreCallingIdentity(token);
717 public boolean setActiveScorer(String packageName) {
718 enforceSystemOrHasScoreNetworks();
719 return mNetworkScorerAppManager.setActiveScorer(packageName);
723 * Determine whether the application with the given UID is the enabled scorer.
725 * @param callingUid the UID to check
726 * @return true if the provided UID is the active scorer, false otherwise.
729 public boolean isCallerActiveScorer(int callingUid) {
730 synchronized (mServiceConnectionLock) {
731 return mServiceConnection != null
732 && mServiceConnection.getAppData().packageUid == callingUid;
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.");
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.");
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.");
762 * Obtain the package name of the current active network scorer.
764 * @return the full package name of the current active scorer, or null if there is no active
768 public String getActiveScorerPackage() {
769 enforceSystemOrHasScoreNetworks();
770 synchronized (mServiceConnectionLock) {
771 if (mServiceConnection != null) {
772 return mServiceConnection.getPackageName();
779 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
782 public NetworkScorerAppData getActiveScorer() {
783 // Only the system can access this data.
785 synchronized (mServiceConnectionLock) {
786 if (mServiceConnection != null) {
787 return mServiceConnection.getAppData();
795 * Returns the list of available scorer apps. The list will be empty if there are
799 public List<NetworkScorerAppData> getAllValidScorers() {
800 // Only the system can access this data.
802 return mNetworkScorerAppManager.getAllValidScorers();
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.
812 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
813 private void clearInternal() {
814 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
816 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
818 networkScoreCache.clearScores();
819 } catch (RemoteException e) {
820 if (Log.isLoggable(TAG, Log.VERBOSE)) {
821 Log.v(TAG, "Unable to clear scores", e);
825 }, getScoreCacheLists());
829 public void registerNetworkScoreCache(int networkType,
830 INetworkScoreCache scoreCache,
833 final long token = Binder.clearCallingIdentity();
835 synchronized (mScoreCaches) {
836 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
837 if (callbackList == null) {
838 callbackList = new RemoteCallbackList<>();
839 mScoreCaches.put(networkType, callbackList);
841 if (!callbackList.register(scoreCache, filterType)) {
842 if (callbackList.getRegisteredCallbackCount() == 0) {
843 mScoreCaches.remove(networkType);
845 if (Log.isLoggable(TAG, Log.VERBOSE)) {
846 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
851 Binder.restoreCallingIdentity(token);
856 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
858 final long token = Binder.clearCallingIdentity();
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 "
867 } else if (callbackList.getRegisteredCallbackCount() == 0) {
868 mScoreCaches.remove(networkType);
872 Binder.restoreCallingIdentity(token);
877 public boolean requestScores(NetworkKey[] networks) {
879 final long token = Binder.clearCallingIdentity();
881 final INetworkRecommendationProvider provider = getRecommendationProvider();
882 if (provider != null) {
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.
888 } catch (RemoteException e) {
889 Log.w(TAG, "Failed to request scores.", e);
890 // TODO: 12/15/16 - Keep track of failures.
895 Binder.restoreCallingIdentity(token);
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();
904 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
905 if (currentScorer == null) {
906 writer.println("Scoring is disabled.");
909 writer.println("Current scorer: " + currentScorer);
911 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
913 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
915 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
916 } catch (IOException | RemoteException e) {
917 writer.println("Failed to dump score cache: " + e);
920 }, getScoreCacheLists());
922 synchronized (mServiceConnectionLock) {
923 if (mServiceConnection != null) {
924 mServiceConnection.dump(fd, writer, args);
926 writer.println("ScoringServiceConnection: null");
931 Binder.restoreCallingIdentity(token);
936 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
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.
941 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
942 synchronized (mScoreCaches) {
943 return new ArrayList<>(mScoreCaches.values());
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();
953 for (int i = 0; i < count; i++) {
954 consumer.accept(callbackList.getBroadcastItem(i),
955 callbackList.getBroadcastCookie(i));
958 callbackList.finishBroadcast();
965 private INetworkRecommendationProvider getRecommendationProvider() {
966 synchronized (mServiceConnectionLock) {
967 if (mServiceConnection != null) {
968 return mServiceConnection.getRecommendationProvider();
974 // The class and methods need to be public for Mockito to work.
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;
982 ScoringServiceConnection(NetworkScorerAppData appData) {
987 public void bind(Context context) {
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,
995 Log.w(TAG, "Bind call failed for " + service);
996 context.unbindService(this);
998 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1004 public void unbind(Context context) {
1008 context.unbindService(this);
1009 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
1011 } catch (RuntimeException e) {
1012 Log.e(TAG, "Unbind failed.", e);
1016 mRecommendationProvider = null;
1020 public NetworkScorerAppData getAppData() {
1025 public INetworkRecommendationProvider getRecommendationProvider() {
1026 return mRecommendationProvider;
1030 public String getPackageName() {
1031 return mAppData.getRecommendationServiceComponent().getPackageName();
1035 public boolean isAlive() {
1036 return mBound && mConnected;
1040 public void onServiceConnected(ComponentName name, IBinder service) {
1041 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
1043 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
1047 public void onServiceDisconnected(ComponentName name) {
1049 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1052 mRecommendationProvider = null;
1055 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1056 writer.println("ScoringServiceConnection: "
1057 + mAppData.getRecommendationServiceComponent()
1058 + ", bound: " + mBound
1059 + ", connected: " + mConnected);
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;
1068 public ServiceHandler(Looper looper) {
1073 public void handleMessage(Message msg) {
1074 final int what = msg.what;
1076 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
1077 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
1082 Log.w(TAG,"Unknown message: " + what);