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.ComponentName;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.ServiceConnection;
     27 import android.content.pm.PackageManager;
     28 import android.net.INetworkScoreCache;
     29 import android.net.INetworkScoreService;
     30 import android.net.NetworkScoreManager;
     31 import android.net.NetworkScorerAppManager;
     32 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
     33 import android.net.ScoredNetwork;
     34 import android.os.Binder;
     35 import android.os.IBinder;
     36 import android.os.RemoteException;
     37 import android.os.UserHandle;
     38 import android.provider.Settings;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 
     42 import com.android.internal.R;
     43 import com.android.internal.annotations.GuardedBy;
     44 import com.android.internal.content.PackageMonitor;
     45 
     46 import java.io.FileDescriptor;
     47 import java.io.PrintWriter;
     48 import java.util.ArrayList;
     49 import java.util.HashMap;
     50 import java.util.HashSet;
     51 import java.util.List;
     52 import java.util.Map;
     53 import java.util.Set;
     54 
     55 /**
     56  * Backing service for {@link android.net.NetworkScoreManager}.
     57  * @hide
     58  */
     59 public class NetworkScoreService extends INetworkScoreService.Stub {
     60     private static final String TAG = "NetworkScoreService";
     61     private static final boolean DBG = false;
     62 
     63     private final Context mContext;
     64     private final Map<Integer, INetworkScoreCache> mScoreCaches;
     65     /** Lock used to update mPackageMonitor when scorer package changes occur. */
     66     private final Object mPackageMonitorLock = new Object[0];
     67 
     68     @GuardedBy("mPackageMonitorLock")
     69     private NetworkScorerPackageMonitor mPackageMonitor;
     70     private ScoringServiceConnection mServiceConnection;
     71 
     72     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
     73         @Override
     74         public void onReceive(Context context, Intent intent) {
     75             final String action = intent.getAction();
     76             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
     77             if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
     78             if (userId == UserHandle.USER_NULL) return;
     79 
     80             if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
     81                 onUserUnlocked(userId);
     82             }
     83         }
     84     };
     85 
     86     /**
     87      * Clears scores when the active scorer package is no longer valid and
     88      * manages the service connection.
     89      */
     90     private class NetworkScorerPackageMonitor extends PackageMonitor {
     91         final String mRegisteredPackage;
     92 
     93         private NetworkScorerPackageMonitor(String mRegisteredPackage) {
     94             this.mRegisteredPackage = mRegisteredPackage;
     95         }
     96 
     97         @Override
     98         public void onPackageAdded(String packageName, int uid) {
     99             evaluateBinding(packageName, true /* forceUnbind */);
    100         }
    101 
    102         @Override
    103         public void onPackageRemoved(String packageName, int uid) {
    104             evaluateBinding(packageName, true /* forceUnbind */);
    105         }
    106 
    107         @Override
    108         public void onPackageModified(String packageName) {
    109             evaluateBinding(packageName, false /* forceUnbind */);
    110         }
    111 
    112         @Override
    113         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
    114             if (doit) { // "doit" means the force stop happened instead of just being queried for.
    115                 for (String packageName : packages) {
    116                     evaluateBinding(packageName, true /* forceUnbind */);
    117                 }
    118             }
    119             return super.onHandleForceStop(intent, packages, uid, doit);
    120         }
    121 
    122         @Override
    123         public void onPackageUpdateFinished(String packageName, int uid) {
    124             evaluateBinding(packageName, true /* forceUnbind */);
    125         }
    126 
    127         private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
    128             if (mRegisteredPackage.equals(scorerPackageName)) {
    129                 if (DBG) {
    130                     Log.d(TAG, "Evaluating binding for: " + scorerPackageName
    131                             + ", forceUnbind=" + forceUnbind);
    132                 }
    133                 final NetworkScorerAppData activeScorer =
    134                         NetworkScorerAppManager.getActiveScorer(mContext);
    135                 if (activeScorer == null) {
    136                     // Package change has invalidated a scorer, this will also unbind any service
    137                     // connection.
    138                     Log.i(TAG, "Package " + mRegisteredPackage +
    139                             " is no longer valid, disabling scoring.");
    140                     setScorerInternal(null);
    141                 } else if (activeScorer.mScoringServiceClassName == null) {
    142                     // The scoring service is not available, make sure it's unbound.
    143                     unbindFromScoringServiceIfNeeded();
    144                 } else { // The scoring service changed in some way.
    145                     if (forceUnbind) {
    146                         unbindFromScoringServiceIfNeeded();
    147                     }
    148                     bindToScoringServiceIfNeeded(activeScorer);
    149                 }
    150             }
    151         }
    152     }
    153 
    154     public NetworkScoreService(Context context) {
    155         mContext = context;
    156         mScoreCaches = new HashMap<>();
    157         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
    158         // TODO: Need to update when we support per-user scorers. http://b/23422763
    159         mContext.registerReceiverAsUser(
    160                 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
    161                 null /* scheduler */);
    162     }
    163 
    164     /** Called when the system is ready to run third-party code but before it actually does so. */
    165     void systemReady() {
    166         if (DBG) Log.d(TAG, "systemReady");
    167         ContentResolver cr = mContext.getContentResolver();
    168         if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
    169             // On first run, we try to initialize the scorer to the one configured at build time.
    170             // This will be a no-op if the scorer isn't actually valid.
    171             String defaultPackage = mContext.getResources().getString(
    172                     R.string.config_defaultNetworkScorerPackageName);
    173             if (!TextUtils.isEmpty(defaultPackage)) {
    174                 NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
    175             }
    176             Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
    177         }
    178 
    179         registerPackageMonitorIfNeeded();
    180     }
    181 
    182     /** Called when the system is ready for us to start third-party code. */
    183     void systemRunning() {
    184         if (DBG) Log.d(TAG, "systemRunning");
    185         bindToScoringServiceIfNeeded();
    186     }
    187 
    188     private void onUserUnlocked(int userId) {
    189         registerPackageMonitorIfNeeded();
    190         bindToScoringServiceIfNeeded();
    191     }
    192 
    193     private void registerPackageMonitorIfNeeded() {
    194         if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
    195         NetworkScorerAppData scorer = NetworkScorerAppManager.getActiveScorer(mContext);
    196         synchronized (mPackageMonitorLock) {
    197             // Unregister the current monitor if needed.
    198             if (mPackageMonitor != null) {
    199                 if (DBG) {
    200                     Log.d(TAG, "Unregistering package monitor for "
    201                             + mPackageMonitor.mRegisteredPackage);
    202                 }
    203                 mPackageMonitor.unregister();
    204                 mPackageMonitor = null;
    205             }
    206 
    207             // Create and register the monitor if a scorer is active.
    208             if (scorer != null) {
    209                 mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
    210                 // TODO: Need to update when we support per-user scorers. http://b/23422763
    211                 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
    212                         false /* externalStorage */);
    213                 if (DBG) {
    214                     Log.d(TAG, "Registered package monitor for "
    215                             + mPackageMonitor.mRegisteredPackage);
    216                 }
    217             }
    218         }
    219     }
    220 
    221     private void bindToScoringServiceIfNeeded() {
    222         if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
    223         NetworkScorerAppData scorerData = NetworkScorerAppManager.getActiveScorer(mContext);
    224         bindToScoringServiceIfNeeded(scorerData);
    225     }
    226 
    227     private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
    228         if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
    229         if (scorerData != null && scorerData.mScoringServiceClassName != null) {
    230             ComponentName componentName =
    231                     new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
    232             // If we're connected to a different component then drop it.
    233             if (mServiceConnection != null
    234                     && !mServiceConnection.mComponentName.equals(componentName)) {
    235                 unbindFromScoringServiceIfNeeded();
    236             }
    237 
    238             // If we're not connected at all then create a new connection.
    239             if (mServiceConnection == null) {
    240                 mServiceConnection = new ScoringServiceConnection(componentName);
    241             }
    242 
    243             // Make sure the connection is connected (idempotent)
    244             mServiceConnection.connect(mContext);
    245         } else { // otherwise make sure it isn't bound.
    246             unbindFromScoringServiceIfNeeded();
    247         }
    248     }
    249 
    250     private void unbindFromScoringServiceIfNeeded() {
    251         if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
    252         if (mServiceConnection != null) {
    253             mServiceConnection.disconnect(mContext);
    254         }
    255         mServiceConnection = null;
    256     }
    257 
    258     @Override
    259     public boolean updateScores(ScoredNetwork[] networks) {
    260         if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
    261             throw new SecurityException("Caller with UID " + getCallingUid() +
    262                     " is not the active scorer.");
    263         }
    264 
    265         // Separate networks by type.
    266         Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
    267         for (ScoredNetwork network : networks) {
    268             List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
    269             if (networkList == null) {
    270                 networkList = new ArrayList<>();
    271                 networksByType.put(network.networkKey.type, networkList);
    272             }
    273             networkList.add(network);
    274         }
    275 
    276         // Pass the scores of each type down to the appropriate network scorer.
    277         for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
    278             INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
    279             if (scoreCache != null) {
    280                 try {
    281                     scoreCache.updateScores(entry.getValue());
    282                 } catch (RemoteException e) {
    283                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    284                         Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
    285                     }
    286                 }
    287             } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
    288                 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
    289             }
    290         }
    291 
    292         return true;
    293     }
    294 
    295     @Override
    296     public boolean clearScores() {
    297         // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
    298         // should be allowed to flush all scores.
    299         if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
    300                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
    301                         PackageManager.PERMISSION_GRANTED) {
    302             clearInternal();
    303             return true;
    304         } else {
    305             throw new SecurityException(
    306                     "Caller is neither the active scorer nor the scorer manager.");
    307         }
    308     }
    309 
    310     @Override
    311     public boolean setActiveScorer(String packageName) {
    312         // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
    313         // to directly set the scorer app rather than having to use the consent dialog. The
    314         // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
    315         // do the right thing and not enable this feature without explaining it to the user.
    316         // In the future, should this API be opened to 3p apps, we will need to lock this down and
    317         // figure out another way to streamline the UX.
    318 
    319         // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
    320         mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
    321 
    322         return setScorerInternal(packageName);
    323     }
    324 
    325     @Override
    326     public void disableScoring() {
    327         // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
    328         // should be allowed to disable scoring.
    329         if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
    330                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
    331                         PackageManager.PERMISSION_GRANTED) {
    332             // The return value is discarded here because at this point, the call should always
    333             // succeed. The only reason for failure is if the new package is not a valid scorer, but
    334             // we're disabling scoring altogether here.
    335             setScorerInternal(null /* packageName */);
    336         } else {
    337             throw new SecurityException(
    338                     "Caller is neither the active scorer nor the scorer manager.");
    339         }
    340     }
    341 
    342     /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
    343     private boolean setScorerInternal(String packageName) {
    344         if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
    345         long token = Binder.clearCallingIdentity();
    346         try {
    347             unbindFromScoringServiceIfNeeded();
    348             // Preemptively clear scores even though the set operation could fail. We do this for
    349             // safety as scores should never be compared across apps; in practice, Settings should
    350             // only be allowing valid apps to be set as scorers, so failure here should be rare.
    351             clearInternal();
    352             // Get the scorer that is about to be replaced, if any, so we can notify it directly.
    353             NetworkScorerAppData prevScorer = NetworkScorerAppManager.getActiveScorer(mContext);
    354             boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
    355             // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
    356             // then we'll attempt to restore the previous binding (if any), otherwise an attempt
    357             // will be made to bind to the new scorer.
    358             bindToScoringServiceIfNeeded();
    359             if (result) { // new scorer successfully set
    360                 registerPackageMonitorIfNeeded();
    361 
    362                 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
    363                 if (prevScorer != null) { // Directly notify the old scorer.
    364                     intent.setPackage(prevScorer.mPackageName);
    365                     // TODO: Need to update when we support per-user scorers. http://b/23422763
    366                     mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
    367                 }
    368 
    369                 if (packageName != null) { // Then notify the new scorer
    370                     intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
    371                     intent.setPackage(packageName);
    372                     // TODO: Need to update when we support per-user scorers. http://b/23422763
    373                     mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
    374                 }
    375             }
    376             return result;
    377         } finally {
    378             Binder.restoreCallingIdentity(token);
    379         }
    380     }
    381 
    382     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
    383     private void clearInternal() {
    384         Set<INetworkScoreCache> cachesToClear = getScoreCaches();
    385 
    386         for (INetworkScoreCache scoreCache : cachesToClear) {
    387             try {
    388                 scoreCache.clearScores();
    389             } catch (RemoteException e) {
    390                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    391                     Log.v(TAG, "Unable to clear scores", e);
    392                 }
    393             }
    394         }
    395     }
    396 
    397     @Override
    398     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
    399         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
    400         synchronized (mScoreCaches) {
    401             if (mScoreCaches.containsKey(networkType)) {
    402                 throw new IllegalArgumentException(
    403                         "Score cache already registered for type " + networkType);
    404             }
    405             mScoreCaches.put(networkType, scoreCache);
    406         }
    407     }
    408 
    409     @Override
    410     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
    411         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
    412         NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
    413         if (currentScorer == null) {
    414             writer.println("Scoring is disabled.");
    415             return;
    416         }
    417         writer.println("Current scorer: " + currentScorer.mPackageName);
    418 
    419         for (INetworkScoreCache scoreCache : getScoreCaches()) {
    420             try {
    421                 scoreCache.asBinder().dump(fd, args);
    422             } catch (RemoteException e) {
    423                 writer.println("Unable to dump score cache");
    424                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    425                     Log.v(TAG, "Unable to dump score cache", e);
    426                 }
    427             }
    428         }
    429         if (mServiceConnection != null) {
    430             mServiceConnection.dump(fd, writer, args);
    431         } else {
    432             writer.println("ScoringServiceConnection: null");
    433         }
    434         writer.flush();
    435     }
    436 
    437     /**
    438      * Returns a set of all score caches that are currently active.
    439      *
    440      * <p>May be used to perform an action on all score caches without potentially strange behavior
    441      * if a new scorer is registered during that action's execution.
    442      */
    443     private Set<INetworkScoreCache> getScoreCaches() {
    444         synchronized (mScoreCaches) {
    445             return new HashSet<>(mScoreCaches.values());
    446         }
    447     }
    448 
    449     private static class ScoringServiceConnection implements ServiceConnection {
    450         private final ComponentName mComponentName;
    451         private boolean mBound = false;
    452         private boolean mConnected = false;
    453 
    454         ScoringServiceConnection(ComponentName componentName) {
    455             mComponentName = componentName;
    456         }
    457 
    458         void connect(Context context) {
    459             if (!mBound) {
    460                 Intent service = new Intent();
    461                 service.setComponent(mComponentName);
    462                 mBound = context.bindServiceAsUser(service, this,
    463                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
    464                         UserHandle.SYSTEM);
    465                 if (!mBound) {
    466                     Log.w(TAG, "Bind call failed for " + service);
    467                 } else {
    468                     if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
    469                 }
    470             }
    471         }
    472 
    473         void disconnect(Context context) {
    474             try {
    475                 if (mBound) {
    476                     mBound = false;
    477                     context.unbindService(this);
    478                     if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
    479                 }
    480             } catch (RuntimeException e) {
    481                 Log.e(TAG, "Unbind failed.", e);
    482             }
    483         }
    484 
    485         @Override
    486         public void onServiceConnected(ComponentName name, IBinder service) {
    487             if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
    488             mConnected = true;
    489         }
    490 
    491         @Override
    492         public void onServiceDisconnected(ComponentName name) {
    493             if (DBG) {
    494                 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
    495             }
    496             mConnected = false;
    497         }
    498 
    499         public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
    500             writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
    501                     + ", connected: " + mConnected);
    502         }
    503     }
    504 }
    505