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