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