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_SCORE_NETWORKS) should 119 // be allowed to flush all scores. 120 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) || 121 mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) == 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 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG); 134 return setScorerInternal(packageName); 135 } 136 137 @Override 138 public void disableScoring() { 139 // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETOWRKS) should 140 // be allowed to disable scoring. 141 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) || 142 mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) == 143 PackageManager.PERMISSION_GRANTED) { 144 // The return value is discarded here because at this point, the call should always 145 // succeed. The only reason for failure is if the new package is not a valid scorer, but 146 // we're disabling scoring altogether here. 147 setScorerInternal(null /* packageName */); 148 } else { 149 throw new SecurityException( 150 "Caller is neither the active scorer nor the scorer manager."); 151 } 152 } 153 154 /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */ 155 private boolean setScorerInternal(String packageName) { 156 long token = Binder.clearCallingIdentity(); 157 try { 158 // Preemptively clear scores even though the set operation could fail. We do this for 159 // safety as scores should never be compared across apps; in practice, Settings should 160 // only be allowing valid apps to be set as scorers, so failure here should be rare. 161 clearInternal(); 162 boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName); 163 if (result) { 164 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED); 165 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName); 166 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 167 } 168 return result; 169 } finally { 170 Binder.restoreCallingIdentity(token); 171 } 172 } 173 174 /** Clear scores. Callers are responsible for checking permissions as appropriate. */ 175 private void clearInternal() { 176 Set<INetworkScoreCache> cachesToClear = getScoreCaches(); 177 178 for (INetworkScoreCache scoreCache : cachesToClear) { 179 try { 180 scoreCache.clearScores(); 181 } catch (RemoteException e) { 182 if (Log.isLoggable(TAG, Log.VERBOSE)) { 183 Log.v(TAG, "Unable to clear scores", e); 184 } 185 } 186 } 187 } 188 189 @Override 190 public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { 191 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG); 192 synchronized (mScoreCaches) { 193 if (mScoreCaches.containsKey(networkType)) { 194 throw new IllegalArgumentException( 195 "Score cache already registered for type " + networkType); 196 } 197 mScoreCaches.put(networkType, scoreCache); 198 } 199 } 200 201 @Override 202 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 203 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); 204 NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext); 205 if (currentScorer == null) { 206 writer.println("Scoring is disabled."); 207 return; 208 } 209 writer.println("Current scorer: " + currentScorer.mPackageName); 210 writer.flush(); 211 212 for (INetworkScoreCache scoreCache : getScoreCaches()) { 213 try { 214 scoreCache.asBinder().dump(fd, args); 215 } catch (RemoteException e) { 216 writer.println("Unable to dump score cache"); 217 if (Log.isLoggable(TAG, Log.VERBOSE)) { 218 Log.v(TAG, "Unable to dump score cache", e); 219 } 220 } 221 } 222 } 223 224 /** 225 * Returns a set of all score caches that are currently active. 226 * 227 * <p>May be used to perform an action on all score caches without potentially strange behavior 228 * if a new scorer is registered during that action's execution. 229 */ 230 private Set<INetworkScoreCache> getScoreCaches() { 231 synchronized (mScoreCaches) { 232 return new HashSet<>(mScoreCaches.values()); 233 } 234 } 235} 236