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.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.pm.PackageManager;
     26 import android.net.INetworkScoreCache;
     27 import android.net.INetworkScoreService;
     28 import android.net.NetworkScoreManager;
     29 import android.net.NetworkScorerAppManager;
     30 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
     31 import android.net.ScoredNetwork;
     32 import android.os.Binder;
     33 import android.os.PatternMatcher;
     34 import android.os.RemoteException;
     35 import android.os.UserHandle;
     36 import android.provider.Settings;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 
     40 import com.android.internal.R;
     41 import com.android.internal.annotations.GuardedBy;
     42 
     43 import java.io.FileDescriptor;
     44 import java.io.PrintWriter;
     45 import java.util.ArrayList;
     46 import java.util.HashMap;
     47 import java.util.HashSet;
     48 import java.util.List;
     49 import java.util.Map;
     50 import java.util.Set;
     51 
     52 /**
     53  * Backing service for {@link android.net.NetworkScoreManager}.
     54  * @hide
     55  */
     56 public class NetworkScoreService extends INetworkScoreService.Stub {
     57     private static final String TAG = "NetworkScoreService";
     58 
     59     private final Context mContext;
     60 
     61     private final Map<Integer, INetworkScoreCache> mScoreCaches;
     62 
     63     /** Lock used to update mReceiver when scorer package changes occur. */
     64     private Object mReceiverLock = new Object[0];
     65 
     66     /** Clears scores when the active scorer package is no longer valid. */
     67     @GuardedBy("mReceiverLock")
     68     private ScorerChangedReceiver mReceiver;
     69 
     70     private class ScorerChangedReceiver extends BroadcastReceiver {
     71         final String mRegisteredPackage;
     72 
     73         ScorerChangedReceiver(String packageName) {
     74             mRegisteredPackage = packageName;
     75         }
     76 
     77         @Override
     78         public void onReceive(Context context, Intent intent) {
     79             String action = intent.getAction();
     80             if ((Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
     81                     Intent.ACTION_PACKAGE_REPLACED.equals(action) ||
     82                     Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) &&
     83                     NetworkScorerAppManager.getActiveScorer(mContext) == null) {
     84                 // Package change has invalidated a scorer.
     85                 Log.i(TAG, "Package " + mRegisteredPackage +
     86                         " is no longer valid, disabling scoring");
     87                 setScorerInternal(null);
     88             }
     89         }
     90     }
     91 
     92     public NetworkScoreService(Context context) {
     93         mContext = context;
     94         mScoreCaches = new HashMap<>();
     95     }
     96 
     97     /** Called when the system is ready to run third-party code but before it actually does so. */
     98     void systemReady() {
     99         ContentResolver cr = mContext.getContentResolver();
    100         if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
    101             // On first run, we try to initialize the scorer to the one configured at build time.
    102             // This will be a no-op if the scorer isn't actually valid.
    103             String defaultPackage = mContext.getResources().getString(
    104                     R.string.config_defaultNetworkScorerPackageName);
    105             if (!TextUtils.isEmpty(defaultPackage)) {
    106                 NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
    107             }
    108             Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
    109         }
    110 
    111         registerPackageReceiverIfNeeded();
    112     }
    113 
    114     private void registerPackageReceiverIfNeeded() {
    115         NetworkScorerAppData scorer = NetworkScorerAppManager.getActiveScorer(mContext);
    116         synchronized (mReceiverLock) {
    117             // Unregister the receiver if the current scorer has changed since last registration.
    118             if (mReceiver != null) {
    119                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    120                     Log.v(TAG, "Unregistering receiver for " + mReceiver.mRegisteredPackage);
    121                 }
    122                 mContext.unregisterReceiver(mReceiver);
    123                 mReceiver = null;
    124             }
    125 
    126             // Register receiver if a scorer is active.
    127             if (scorer != null) {
    128                 IntentFilter filter = new IntentFilter();
    129                 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    130                 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
    131                 filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
    132                 filter.addDataScheme("package");
    133                 filter.addDataSchemeSpecificPart(scorer.mPackageName,
    134                         PatternMatcher.PATTERN_LITERAL);
    135                 mReceiver = new ScorerChangedReceiver(scorer.mPackageName);
    136                 // TODO: Need to update when we support per-user scorers.
    137                 mContext.registerReceiverAsUser(mReceiver, UserHandle.OWNER, filter, null, null);
    138                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    139                     Log.v(TAG, "Registered receiver for " + scorer.mPackageName);
    140                 }
    141             }
    142         }
    143     }
    144 
    145     @Override
    146     public boolean updateScores(ScoredNetwork[] networks) {
    147         if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
    148             throw new SecurityException("Caller with UID " + getCallingUid() +
    149                     " is not the active scorer.");
    150         }
    151 
    152         // Separate networks by type.
    153         Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
    154         for (ScoredNetwork network : networks) {
    155             List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
    156             if (networkList == null) {
    157                 networkList = new ArrayList<>();
    158                 networksByType.put(network.networkKey.type, networkList);
    159             }
    160             networkList.add(network);
    161         }
    162 
    163         // Pass the scores of each type down to the appropriate network scorer.
    164         for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
    165             INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
    166             if (scoreCache != null) {
    167                 try {
    168                     scoreCache.updateScores(entry.getValue());
    169                 } catch (RemoteException e) {
    170                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    171                         Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
    172                     }
    173                 }
    174             } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
    175                 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
    176             }
    177         }
    178 
    179         return true;
    180     }
    181 
    182     @Override
    183     public boolean clearScores() {
    184         // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
    185         // should be allowed to flush all scores.
    186         if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
    187                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
    188                         PackageManager.PERMISSION_GRANTED) {
    189             clearInternal();
    190             return true;
    191         } else {
    192             throw new SecurityException(
    193                     "Caller is neither the active scorer nor the scorer manager.");
    194         }
    195     }
    196 
    197     @Override
    198     public boolean setActiveScorer(String packageName) {
    199         // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
    200         // to directly set the scorer app rather than having to use the consent dialog. The
    201         // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
    202         // do the right thing and not enable this feature without explaining it to the user.
    203         // In the future, should this API be opened to 3p apps, we will need to lock this down and
    204         // figure out another way to streamline the UX.
    205 
    206         // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
    207         mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
    208 
    209         return setScorerInternal(packageName);
    210     }
    211 
    212     @Override
    213     public void disableScoring() {
    214         // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
    215         // should be allowed to disable scoring.
    216         if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
    217                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
    218                         PackageManager.PERMISSION_GRANTED) {
    219             // The return value is discarded here because at this point, the call should always
    220             // succeed. The only reason for failure is if the new package is not a valid scorer, but
    221             // we're disabling scoring altogether here.
    222             setScorerInternal(null /* packageName */);
    223         } else {
    224             throw new SecurityException(
    225                     "Caller is neither the active scorer nor the scorer manager.");
    226         }
    227     }
    228 
    229     /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
    230     private boolean setScorerInternal(String packageName) {
    231         long token = Binder.clearCallingIdentity();
    232         try {
    233             // Preemptively clear scores even though the set operation could fail. We do this for
    234             // safety as scores should never be compared across apps; in practice, Settings should
    235             // only be allowing valid apps to be set as scorers, so failure here should be rare.
    236             clearInternal();
    237             boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
    238             if (result) {
    239                 registerPackageReceiverIfNeeded();
    240                 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
    241                 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
    242                 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    243             }
    244             return result;
    245         } finally {
    246             Binder.restoreCallingIdentity(token);
    247         }
    248     }
    249 
    250     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
    251     private void clearInternal() {
    252         Set<INetworkScoreCache> cachesToClear = getScoreCaches();
    253 
    254         for (INetworkScoreCache scoreCache : cachesToClear) {
    255             try {
    256                 scoreCache.clearScores();
    257             } catch (RemoteException e) {
    258                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    259                     Log.v(TAG, "Unable to clear scores", e);
    260                 }
    261             }
    262         }
    263     }
    264 
    265     @Override
    266     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
    267         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
    268         synchronized (mScoreCaches) {
    269             if (mScoreCaches.containsKey(networkType)) {
    270                 throw new IllegalArgumentException(
    271                         "Score cache already registered for type " + networkType);
    272             }
    273             mScoreCaches.put(networkType, scoreCache);
    274         }
    275     }
    276 
    277     @Override
    278     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
    279         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
    280         NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
    281         if (currentScorer == null) {
    282             writer.println("Scoring is disabled.");
    283             return;
    284         }
    285         writer.println("Current scorer: " + currentScorer.mPackageName);
    286         writer.flush();
    287 
    288         for (INetworkScoreCache scoreCache : getScoreCaches()) {
    289             try {
    290                 scoreCache.asBinder().dump(fd, args);
    291             } catch (RemoteException e) {
    292                 writer.println("Unable to dump score cache");
    293                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    294                     Log.v(TAG, "Unable to dump score cache", e);
    295                 }
    296             }
    297         }
    298     }
    299 
    300     /**
    301      * Returns a set of all score caches that are currently active.
    302      *
    303      * <p>May be used to perform an action on all score caches without potentially strange behavior
    304      * if a new scorer is registered during that action's execution.
    305      */
    306     private Set<INetworkScoreCache> getScoreCaches() {
    307         synchronized (mScoreCaches) {
    308             return new HashSet<>(mScoreCaches.values());
    309         }
    310     }
    311 }
    312