1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server; 18 19import android.Manifest.permission; 20import android.content.BroadcastReceiver; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.pm.PackageManager; 26import android.net.INetworkScoreCache; 27import android.net.INetworkScoreService; 28import android.net.NetworkScoreManager; 29import android.net.NetworkScorerAppManager; 30import android.net.NetworkScorerAppManager.NetworkScorerAppData; 31import android.net.ScoredNetwork; 32import android.os.Binder; 33import android.os.PatternMatcher; 34import android.os.RemoteException; 35import android.os.UserHandle; 36import android.provider.Settings; 37import android.text.TextUtils; 38import android.util.Log; 39 40import com.android.internal.R; 41import com.android.internal.annotations.GuardedBy; 42 43import java.io.FileDescriptor; 44import java.io.PrintWriter; 45import java.util.ArrayList; 46import java.util.HashMap; 47import java.util.HashSet; 48import java.util.List; 49import java.util.Map; 50import java.util.Set; 51 52/** 53 * Backing service for {@link android.net.NetworkScoreManager}. 54 * @hide 55 */ 56public 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