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