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