1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server;
18
19import android.Manifest.permission;
20import android.annotation.Nullable;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.ServiceConnection;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManagerInternal;
30import android.database.ContentObserver;
31import android.location.LocationManager;
32import android.net.INetworkRecommendationProvider;
33import android.net.INetworkScoreCache;
34import android.net.INetworkScoreService;
35import android.net.NetworkKey;
36import android.net.NetworkScoreManager;
37import android.net.NetworkScorerAppData;
38import android.net.ScoredNetwork;
39import android.net.Uri;
40import android.net.wifi.ScanResult;
41import android.net.wifi.WifiInfo;
42import android.net.wifi.WifiManager;
43import android.net.wifi.WifiScanner;
44import android.os.Binder;
45import android.os.Build;
46import android.os.Handler;
47import android.os.IBinder;
48import android.os.Looper;
49import android.os.Message;
50import android.os.RemoteCallbackList;
51import android.os.RemoteException;
52import android.os.UserHandle;
53import android.provider.Settings.Global;
54import android.text.TextUtils;
55import android.util.ArrayMap;
56import android.util.ArraySet;
57import android.util.IntArray;
58import android.util.Log;
59
60import com.android.internal.annotations.GuardedBy;
61import com.android.internal.annotations.VisibleForTesting;
62import com.android.internal.content.PackageMonitor;
63import com.android.internal.os.TransferPipe;
64import com.android.internal.telephony.SmsApplication;
65import com.android.internal.util.DumpUtils;
66
67import java.io.FileDescriptor;
68import java.io.IOException;
69import java.io.PrintWriter;
70import java.util.ArrayList;
71import java.util.Collection;
72import java.util.Collections;
73import java.util.List;
74import java.util.Map;
75import java.util.Set;
76import java.util.function.BiConsumer;
77import java.util.function.Function;
78import java.util.function.Supplier;
79import java.util.function.UnaryOperator;
80
81/**
82 * Backing service for {@link android.net.NetworkScoreManager}.
83 * @hide
84 */
85public 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}
1087