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