blob: b05a6908aee52c217710992bf1a2e30bd9a6337b [file] [log] [blame]
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001/*
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;
Jeff Davidson7842f642014-11-23 13:48:12 -080020import android.content.BroadcastReceiver;
Jeff Davidson56f9f732014-08-14 16:47:23 -070021import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070022import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070023import android.content.Intent;
Jeff Davidson7842f642014-11-23 13:48:12 -080024import android.content.IntentFilter;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070025import android.content.pm.PackageManager;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070026import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.net.INetworkScoreService;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070028import android.net.NetworkScoreManager;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070029import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070030import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.net.ScoredNetwork;
Jeff Davidson26fd1432014-07-29 09:39:52 -070032import android.os.Binder;
Jeff Davidson7842f642014-11-23 13:48:12 -080033import android.os.PatternMatcher;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070034import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070035import android.os.UserHandle;
Jeff Davidson56f9f732014-08-14 16:47:23 -070036import android.provider.Settings;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070037import android.text.TextUtils;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070038import android.util.Log;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070039
40import com.android.internal.R;
Jeff Davidson7842f642014-11-23 13:48:12 -080041import com.android.internal.annotations.GuardedBy;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070042
43import java.io.FileDescriptor;
44import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070045import java.util.ArrayList;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070046import java.util.HashMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070047import java.util.HashSet;
48import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070049import java.util.Map;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070050import java.util.Set;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070051
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
Jeff Davidson6a4b2202014-04-16 17:29:40 -070059 private final Context mContext;
60
Jeff Davidson14f1ec02014-04-29 11:58:26 -070061 private final Map<Integer, INetworkScoreCache> mScoreCaches;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070062
Jeff Davidson7842f642014-11-23 13:48:12 -080063 /** 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
Jeff Davidson6a4b2202014-04-16 17:29:40 -070092 public NetworkScoreService(Context context) {
93 mContext = context;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070094 mScoreCaches = new HashMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -070095 }
96
97 /** Called when the system is ready to run third-party code but before it actually does so. */
98 void systemReady() {
Jeff Davidson56f9f732014-08-14 16:47:23 -070099 ContentResolver cr = mContext.getContentResolver();
100 if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700101 // 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 }
Jeff Davidson56f9f732014-08-14 16:47:23 -0700108 Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700109 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800110
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 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700143 }
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
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700152 // Separate networks by type.
153 Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700154 for (ScoredNetwork network : networks) {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700155 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 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700177 }
178
179 return true;
180 }
181
182 @Override
183 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800184 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
185 // should be allowed to flush all scores.
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700186 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800187 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700188 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) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800199 // 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
Jeff Davidson26fd1432014-07-29 09:39:52 -0700209 return setScorerInternal(packageName);
210 }
211
212 @Override
213 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800214 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
215 // should be allowed to disable scoring.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700216 if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800217 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700218 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.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700226 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700227 }
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) {
Jeff Davidson7842f642014-11-23 13:48:12 -0800239 registerPackageReceiverIfNeeded();
Jeff Davidson26fd1432014-07-29 09:39:52 -0700240 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
241 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
Jeff Davidsonac7285d2014-08-08 15:12:47 -0700242 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700243 }
244 return result;
245 } finally {
246 Binder.restoreCallingIdentity(token);
247 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700248 }
249
250 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
251 private void clearInternal() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700252 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) {
Jeff Davidson16197792014-11-03 17:39:54 -0800267 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700268 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 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700275 }
276
277 @Override
278 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
279 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeff Davidsonc7415532014-06-23 18:15:34 -0700280 NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700281 if (currentScorer == null) {
282 writer.println("Scoring is disabled.");
283 return;
284 }
Jeff Davidsonc7415532014-06-23 18:15:34 -0700285 writer.println("Current scorer: " + currentScorer.mPackageName);
Jeff Davidson68c46fd2014-05-09 15:51:34 -0700286 writer.flush();
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700287
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 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700296 }
297 }
298 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700299
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 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700311}