blob: 27e4aa48c047013ef3dac16747d747b58ef9194c [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;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070030import android.net.NetworkScoreManager;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070032import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080033import android.net.RecommendationRequest;
34import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070035import android.net.ScoredNetwork;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080036import android.net.wifi.WifiConfiguration;
Jeff Davidson26fd1432014-07-29 09:39:52 -070037import android.os.Binder;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070038import android.os.IBinder;
Amin Shaikh972e2362016-12-07 14:08:09 -080039import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070040import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070041import android.os.UserHandle;
Jeff Davidson56f9f732014-08-14 16:47:23 -070042import android.provider.Settings;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070043import android.text.TextUtils;
Amin Shaikh972e2362016-12-07 14:08:09 -080044import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070045import android.util.Log;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070046
47import com.android.internal.R;
Jeff Davidson7842f642014-11-23 13:48:12 -080048import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080049import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070050import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070051import com.android.internal.os.TransferPipe;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070052
53import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070054import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070055import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070056import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080057import java.util.Collection;
58import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070059import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070060import java.util.Map;
Amin Shaikh972e2362016-12-07 14:08:09 -080061import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070062
63/**
64 * Backing service for {@link android.net.NetworkScoreManager}.
65 * @hide
66 */
67public class NetworkScoreService extends INetworkScoreService.Stub {
68 private static final String TAG = "NetworkScoreService";
Jeremy Joslindd251ef2016-03-14 11:17:41 -070069 private static final boolean DBG = false;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070070
Jeff Davidson6a4b2202014-04-16 17:29:40 -070071 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080072 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080073 @GuardedBy("mScoreCaches")
74 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070075 /** Lock used to update mPackageMonitor when scorer package changes occur. */
76 private final Object mPackageMonitorLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080077
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070078 @GuardedBy("mPackageMonitorLock")
79 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070080 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080081
Jeremy Joslin967b5812016-06-02 07:58:14 -070082 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
83 @Override
84 public void onReceive(Context context, Intent intent) {
85 final String action = intent.getAction();
86 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
87 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
88 if (userId == UserHandle.USER_NULL) return;
89
90 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
91 onUserUnlocked(userId);
92 }
93 }
94 };
95
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070096 /**
97 * Clears scores when the active scorer package is no longer valid and
98 * manages the service connection.
99 */
100 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeff Davidson7842f642014-11-23 13:48:12 -0800101 final String mRegisteredPackage;
102
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700103 private NetworkScorerPackageMonitor(String mRegisteredPackage) {
104 this.mRegisteredPackage = mRegisteredPackage;
Jeff Davidson7842f642014-11-23 13:48:12 -0800105 }
106
107 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700108 public void onPackageAdded(String packageName, int uid) {
109 evaluateBinding(packageName, true /* forceUnbind */);
110 }
111
112 @Override
113 public void onPackageRemoved(String packageName, int uid) {
114 evaluateBinding(packageName, true /* forceUnbind */);
115 }
116
117 @Override
118 public void onPackageModified(String packageName) {
119 evaluateBinding(packageName, false /* forceUnbind */);
120 }
121
122 @Override
123 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
124 if (doit) { // "doit" means the force stop happened instead of just being queried for.
125 for (String packageName : packages) {
126 evaluateBinding(packageName, true /* forceUnbind */);
127 }
128 }
129 return super.onHandleForceStop(intent, packages, uid, doit);
130 }
131
132 @Override
133 public void onPackageUpdateFinished(String packageName, int uid) {
134 evaluateBinding(packageName, true /* forceUnbind */);
135 }
136
137 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
138 if (mRegisteredPackage.equals(scorerPackageName)) {
139 if (DBG) {
140 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
141 + ", forceUnbind=" + forceUnbind);
142 }
143 final NetworkScorerAppData activeScorer =
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800144 mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700145 if (activeScorer == null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700146 // Package change has invalidated a scorer, this will also unbind any service
147 // connection.
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700148 Log.i(TAG, "Package " + mRegisteredPackage +
149 " is no longer valid, disabling scoring.");
150 setScorerInternal(null);
151 } else if (activeScorer.mScoringServiceClassName == null) {
152 // The scoring service is not available, make sure it's unbound.
153 unbindFromScoringServiceIfNeeded();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700154 } else { // The scoring service changed in some way.
155 if (forceUnbind) {
156 unbindFromScoringServiceIfNeeded();
157 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700158 bindToScoringServiceIfNeeded(activeScorer);
159 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800160 }
161 }
162 }
163
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700164 public NetworkScoreService(Context context) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800165 this(context, new NetworkScorerAppManager(context));
166 }
167
168 @VisibleForTesting
169 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700170 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800171 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800172 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700173 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
174 // TODO: Need to update when we support per-user scorers. http://b/23422763
175 mContext.registerReceiverAsUser(
176 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
177 null /* scheduler */);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700178 }
179
180 /** Called when the system is ready to run third-party code but before it actually does so. */
181 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700182 if (DBG) Log.d(TAG, "systemReady");
Jeff Davidson56f9f732014-08-14 16:47:23 -0700183 ContentResolver cr = mContext.getContentResolver();
184 if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700185 // On first run, we try to initialize the scorer to the one configured at build time.
186 // This will be a no-op if the scorer isn't actually valid.
187 String defaultPackage = mContext.getResources().getString(
188 R.string.config_defaultNetworkScorerPackageName);
189 if (!TextUtils.isEmpty(defaultPackage)) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800190 mNetworkScorerAppManager.setActiveScorer(defaultPackage);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700191 }
Jeff Davidson56f9f732014-08-14 16:47:23 -0700192 Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700193 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800194
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700195 registerPackageMonitorIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800196 }
197
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700198 /** Called when the system is ready for us to start third-party code. */
199 void systemRunning() {
200 if (DBG) Log.d(TAG, "systemRunning");
201 bindToScoringServiceIfNeeded();
202 }
203
Jeremy Joslin967b5812016-06-02 07:58:14 -0700204 private void onUserUnlocked(int userId) {
205 registerPackageMonitorIfNeeded();
206 bindToScoringServiceIfNeeded();
207 }
208
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700209 private void registerPackageMonitorIfNeeded() {
210 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800211 NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700212 synchronized (mPackageMonitorLock) {
213 // Unregister the current monitor if needed.
214 if (mPackageMonitor != null) {
215 if (DBG) {
216 Log.d(TAG, "Unregistering package monitor for "
217 + mPackageMonitor.mRegisteredPackage);
Jeff Davidson7842f642014-11-23 13:48:12 -0800218 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700219 mPackageMonitor.unregister();
220 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800221 }
222
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700223 // Create and register the monitor if a scorer is active.
Jeff Davidson7842f642014-11-23 13:48:12 -0800224 if (scorer != null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700225 mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700226 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700227 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
228 false /* externalStorage */);
229 if (DBG) {
230 Log.d(TAG, "Registered package monitor for "
231 + mPackageMonitor.mRegisteredPackage);
Jeff Davidson7842f642014-11-23 13:48:12 -0800232 }
233 }
234 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700235 }
236
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700237 private void bindToScoringServiceIfNeeded() {
238 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800239 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700240 bindToScoringServiceIfNeeded(scorerData);
241 }
242
243 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
244 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
245 if (scorerData != null && scorerData.mScoringServiceClassName != null) {
246 ComponentName componentName =
247 new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
248 // If we're connected to a different component then drop it.
249 if (mServiceConnection != null
250 && !mServiceConnection.mComponentName.equals(componentName)) {
251 unbindFromScoringServiceIfNeeded();
252 }
253
254 // If we're not connected at all then create a new connection.
255 if (mServiceConnection == null) {
256 mServiceConnection = new ScoringServiceConnection(componentName);
257 }
258
259 // Make sure the connection is connected (idempotent)
260 mServiceConnection.connect(mContext);
Jeremy Joslin967b5812016-06-02 07:58:14 -0700261 } else { // otherwise make sure it isn't bound.
262 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700263 }
264 }
265
266 private void unbindFromScoringServiceIfNeeded() {
267 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
268 if (mServiceConnection != null) {
269 mServiceConnection.disconnect(mContext);
270 }
271 mServiceConnection = null;
272 }
273
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700274 @Override
275 public boolean updateScores(ScoredNetwork[] networks) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800276 if (!mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700277 throw new SecurityException("Caller with UID " + getCallingUid() +
278 " is not the active scorer.");
279 }
280
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700281 // Separate networks by type.
Amin Shaikh972e2362016-12-07 14:08:09 -0800282 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700283 for (ScoredNetwork network : networks) {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700284 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
285 if (networkList == null) {
286 networkList = new ArrayList<>();
287 networksByType.put(network.networkKey.type, networkList);
288 }
289 networkList.add(network);
290 }
291
292 // Pass the scores of each type down to the appropriate network scorer.
Amin Shaikh972e2362016-12-07 14:08:09 -0800293 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
294 final RemoteCallbackList<INetworkScoreCache> callbackList;
295 final boolean isEmpty;
296 synchronized (mScoreCaches) {
297 callbackList = mScoreCaches.get(entry.getKey());
298 isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
299 }
300 if (isEmpty) {
301 if (Log.isLoggable(TAG, Log.VERBOSE)) {
302 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
303 }
304 continue;
305 }
306
307 sendCallback(new Consumer<INetworkScoreCache>() {
308 @Override
309 public void accept(INetworkScoreCache networkScoreCache) {
310 try {
311 networkScoreCache.updateScores(entry.getValue());
312 } catch (RemoteException e) {
313 if (Log.isLoggable(TAG, Log.VERBOSE)) {
314 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
315 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700316 }
317 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800318 }, Collections.singleton(callbackList));
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700319 }
320
321 return true;
322 }
323
324 @Override
325 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800326 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
327 // should be allowed to flush all scores.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800328 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800329 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700330 PackageManager.PERMISSION_GRANTED) {
331 clearInternal();
332 return true;
333 } else {
334 throw new SecurityException(
335 "Caller is neither the active scorer nor the scorer manager.");
336 }
337 }
338
339 @Override
340 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800341 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
342 // to directly set the scorer app rather than having to use the consent dialog. The
343 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
344 // do the right thing and not enable this feature without explaining it to the user.
345 // In the future, should this API be opened to 3p apps, we will need to lock this down and
346 // figure out another way to streamline the UX.
347
348 // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
349 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
350
Jeff Davidson26fd1432014-07-29 09:39:52 -0700351 return setScorerInternal(packageName);
352 }
353
354 @Override
355 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800356 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
357 // should be allowed to disable scoring.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800358 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800359 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700360 PackageManager.PERMISSION_GRANTED) {
361 // The return value is discarded here because at this point, the call should always
362 // succeed. The only reason for failure is if the new package is not a valid scorer, but
363 // we're disabling scoring altogether here.
364 setScorerInternal(null /* packageName */);
365 } else {
366 throw new SecurityException(
367 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700368 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700369 }
370
371 /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
372 private boolean setScorerInternal(String packageName) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700373 if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
Jeff Davidson26fd1432014-07-29 09:39:52 -0700374 long token = Binder.clearCallingIdentity();
375 try {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700376 unbindFromScoringServiceIfNeeded();
Jeff Davidson26fd1432014-07-29 09:39:52 -0700377 // Preemptively clear scores even though the set operation could fail. We do this for
378 // safety as scores should never be compared across apps; in practice, Settings should
379 // only be allowing valid apps to be set as scorers, so failure here should be rare.
380 clearInternal();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800381 // Get the scorer that is about to be replaced, if any, so we can notify it directly.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800382 NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer();
383 boolean result = mNetworkScorerAppManager.setActiveScorer(packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700384 // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
385 // then we'll attempt to restore the previous binding (if any), otherwise an attempt
386 // will be made to bind to the new scorer.
387 bindToScoringServiceIfNeeded();
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800388 if (result) { // new scorer successfully set
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700389 registerPackageMonitorIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700390
Jeff Davidson26fd1432014-07-29 09:39:52 -0700391 Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
Jeremy Joslinda11f5c2016-02-10 07:31:33 -0800392 if (prevScorer != null) { // Directly notify the old scorer.
393 intent.setPackage(prevScorer.mPackageName);
394 // TODO: Need to update when we support per-user scorers. http://b/23422763
395 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
396 }
397
398 if (packageName != null) { // Then notify the new scorer
399 intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
400 intent.setPackage(packageName);
401 // TODO: Need to update when we support per-user scorers. http://b/23422763
402 mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
403 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700404 }
405 return result;
406 } finally {
407 Binder.restoreCallingIdentity(token);
408 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700409 }
410
411 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
412 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800413 sendCallback(new Consumer<INetworkScoreCache>() {
414 @Override
415 public void accept(INetworkScoreCache networkScoreCache) {
416 try {
417 networkScoreCache.clearScores();
418 } catch (RemoteException e) {
419 if (Log.isLoggable(TAG, Log.VERBOSE)) {
420 Log.v(TAG, "Unable to clear scores", e);
421 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700422 }
423 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800424 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700425 }
426
427 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800428 public void registerNetworkScoreCache(int networkType,
429 INetworkScoreCache scoreCache,
430 int filterType) {
Jeff Davidson16197792014-11-03 17:39:54 -0800431 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700432 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800433 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
434 if (callbackList == null) {
435 callbackList = new RemoteCallbackList<>();
436 mScoreCaches.put(networkType, callbackList);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700437 }
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800438 if (!callbackList.register(scoreCache, filterType)) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800439 if (callbackList.getRegisteredCallbackCount() == 0) {
440 mScoreCaches.remove(networkType);
441 }
442 if (Log.isLoggable(TAG, Log.VERBOSE)) {
443 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
444 }
445 }
446 }
447 }
448
449 @Override
450 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
451 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
452 synchronized (mScoreCaches) {
453 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
454 if (callbackList == null || !callbackList.unregister(scoreCache)) {
455 if (Log.isLoggable(TAG, Log.VERBOSE)) {
456 Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
457 }
458 } else if (callbackList.getRegisteredCallbackCount() == 0) {
459 mScoreCaches.remove(networkType);
460 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700461 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700462 }
463
464 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800465 public RecommendationResult requestRecommendation(RecommendationRequest request) {
466 // TODO(jjoslin): 11/25/16 - Update with real impl.
467 WifiConfiguration selectedConfig = null;
468 if (request != null) {
469 selectedConfig = request.getCurrentSelectedConfig();
470 }
471 return new RecommendationResult(selectedConfig);
472 }
473
474 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800475 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700476 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800477 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700478 if (currentScorer == null) {
479 writer.println("Scoring is disabled.");
480 return;
481 }
Jeff Davidsonc7415532014-06-23 18:15:34 -0700482 writer.println("Current scorer: " + currentScorer.mPackageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700483
Amin Shaikh972e2362016-12-07 14:08:09 -0800484 sendCallback(new Consumer<INetworkScoreCache>() {
485 @Override
486 public void accept(INetworkScoreCache networkScoreCache) {
487 try {
488 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
489 } catch (IOException | RemoteException e) {
490 writer.println("Failed to dump score cache: " + e);
491 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700492 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800493 }, getScoreCacheLists());
494
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700495 if (mServiceConnection != null) {
496 mServiceConnection.dump(fd, writer, args);
497 } else {
498 writer.println("ScoringServiceConnection: null");
499 }
500 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700501 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700502
503 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800504 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700505 *
506 * <p>May be used to perform an action on all score caches without potentially strange behavior
507 * if a new scorer is registered during that action's execution.
508 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800509 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700510 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800511 return new ArrayList<>(mScoreCaches.values());
512 }
513 }
514
515 private void sendCallback(Consumer<INetworkScoreCache> consumer,
516 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
517 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
518 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
519 final int count = callbackList.beginBroadcast();
520 try {
521 for (int i = 0; i < count; i++) {
522 consumer.accept(callbackList.getBroadcastItem(i));
523 }
524 } finally {
525 callbackList.finishBroadcast();
526 }
527 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700528 }
529 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700530
531 private static class ScoringServiceConnection implements ServiceConnection {
532 private final ComponentName mComponentName;
533 private boolean mBound = false;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700534 private boolean mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700535
536 ScoringServiceConnection(ComponentName componentName) {
537 mComponentName = componentName;
538 }
539
540 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700541 if (!mBound) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700542 Intent service = new Intent();
543 service.setComponent(mComponentName);
544 mBound = context.bindServiceAsUser(service, this,
545 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
546 UserHandle.SYSTEM);
547 if (!mBound) {
548 Log.w(TAG, "Bind call failed for " + service);
549 } else {
550 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
551 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700552 }
553 }
554
555 void disconnect(Context context) {
556 try {
557 if (mBound) {
558 mBound = false;
559 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700560 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700561 }
562 } catch (RuntimeException e) {
563 Log.e(TAG, "Unbind failed.", e);
564 }
565 }
566
567 @Override
568 public void onServiceConnected(ComponentName name, IBinder service) {
569 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700570 mConnected = true;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700571 }
572
573 @Override
574 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700575 if (DBG) {
576 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
577 }
578 mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700579 }
580
581 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700582 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
583 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700584 }
585 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700586}