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