blob: 723d5d8369f4925ca4790c07ee10c3a39e9e2834 [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;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080028import android.database.ContentObserver;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070029import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070030import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080031import android.net.NetworkKey;
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 Joslinfa4f08e2016-12-06 07:42:38 -080037import android.net.Uri;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080038import android.net.wifi.WifiConfiguration;
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;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080043import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080044import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070045import android.util.Log;
Jeff Davidson7842f642014-11-23 13:48:12 -080046import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080047import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070048import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070049import com.android.internal.os.TransferPipe;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070050import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070051import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070052import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070053import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080054import java.util.Collection;
55import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070056import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070057import java.util.Map;
Amin Shaikh972e2362016-12-07 14:08:09 -080058import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070059
60/**
61 * Backing service for {@link android.net.NetworkScoreManager}.
62 * @hide
63 */
64public class NetworkScoreService extends INetworkScoreService.Stub {
65 private static final String TAG = "NetworkScoreService";
Jeremy Joslindd251ef2016-03-14 11:17:41 -070066 private static final boolean DBG = false;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070067
Jeff Davidson6a4b2202014-04-16 17:29:40 -070068 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080069 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080070 @GuardedBy("mScoreCaches")
71 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070072 /** Lock used to update mPackageMonitor when scorer package changes occur. */
73 private final Object mPackageMonitorLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080074
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070075 @GuardedBy("mPackageMonitorLock")
76 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070077 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080078
Jeremy Joslin967b5812016-06-02 07:58:14 -070079 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
80 @Override
81 public void onReceive(Context context, Intent intent) {
82 final String action = intent.getAction();
83 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
84 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
85 if (userId == UserHandle.USER_NULL) return;
86
87 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
88 onUserUnlocked(userId);
89 }
90 }
91 };
92
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070093 /**
94 * Clears scores when the active scorer package is no longer valid and
95 * manages the service connection.
96 */
97 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080098 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -080099
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800100 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
101 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800102 }
103
104 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700105 public void onPackageAdded(String packageName, int uid) {
106 evaluateBinding(packageName, true /* forceUnbind */);
107 }
108
109 @Override
110 public void onPackageRemoved(String packageName, int uid) {
111 evaluateBinding(packageName, true /* forceUnbind */);
112 }
113
114 @Override
115 public void onPackageModified(String packageName) {
116 evaluateBinding(packageName, false /* forceUnbind */);
117 }
118
119 @Override
120 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
121 if (doit) { // "doit" means the force stop happened instead of just being queried for.
122 for (String packageName : packages) {
123 evaluateBinding(packageName, true /* forceUnbind */);
124 }
125 }
126 return super.onHandleForceStop(intent, packages, uid, doit);
127 }
128
129 @Override
130 public void onPackageUpdateFinished(String packageName, int uid) {
131 evaluateBinding(packageName, true /* forceUnbind */);
132 }
133
134 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800135 if (mPackagesToWatch.contains(scorerPackageName)) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700136 if (DBG) {
137 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
138 + ", forceUnbind=" + forceUnbind);
139 }
140 final NetworkScorerAppData activeScorer =
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800141 mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700142 if (activeScorer == null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700143 // Package change has invalidated a scorer, this will also unbind any service
144 // connection.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800145 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700146 unbindFromScoringServiceIfNeeded();
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800147 } else if (activeScorer.packageName.equals(scorerPackageName)) {
148 if (DBG) {
149 Log.d(TAG, "Possible change to the active scorer: "
150 + activeScorer.packageName);
151 }
152 // The scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700153 if (forceUnbind) {
154 unbindFromScoringServiceIfNeeded();
155 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700156 bindToScoringServiceIfNeeded(activeScorer);
157 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800158 }
159 }
160 }
161
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800162 /**
163 * Reevaluates the service binding when the Settings toggle is changed.
164 */
165 private class SettingsObserver extends ContentObserver {
166
167 public SettingsObserver() {
168 super(null /*handler*/);
169 }
170
171 @Override
172 public void onChange(boolean selfChange) {
173 onChange(selfChange, null);
174 }
175
176 @Override
177 public void onChange(boolean selfChange, Uri uri) {
178 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
179 bindToScoringServiceIfNeeded();
180 }
181 }
182
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700183 public NetworkScoreService(Context context) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800184 this(context, new NetworkScorerAppManager(context));
185 }
186
187 @VisibleForTesting
188 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700189 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800190 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800191 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700192 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
193 // TODO: Need to update when we support per-user scorers. http://b/23422763
194 mContext.registerReceiverAsUser(
195 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
196 null /* scheduler */);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700197 }
198
199 /** Called when the system is ready to run third-party code but before it actually does so. */
200 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700201 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700202 registerPackageMonitorIfNeeded();
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800203 registerRecommendationSettingObserverIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800204 }
205
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700206 /** Called when the system is ready for us to start third-party code. */
207 void systemRunning() {
208 if (DBG) Log.d(TAG, "systemRunning");
209 bindToScoringServiceIfNeeded();
210 }
211
Jeremy Joslin967b5812016-06-02 07:58:14 -0700212 private void onUserUnlocked(int userId) {
213 registerPackageMonitorIfNeeded();
214 bindToScoringServiceIfNeeded();
215 }
216
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800217 private void registerRecommendationSettingObserverIfNeeded() {
218 final List<String> providerPackages =
219 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
220 if (!providerPackages.isEmpty()) {
221 final ContentResolver resolver = mContext.getContentResolver();
222 final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
223 resolver.registerContentObserver(uri, false, new SettingsObserver());
224 }
225 }
226
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700227 private void registerPackageMonitorIfNeeded() {
228 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800229 final List<String> providerPackages =
230 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700231 synchronized (mPackageMonitorLock) {
232 // Unregister the current monitor if needed.
233 if (mPackageMonitor != null) {
234 if (DBG) {
235 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800236 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800237 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700238 mPackageMonitor.unregister();
239 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800240 }
241
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800242 // Create and register the monitor if there are packages that could be providers.
243 if (!providerPackages.isEmpty()) {
244 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700245 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700246 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
247 false /* externalStorage */);
248 if (DBG) {
249 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800250 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800251 }
252 }
253 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700254 }
255
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700256 private void bindToScoringServiceIfNeeded() {
257 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800258 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700259 bindToScoringServiceIfNeeded(scorerData);
260 }
261
262 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
263 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800264 if (scorerData != null && scorerData.recommendationServiceClassName != null) {
265 ComponentName componentName = new ComponentName(scorerData.packageName,
266 scorerData.recommendationServiceClassName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700267 // If we're connected to a different component then drop it.
268 if (mServiceConnection != null
269 && !mServiceConnection.mComponentName.equals(componentName)) {
270 unbindFromScoringServiceIfNeeded();
271 }
272
273 // If we're not connected at all then create a new connection.
274 if (mServiceConnection == null) {
275 mServiceConnection = new ScoringServiceConnection(componentName);
276 }
277
278 // Make sure the connection is connected (idempotent)
279 mServiceConnection.connect(mContext);
Jeremy Joslin967b5812016-06-02 07:58:14 -0700280 } else { // otherwise make sure it isn't bound.
281 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700282 }
283 }
284
285 private void unbindFromScoringServiceIfNeeded() {
286 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
287 if (mServiceConnection != null) {
288 mServiceConnection.disconnect(mContext);
289 }
290 mServiceConnection = null;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800291 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700292 }
293
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700294 @Override
295 public boolean updateScores(ScoredNetwork[] networks) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800296 if (!mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700297 throw new SecurityException("Caller with UID " + getCallingUid() +
298 " is not the active scorer.");
299 }
300
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700301 // Separate networks by type.
Amin Shaikh972e2362016-12-07 14:08:09 -0800302 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700303 for (ScoredNetwork network : networks) {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700304 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
305 if (networkList == null) {
306 networkList = new ArrayList<>();
307 networksByType.put(network.networkKey.type, networkList);
308 }
309 networkList.add(network);
310 }
311
312 // Pass the scores of each type down to the appropriate network scorer.
Amin Shaikh972e2362016-12-07 14:08:09 -0800313 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
314 final RemoteCallbackList<INetworkScoreCache> callbackList;
315 final boolean isEmpty;
316 synchronized (mScoreCaches) {
317 callbackList = mScoreCaches.get(entry.getKey());
318 isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
319 }
320 if (isEmpty) {
321 if (Log.isLoggable(TAG, Log.VERBOSE)) {
322 Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
323 }
324 continue;
325 }
326
327 sendCallback(new Consumer<INetworkScoreCache>() {
328 @Override
329 public void accept(INetworkScoreCache networkScoreCache) {
330 try {
331 networkScoreCache.updateScores(entry.getValue());
332 } catch (RemoteException e) {
333 if (Log.isLoggable(TAG, Log.VERBOSE)) {
334 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
335 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700336 }
337 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800338 }, Collections.singleton(callbackList));
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700339 }
340
341 return true;
342 }
343
344 @Override
345 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800346 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
347 // should be allowed to flush all scores.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800348 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800349 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700350 PackageManager.PERMISSION_GRANTED) {
351 clearInternal();
352 return true;
353 } else {
354 throw new SecurityException(
355 "Caller is neither the active scorer nor the scorer manager.");
356 }
357 }
358
359 @Override
360 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800361 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
362 // to directly set the scorer app rather than having to use the consent dialog. The
363 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
364 // do the right thing and not enable this feature without explaining it to the user.
365 // In the future, should this API be opened to 3p apps, we will need to lock this down and
366 // figure out another way to streamline the UX.
367
368 // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
369 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
370
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800371 // Scorers (recommendation providers) are selected and no longer set.
372 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700373 }
374
375 @Override
376 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800377 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
378 // should be allowed to disable scoring.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800379 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800380 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700381 PackageManager.PERMISSION_GRANTED) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800382 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700383 } else {
384 throw new SecurityException(
385 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700386 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700387 }
388
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700389 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
390 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800391 sendCallback(new Consumer<INetworkScoreCache>() {
392 @Override
393 public void accept(INetworkScoreCache networkScoreCache) {
394 try {
395 networkScoreCache.clearScores();
396 } catch (RemoteException e) {
397 if (Log.isLoggable(TAG, Log.VERBOSE)) {
398 Log.v(TAG, "Unable to clear scores", e);
399 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700400 }
401 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800402 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700403 }
404
405 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800406 public void registerNetworkScoreCache(int networkType,
407 INetworkScoreCache scoreCache,
408 int filterType) {
Jeff Davidson16197792014-11-03 17:39:54 -0800409 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700410 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800411 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
412 if (callbackList == null) {
413 callbackList = new RemoteCallbackList<>();
414 mScoreCaches.put(networkType, callbackList);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700415 }
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800416 if (!callbackList.register(scoreCache, filterType)) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800417 if (callbackList.getRegisteredCallbackCount() == 0) {
418 mScoreCaches.remove(networkType);
419 }
420 if (Log.isLoggable(TAG, Log.VERBOSE)) {
421 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
422 }
423 }
424 }
425 }
426
427 @Override
428 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
429 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
430 synchronized (mScoreCaches) {
431 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
432 if (callbackList == null || !callbackList.unregister(scoreCache)) {
433 if (Log.isLoggable(TAG, Log.VERBOSE)) {
434 Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
435 }
436 } else if (callbackList.getRegisteredCallbackCount() == 0) {
437 mScoreCaches.remove(networkType);
438 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700439 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700440 }
441
442 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800443 public RecommendationResult requestRecommendation(RecommendationRequest request) {
444 // TODO(jjoslin): 11/25/16 - Update with real impl.
445 WifiConfiguration selectedConfig = null;
446 if (request != null) {
447 selectedConfig = request.getCurrentSelectedConfig();
448 }
449 return new RecommendationResult(selectedConfig);
450 }
451
452 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800453 public boolean requestScores(NetworkKey[] networks) {
454 // TODO(jjoslin): 12/13/16 - Implement
455 return false;
456 }
457
458 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800459 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700460 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800461 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700462 if (currentScorer == null) {
463 writer.println("Scoring is disabled.");
464 return;
465 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800466 writer.println("Current scorer: " + currentScorer.packageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700467
Amin Shaikh972e2362016-12-07 14:08:09 -0800468 sendCallback(new Consumer<INetworkScoreCache>() {
469 @Override
470 public void accept(INetworkScoreCache networkScoreCache) {
471 try {
472 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
473 } catch (IOException | RemoteException e) {
474 writer.println("Failed to dump score cache: " + e);
475 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700476 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800477 }, getScoreCacheLists());
478
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700479 if (mServiceConnection != null) {
480 mServiceConnection.dump(fd, writer, args);
481 } else {
482 writer.println("ScoringServiceConnection: null");
483 }
484 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700485 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700486
487 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800488 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700489 *
490 * <p>May be used to perform an action on all score caches without potentially strange behavior
491 * if a new scorer is registered during that action's execution.
492 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800493 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700494 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800495 return new ArrayList<>(mScoreCaches.values());
496 }
497 }
498
499 private void sendCallback(Consumer<INetworkScoreCache> consumer,
500 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
501 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
502 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
503 final int count = callbackList.beginBroadcast();
504 try {
505 for (int i = 0; i < count; i++) {
506 consumer.accept(callbackList.getBroadcastItem(i));
507 }
508 } finally {
509 callbackList.finishBroadcast();
510 }
511 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700512 }
513 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700514
515 private static class ScoringServiceConnection implements ServiceConnection {
516 private final ComponentName mComponentName;
517 private boolean mBound = false;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700518 private boolean mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700519
520 ScoringServiceConnection(ComponentName componentName) {
521 mComponentName = componentName;
522 }
523
524 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700525 if (!mBound) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700526 Intent service = new Intent();
527 service.setComponent(mComponentName);
528 mBound = context.bindServiceAsUser(service, this,
529 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
530 UserHandle.SYSTEM);
531 if (!mBound) {
532 Log.w(TAG, "Bind call failed for " + service);
533 } else {
534 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
535 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700536 }
537 }
538
539 void disconnect(Context context) {
540 try {
541 if (mBound) {
542 mBound = false;
543 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700544 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700545 }
546 } catch (RuntimeException e) {
547 Log.e(TAG, "Unbind failed.", e);
548 }
549 }
550
551 @Override
552 public void onServiceConnected(ComponentName name, IBinder service) {
553 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700554 mConnected = true;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700555 }
556
557 @Override
558 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700559 if (DBG) {
560 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
561 }
562 mConnected = false;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700563 }
564
565 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700566 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
567 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700568 }
569 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700570}