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