OSDN Git Service

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