OSDN Git Service

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