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