blob: a1c3564abf8c83b05c3fa445bdf7a5fa0a24c523 [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;
Jeremy Joslin967b5812016-06-02 07:58:14 -070020import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070021import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070022import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070023import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070024import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070025import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070026import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.pm.PackageManager;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070028import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070029import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080030import android.net.NetworkKey;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070031import android.net.NetworkScoreManager;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070032import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070033import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080034import android.net.RecommendationRequest;
35import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070036import android.net.ScoredNetwork;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080037import android.net.wifi.WifiConfiguration;
Jeff Davidson26fd1432014-07-29 09:39:52 -070038import android.os.Binder;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070039import android.os.IBinder;
Amin Shaikh972e2362016-12-07 14:08:09 -080040import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070041import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070042import android.os.UserHandle;
Jeff Davidson56f9f732014-08-14 16:47:23 -070043import android.provider.Settings;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070044import android.text.TextUtils;
Amin Shaikh972e2362016-12-07 14:08:09 -080045import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070046import android.util.Log;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070047
48import com.android.internal.R;
Jeff Davidson7842f642014-11-23 13:48:12 -080049import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080050import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070051import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070052import com.android.internal.os.TransferPipe;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070053
54import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070055import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070056import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070057import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080058import java.util.Collection;
59import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070060import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070061import java.util.Map;
Amin Shaikh972e2362016-12-07 14:08:09 -080062import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070063
64/**
65 * Backing service for {@link android.net.NetworkScoreManager}.
66 * @hide
67 */
68public class NetworkScoreService extends INetworkScoreService.Stub {
69 private static final String TAG = "NetworkScoreService";
Jeremy Joslindd251ef2016-03-14 11:17:41 -070070 private static final boolean DBG = false;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070071
Jeff Davidson6a4b2202014-04-16 17:29:40 -070072 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080073 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080074 @GuardedBy("mScoreCaches")
75 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070076 /** Lock used to update mPackageMonitor when scorer package changes occur. */
77 private final Object mPackageMonitorLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080078
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070079 @GuardedBy("mPackageMonitorLock")
80 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070081 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080082
Jeremy Joslin967b5812016-06-02 07:58:14 -070083 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
84 @Override
85 public void onReceive(Context context, Intent intent) {
86 final String action = intent.getAction();
87 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
88 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
89 if (userId == UserHandle.USER_NULL) return;
90
91 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
92 onUserUnlocked(userId);
93 }
94 }
95 };
96
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070097 /**
98 * Clears scores when the active scorer package is no longer valid and
99 * manages the service connection.
100 */
101 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeff Davidson7842f642014-11-23 13:48:12 -0800102 final String mRegisteredPackage;
103
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700104 private NetworkScorerPackageMonitor(String mRegisteredPackage) {
105 this.mRegisteredPackage = mRegisteredPackage;
Jeff Davidson7842f642014-11-23 13:48:12 -0800106 }
107
108 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700109 public void onPackageAdded(String packageName, int uid) {
110 evaluateBinding(packageName, true /* forceUnbind */);
111 }
112
113 @Override
114 public void onPackageRemoved(String packageName, int uid) {
115 evaluateBinding(packageName, true /* forceUnbind */);
116 }
117
118 @Override
119 public void onPackageModified(String packageName) {
120 evaluateBinding(packageName, false /* forceUnbind */);
121 }
122
123 @Override
124 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
125 if (doit) { // "doit" means the force stop happened instead of just being queried for.
126 for (String packageName : packages) {
127 evaluateBinding(packageName, true /* forceUnbind */);
128 }
129 }
130 return super.onHandleForceStop(intent, packages, uid, doit);
131 }
132
133 @Override
134 public void onPackageUpdateFinished(String packageName, int uid) {
135 evaluateBinding(packageName, true /* forceUnbind */);
136 }
137
138 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
139 if (mRegisteredPackage.equals(scorerPackageName)) {
140 if (DBG) {
141 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
142 + ", forceUnbind=" + forceUnbind);
143 }
144 final NetworkScorerAppData activeScorer =
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800145 mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700146 if (activeScorer == null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700147 // Package change has invalidated a scorer, this will also unbind any service
148 // connection.
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700149 Log.i(TAG, "Package " + mRegisteredPackage +
150 " is no longer valid, disabling scoring.");
151 setScorerInternal(null);
152 } else if (activeScorer.mScoringServiceClassName == null) {
153 // The scoring service is not available, make sure it's unbound.
154 unbindFromScoringServiceIfNeeded();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700155 } else { // The scoring service changed in some way.
156 if (forceUnbind) {
157 unbindFromScoringServiceIfNeeded();
158 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700159 bindToScoringServiceIfNeeded(activeScorer);
160 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800161 }
162 }
163 }
164
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700165 public NetworkScoreService(Context context) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800166 this(context, new NetworkScorerAppManager(context));
167 }
168
169 @VisibleForTesting
170 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700171 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800172 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800173 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700174 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
175 // TODO: Need to update when we support per-user scorers. http://b/23422763
176 mContext.registerReceiverAsUser(
177 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
178 null /* scheduler */);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700179 }
180
181 /** Called when the system is ready to run third-party code but before it actually does so. */
182 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700183 if (DBG) Log.d(TAG, "systemReady");
Jeff Davidson56f9f732014-08-14 16:47:23 -0700184 ContentResolver cr = mContext.getContentResolver();
185 if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700186 // On first run, we try to initialize the scorer to the one configured at build time.
187 // This will be a no-op if the scorer isn't actually valid.
188 String defaultPackage = mContext.getResources().getString(
189 R.string.config_defaultNetworkScorerPackageName);
190 if (!TextUtils.isEmpty(defaultPackage)) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800191 mNetworkScorerAppManager.setActiveScorer(defaultPackage);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700192 }
Jeff Davidson56f9f732014-08-14 16:47:23 -0700193 Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700194 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800195
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700196 registerPackageMonitorIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800197 }
198
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700199 /** Called when the system is ready for us to start third-party code. */
200 void systemRunning() {
201 if (DBG) Log.d(TAG, "systemRunning");
202 bindToScoringServiceIfNeeded();
203 }
204
Jeremy Joslin967b5812016-06-02 07:58:14 -0700205 private void onUserUnlocked(int userId) {
206 registerPackageMonitorIfNeeded();
207 bindToScoringServiceIfNeeded();
208 }
209
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700210 private void registerPackageMonitorIfNeeded() {
211 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800212 NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700213 synchronized (mPackageMonitorLock) {
214 // Unregister the current monitor if needed.
215 if (mPackageMonitor != null) {
216 if (DBG) {
217 Log.d(TAG, "Unregistering package monitor for "
218 + mPackageMonitor.mRegisteredPackage);
Jeff Davidson7842f642014-11-23 13:48:12 -0800219 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700220 mPackageMonitor.unregister();
221 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800222 }
223
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700224 // Create and register the monitor if a scorer is active.
Jeff Davidson7842f642014-11-23 13:48:12 -0800225 if (scorer != null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700226 mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700227 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700228 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
229 false /* externalStorage */);
230 if (DBG) {
231 Log.d(TAG, "Registered package monitor for "
232 + mPackageMonitor.mRegisteredPackage);
Jeff Davidson7842f642014-11-23 13:48:12 -0800233 }
234 }
235 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700236 }
237
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700238 private void bindToScoringServiceIfNeeded() {
239 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800240 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700241 bindToScoringServiceIfNeeded(scorerData);
242 }
243
244 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
245 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
246 if (scorerData != null && scorerData.mScoringServiceClassName != null) {
247 ComponentName componentName =
248 new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
249 // If we're connected to a different component then drop it.
250 if (mServiceConnection != null
251 && !mServiceConnection.mComponentName.equals(componentName)) {
252 unbindFromScoringServiceIfNeeded();
253 }
254
255 // If we're not connected at all then create a new connection.
256 if (mServiceConnection == null) {
257 mServiceConnection = new ScoringServiceConnection(componentName);
258 }
259
260 // Make sure the connection is connected (idempotent)
261 mServiceConnection.connect(mContext);
Jeremy Joslin967b5812016-06-02 07:58:14 -0700262 } else { // otherwise make sure it isn't bound.
263 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700264 }
265 }
266
267 private void unbindFromScoringServiceIfNeeded() {
268 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
269 if (mServiceConnection != null) {
270 mServiceConnection.disconnect(mContext);
271 }
272 mServiceConnection = null;
273 }
274
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700275 @Override
276 public boolean updateScores(ScoredNetwork[] networks) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800277 if (!mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700278 throw new SecurityException("Caller with UID " + getCallingUid() +
279 " is not the active scorer.");
280 }
281
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700282 // Separate networks by type.
Amin Shaikh972e2362016-12-07 14:08:09 -0800283 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700284 for (ScoredNetwork network : networks) {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700285 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
286 if (networkList == null) {
287 networkList = new ArrayList<>();
288 networksByType.put(network.networkKey.type, networkList);
289 }
290 networkList.add(network);
291 }
292
293 // Pass the scores of each type down to the appropriate network scorer.
Amin Shaikh972e2362016-12-07 14:08:09 -0800294 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
295 final RemoteCallbackList<INetworkScoreCache> callbackList;
296 final boolean isEmpty;
297 synchronized (mScoreCaches) {
298 callbackList = mScoreCaches.get(entry.getKey());
299 isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
300 }
301 if (isEmpty) {
302 if (Log.isLoggable(TAG, Log.VERBOSE)) {
303 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
304 }
305 continue;
306 }
307
308 sendCallback(new Consumer<INetworkScoreCache>() {
309 @Override
310 public void accept(INetworkScoreCache networkScoreCache) {
311 try {
312 networkScoreCache.updateScores(entry.getValue());
313 } catch (RemoteException e) {
314 if (Log.isLoggable(TAG, Log.VERBOSE)) {
315 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
316 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700317 }
318 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800319 }, Collections.singleton(callbackList));
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700320 }
321
322 return true;
323 }
324
325 @Override
326 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800327 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
328 // should be allowed to flush all scores.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800329 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800330 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700331 PackageManager.PERMISSION_GRANTED) {
332 clearInternal();
333 return true;
334 } else {
335 throw new SecurityException(
336 "Caller is neither the active scorer nor the scorer manager.");
337 }
338 }
339
340 @Override
341 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800342 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
343 // to directly set the scorer app rather than having to use the consent dialog. The
344 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
345 // do the right thing and not enable this feature without explaining it to the user.
346 // In the future, should this API be opened to 3p apps, we will need to lock this down and
347 // figure out another way to streamline the UX.
348
349 // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
350 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
351
Jeff Davidson26fd1432014-07-29 09:39:52 -0700352 return setScorerInternal(packageName);
353 }
354
355 @Override
356 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800357 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
358 // should be allowed to disable scoring.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800359 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800360 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700361 PackageManager.PERMISSION_GRANTED) {
362 // The return value is discarded here because at this point, the call should always
363 // succeed. The only reason for failure is if the new package is not a valid scorer, but
364 // we're disabling scoring altogether here.
365 setScorerInternal(null /* packageName */);
366 } else {
367 throw new SecurityException(
368 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700369 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700370 }
371
372 /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
373 private boolean setScorerInternal(String packageName) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700374 if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
Jeff Davidson26fd1432014-07-29 09:39:52 -0700375 long token = Binder.clearCallingIdentity();
376 try {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700377 unbindFromScoringServiceIfNeeded();
Jeff Davidson26fd1432014-07-29 09:39:52 -0700378 // Preemptively clear scores even though the set operation could fail. We do this for
379 // safety as scores should never be compared across apps; in practice, Settings should
380 // only be allowing valid apps to be set as scorers, so failure here should be rare.
381 clearInternal();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800382 // Get the scorer that is about to be replaced, if any, so we can notify it directly.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800383 NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer();
384 boolean result = mNetworkScorerAppManager.setActiveScorer(packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700385 // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
386 // then we'll attempt to restore the previous binding (if any), otherwise an attempt
387 // will be made to bind to the new scorer.
388 bindToScoringServiceIfNeeded();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800389 if (result) { // new scorer successfully set
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700390 registerPackageMonitorIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700391
Jeff Davidson26fd1432014-07-29 09:39:52 -0700392 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800393 if (prevScorer != null) { // Directly notify the old scorer.
394 intent.setPackage(prevScorer.mPackageName);
395 // TODO: Need to update when we support per-user scorers. http://b/23422763
396 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
397 }
398
399 if (packageName != null) { // Then notify the new scorer
400 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
401 intent.setPackage(packageName);
402 // TODO: Need to update when we support per-user scorers. http://b/23422763
403 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
404 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700405 }
406 return result;
407 } finally {
408 Binder.restoreCallingIdentity(token);
409 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700410 }
411
412 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
413 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800414 sendCallback(new Consumer<INetworkScoreCache>() {
415 @Override
416 public void accept(INetworkScoreCache networkScoreCache) {
417 try {
418 networkScoreCache.clearScores();
419 } catch (RemoteException e) {
420 if (Log.isLoggable(TAG, Log.VERBOSE)) {
421 Log.v(TAG, "Unable to clear scores", e);
422 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700423 }
424 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800425 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700426 }
427
428 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800429 public void registerNetworkScoreCache(int networkType,
430 INetworkScoreCache scoreCache,
431 int filterType) {
Jeff Davidson16197792014-11-03 17:39:54 -0800432 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700433 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800434 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
435 if (callbackList == null) {
436 callbackList = new RemoteCallbackList<>();
437 mScoreCaches.put(networkType, callbackList);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700438 }
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800439 if (!callbackList.register(scoreCache, filterType)) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800440 if (callbackList.getRegisteredCallbackCount() == 0) {
441 mScoreCaches.remove(networkType);
442 }
443 if (Log.isLoggable(TAG, Log.VERBOSE)) {
444 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
445 }
446 }
447 }
448 }
449
450 @Override
451 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
452 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
453 synchronized (mScoreCaches) {
454 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
455 if (callbackList == null || !callbackList.unregister(scoreCache)) {
456 if (Log.isLoggable(TAG, Log.VERBOSE)) {
457 Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
458 }
459 } else if (callbackList.getRegisteredCallbackCount() == 0) {
460 mScoreCaches.remove(networkType);
461 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700462 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700463 }
464
465 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800466 public RecommendationResult requestRecommendation(RecommendationRequest request) {
467 // TODO(jjoslin): 11/25/16 - Update with real impl.
468 WifiConfiguration selectedConfig = null;
469 if (request != null) {
470 selectedConfig = request.getCurrentSelectedConfig();
471 }
472 return new RecommendationResult(selectedConfig);
473 }
474
475 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800476 public boolean requestScores(NetworkKey[] networks) {
477 // TODO(jjoslin): 12/13/16 - Implement
478 return false;
479 }
480
481 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800482 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700483 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800484 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700485 if (currentScorer == null) {
486 writer.println("Scoring is disabled.");
487 return;
488 }
Jeff Davidsonc7415532014-06-23 18:15:34 -0700489 writer.println("Current scorer: " + currentScorer.mPackageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700490
Amin Shaikh972e2362016-12-07 14:08:09 -0800491 sendCallback(new Consumer<INetworkScoreCache>() {
492 @Override
493 public void accept(INetworkScoreCache networkScoreCache) {
494 try {
495 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
496 } catch (IOException | RemoteException e) {
497 writer.println("Failed to dump score cache: " + e);
498 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700499 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800500 }, getScoreCacheLists());
501
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700502 if (mServiceConnection != null) {
503 mServiceConnection.dump(fd, writer, args);
504 } else {
505 writer.println("ScoringServiceConnection: null");
506 }
507 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700508 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700509
510 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800511 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700512 *
513 * <p>May be used to perform an action on all score caches without potentially strange behavior
514 * if a new scorer is registered during that action's execution.
515 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800516 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700517 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800518 return new ArrayList<>(mScoreCaches.values());
519 }
520 }
521
522 private void sendCallback(Consumer<INetworkScoreCache> consumer,
523 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
524 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
525 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
526 final int count = callbackList.beginBroadcast();
527 try {
528 for (int i = 0; i < count; i++) {
529 consumer.accept(callbackList.getBroadcastItem(i));
530 }
531 } finally {
532 callbackList.finishBroadcast();
533 }
534 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700535 }
536 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700537
538 private static class ScoringServiceConnection implements ServiceConnection {
539 private final ComponentName mComponentName;
540 private boolean mBound = false;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700541 private boolean mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700542
543 ScoringServiceConnection(ComponentName componentName) {
544 mComponentName = componentName;
545 }
546
547 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700548 if (!mBound) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700549 Intent service = new Intent();
550 service.setComponent(mComponentName);
551 mBound = context.bindServiceAsUser(service, this,
552 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
553 UserHandle.SYSTEM);
554 if (!mBound) {
555 Log.w(TAG, "Bind call failed for " + service);
556 } else {
557 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
558 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700559 }
560 }
561
562 void disconnect(Context context) {
563 try {
564 if (mBound) {
565 mBound = false;
566 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700567 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700568 }
569 } catch (RuntimeException e) {
570 Log.e(TAG, "Unbind failed.", e);
571 }
572 }
573
574 @Override
575 public void onServiceConnected(ComponentName name, IBinder service) {
576 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700577 mConnected = true;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700578 }
579
580 @Override
581 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700582 if (DBG) {
583 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
584 }
585 mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700586 }
587
588 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700589 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
590 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700591 }
592 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700593}