blob: 44c02270fe6cc18760c970c5d674258d48763a14 [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 Joslin145c3432016-12-09 13:11:51 -080020import android.annotation.Nullable;
Jeremy Joslin967b5812016-06-02 07:58:14 -070021import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070022import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070023import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070024import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070025import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070026import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070027import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070028import android.content.pm.PackageManager;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080029import android.database.ContentObserver;
Jeremy Joslin59502eb2017-07-14 15:00:53 -070030import android.location.LocationManager;
Jeremy Joslin145c3432016-12-09 13:11:51 -080031import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070032import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070033import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080034import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080035import android.net.NetworkScoreManager;
Jeremy Joslinf621bc92017-02-16 11:11:57 -080036import android.net.NetworkScorerAppData;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070037import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080038import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080039import android.net.wifi.ScanResult;
40import android.net.wifi.WifiInfo;
41import android.net.wifi.WifiManager;
42import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080043import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080044import android.os.Build;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080045import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070046import android.os.IBinder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080047import android.os.Looper;
48import android.os.Message;
Amin Shaikh972e2362016-12-07 14:08:09 -080049import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070050import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070051import android.os.UserHandle;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080052import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080053import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080054import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070055import android.util.Log;
Jeremy Joslin145c3432016-12-09 13:11:51 -080056
Jeff Davidson7842f642014-11-23 13:48:12 -080057import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080058import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070059import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070060import com.android.internal.os.TransferPipe;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060061import com.android.internal.util.DumpUtils;
Jeremy Joslin145c3432016-12-09 13:11:51 -080062
Jeff Davidson6a4b2202014-04-16 17:29:40 -070063import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070064import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070065import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070066import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080067import java.util.Collection;
68import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070069import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070070import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080071import java.util.Set;
Jeremy Joslinba242732017-01-24 17:16:42 -080072import java.util.function.BiConsumer;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070073import java.util.function.Function;
Jeremy Joslinba242732017-01-24 17:16:42 -080074import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080075import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070076
77/**
78 * Backing service for {@link android.net.NetworkScoreManager}.
79 * @hide
80 */
81public class NetworkScoreService extends INetworkScoreService.Stub {
82 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080083 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080084 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070085
Jeff Davidson6a4b2202014-04-16 17:29:40 -070086 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080087 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080088 @GuardedBy("mScoreCaches")
89 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070090 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -080091 private final Object mPackageMonitorLock = new Object();
92 private final Object mServiceConnectionLock = new Object();
93 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -080094 private final DispatchingContentObserver mContentObserver;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070095 private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer;
Jeff Davidson7842f642014-11-23 13:48:12 -080096
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070097 @GuardedBy("mPackageMonitorLock")
98 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -080099 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700100 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -0800101
Jeremy Joslin967b5812016-06-02 07:58:14 -0700102 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
103 @Override
104 public void onReceive(Context context, Intent intent) {
105 final String action = intent.getAction();
106 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
107 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
108 if (userId == UserHandle.USER_NULL) return;
109
110 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
111 onUserUnlocked(userId);
112 }
113 }
114 };
115
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700116 private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
117 @Override
118 public void onReceive(Context context, Intent intent) {
119 final String action = intent.getAction();
120 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
121 refreshBinding();
122 }
123 }
124 };
125
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700126 /**
127 * Clears scores when the active scorer package is no longer valid and
128 * manages the service connection.
129 */
130 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800131 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800132
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800133 private NetworkScorerPackageMonitor(String packageToWatch) {
134 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800135 }
136
137 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700138 public void onPackageAdded(String packageName, int uid) {
139 evaluateBinding(packageName, true /* forceUnbind */);
140 }
141
142 @Override
143 public void onPackageRemoved(String packageName, int uid) {
144 evaluateBinding(packageName, true /* forceUnbind */);
145 }
146
147 @Override
148 public void onPackageModified(String packageName) {
149 evaluateBinding(packageName, false /* forceUnbind */);
150 }
151
152 @Override
153 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
154 if (doit) { // "doit" means the force stop happened instead of just being queried for.
155 for (String packageName : packages) {
156 evaluateBinding(packageName, true /* forceUnbind */);
157 }
158 }
159 return super.onHandleForceStop(intent, packages, uid, doit);
160 }
161
162 @Override
163 public void onPackageUpdateFinished(String packageName, int uid) {
164 evaluateBinding(packageName, true /* forceUnbind */);
165 }
166
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800167 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
168 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800169 // Early exit when we don't care about the package that has changed.
170 return;
171 }
172
173 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800174 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800175 + ", forceUnbind=" + forceUnbind);
176 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800177
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800178 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
179 if (activeScorer == null) {
180 // Package change has invalidated a scorer, this will also unbind any service
181 // connection.
182 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800183 refreshBinding();
184 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800185 if (forceUnbind) {
186 unbindFromScoringServiceIfNeeded();
187 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800188 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800189 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
190 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800191 }
192 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800193 }
194 }
195 }
196
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800197 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800198 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800199 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800200 @VisibleForTesting
201 public static class DispatchingContentObserver extends ContentObserver {
202 final private Map<Uri, Integer> mUriEventMap;
203 final private Context mContext;
204 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800205
Jeremy Joslincb594f32017-01-03 17:31:23 -0800206 public DispatchingContentObserver(Context context, Handler handler) {
207 super(handler);
208 mContext = context;
209 mHandler = handler;
210 mUriEventMap = new ArrayMap<>();
211 }
212
213 void observe(Uri uri, int what) {
214 mUriEventMap.put(uri, what);
215 final ContentResolver resolver = mContext.getContentResolver();
216 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800217 }
218
219 @Override
220 public void onChange(boolean selfChange) {
221 onChange(selfChange, null);
222 }
223
224 @Override
225 public void onChange(boolean selfChange, Uri uri) {
226 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800227 final Integer what = mUriEventMap.get(uri);
228 if (what != null) {
229 mHandler.obtainMessage(what).sendToTarget();
230 } else {
231 Log.w(TAG, "No matching event to send for URI = " + uri);
232 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800233 }
234 }
235
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700236 public NetworkScoreService(Context context) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700237 this(context, new NetworkScorerAppManager(context),
238 ScoringServiceConnection::new, Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800239 }
240
241 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800242 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700243 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer,
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800244 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700245 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800246 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800247 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700248 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
249 // TODO: Need to update when we support per-user scorers. http://b/23422763
250 mContext.registerReceiverAsUser(
251 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
252 null /* scheduler */);
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800253 mHandler = new ServiceHandler(looper);
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700254 IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
255 mContext.registerReceiverAsUser(
256 mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
257 null /* broadcastPermission*/, mHandler);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800258 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700259 mServiceConnProducer = serviceConnProducer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700260 }
261
262 /** Called when the system is ready to run third-party code but before it actually does so. */
263 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700264 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800265 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800266 }
267
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700268 /** Called when the system is ready for us to start third-party code. */
269 void systemRunning() {
270 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700271 }
272
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800273 @VisibleForTesting
274 void onUserUnlocked(int userId) {
275 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
276 refreshBinding();
277 }
278
279 private void refreshBinding() {
280 if (DBG) Log.d(TAG, "refreshBinding()");
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800281 // Make sure the scorer is up-to-date
282 mNetworkScorerAppManager.updateState();
Jeremy Joslinb0fe2172017-03-31 10:38:31 -0700283 mNetworkScorerAppManager.migrateNetworkScorerAppSettingIfNeeded();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700284 registerPackageMonitorIfNeeded();
285 bindToScoringServiceIfNeeded();
286 }
287
Jeremy Joslincb594f32017-01-03 17:31:23 -0800288 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800289 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
290 mContentObserver.observe(packageNameUri,
291 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800292
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800293 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
294 mContentObserver.observe(settingUri,
295 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800296 }
297
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800298 /**
299 * Ensures the package manager is registered to monitor the current active scorer.
300 * If a discrepancy is found any previous monitor will be cleaned up
301 * and a new monitor will be created.
302 *
303 * This method is idempotent.
304 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700305 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800306 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
307 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700308 synchronized (mPackageMonitorLock) {
309 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800310 if (mPackageMonitor != null && (appData == null
311 || !appData.getRecommendationServicePackageName().equals(
312 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700313 if (DBG) {
314 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800315 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800316 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700317 mPackageMonitor.unregister();
318 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800319 }
320
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800321 // Create and register the monitor if a scorer is active.
322 if (appData != null && mPackageMonitor == null) {
323 mPackageMonitor = new NetworkScorerPackageMonitor(
324 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700325 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700326 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
327 false /* externalStorage */);
328 if (DBG) {
329 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800330 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800331 }
332 }
333 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700334 }
335
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700336 private void bindToScoringServiceIfNeeded() {
337 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800338 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700339 bindToScoringServiceIfNeeded(scorerData);
340 }
341
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800342 /**
343 * Ensures the service connection is bound to the current active scorer.
344 * If a discrepancy is found any previous connection will be cleaned up
345 * and a new connection will be created.
346 *
347 * This method is idempotent.
348 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800349 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
350 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
351 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800352 synchronized (mServiceConnectionLock) {
353 // If we're connected to a different component then drop it.
354 if (mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700355 && !mServiceConnection.getAppData().equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800356 unbindFromScoringServiceIfNeeded();
357 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700358
Jeremy Joslin145c3432016-12-09 13:11:51 -0800359 // If we're not connected at all then create a new connection.
360 if (mServiceConnection == null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700361 mServiceConnection = mServiceConnProducer.apply(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800362 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700363
Jeremy Joslin145c3432016-12-09 13:11:51 -0800364 // Make sure the connection is connected (idempotent)
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700365 mServiceConnection.bind(mContext);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800366 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700367 } else { // otherwise make sure it isn't bound.
368 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700369 }
370 }
371
372 private void unbindFromScoringServiceIfNeeded() {
373 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800374 synchronized (mServiceConnectionLock) {
375 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700376 mServiceConnection.unbind(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800377 if (DBG) Log.d(TAG, "Disconnected from: "
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700378 + mServiceConnection.getAppData().getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800379 }
380 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700381 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800382 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700383 }
384
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700385 @Override
386 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800387 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700388 throw new SecurityException("Caller with UID " + getCallingUid() +
389 " is not the active scorer.");
390 }
391
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800392 final long token = Binder.clearCallingIdentity();
393 try {
394 // Separate networks by type.
395 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
396 for (ScoredNetwork network : networks) {
397 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
398 if (networkList == null) {
399 networkList = new ArrayList<>();
400 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800401 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800402 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800403 }
404
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800405 // Pass the scores of each type down to the appropriate network scorer.
406 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
407 final RemoteCallbackList<INetworkScoreCache> callbackList;
408 final boolean isEmpty;
409 synchronized (mScoreCaches) {
410 callbackList = mScoreCaches.get(entry.getKey());
411 isEmpty = callbackList == null
412 || callbackList.getRegisteredCallbackCount() == 0;
413 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800414
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800415 if (isEmpty) {
416 if (Log.isLoggable(TAG, Log.VERBOSE)) {
417 Log.v(TAG, "No scorer registered for type " + entry.getKey()
418 + ", discarding");
419 }
420 continue;
421 }
422
Jeremy Joslinba242732017-01-24 17:16:42 -0800423 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800424 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800425 entry.getKey());
426 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800427 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700428
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800429 return true;
430 } finally {
431 Binder.restoreCallingIdentity(token);
432 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700433 }
434
Jeremy Joslinba242732017-01-24 17:16:42 -0800435 /**
436 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
437 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
438 * accepted {@link INetworkScoreCache} implementation.
439 */
440 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800441 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800442 implements BiConsumer<INetworkScoreCache, Object> {
443 private final Context mContext;
444 private final List<ScoredNetwork> mScoredNetworkList;
445 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800446 // TODO: 1/23/17 - Consider a Map if we implement more filters.
447 // These are created on-demand to defer the construction cost until
448 // an instance is actually needed.
449 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
450 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800451
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800452 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800453 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800454 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
455 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800456 }
457
458 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800459 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800460 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800461 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
462 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800463 mContext = context;
464 mScoredNetworkList = scoredNetworkList;
465 mNetworkType = networkType;
466 mCurrentNetworkFilter = currentNetworkFilter;
467 mScanResultsFilter = scanResultsFilter;
468 }
469
470 @Override
471 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
472 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
473 if (cookie instanceof Integer) {
474 filterType = (Integer) cookie;
475 }
476
477 try {
478 final List<ScoredNetwork> filteredNetworkList =
479 filterScores(mScoredNetworkList, filterType);
480 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800481 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800482 }
483 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800484 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800485 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
486 }
487 }
488 }
489
490 /**
491 * Applies the appropriate filter and returns the filtered results.
492 */
493 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
494 int filterType) {
495 switch (filterType) {
496 case NetworkScoreManager.CACHE_FILTER_NONE:
497 return scoredNetworkList;
498
499 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
500 if (mCurrentNetworkFilter == null) {
501 mCurrentNetworkFilter =
502 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
503 }
504 return mCurrentNetworkFilter.apply(scoredNetworkList);
505
506 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
507 if (mScanResultsFilter == null) {
508 mScanResultsFilter = new ScanResultsScoreCacheFilter(
509 new ScanResultsSupplier(mContext));
510 }
511 return mScanResultsFilter.apply(scoredNetworkList);
512
513 default:
514 Log.w(TAG, "Unknown filter type: " + filterType);
515 return scoredNetworkList;
516 }
517 }
518 }
519
520 /**
521 * Helper class that improves the testability of the cache filter Functions.
522 */
523 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
524 private final Context mContext;
525
526 WifiInfoSupplier(Context context) {
527 mContext = context;
528 }
529
530 @Override
531 public WifiInfo get() {
532 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
533 if (wifiManager != null) {
534 return wifiManager.getConnectionInfo();
535 }
536 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
537 return null;
538 }
539 }
540
541 /**
542 * Helper class that improves the testability of the cache filter Functions.
543 */
544 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
545 private final Context mContext;
546
547 ScanResultsSupplier(Context context) {
548 mContext = context;
549 }
550
551 @Override
552 public List<ScanResult> get() {
553 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
554 if (wifiScanner != null) {
555 return wifiScanner.getSingleScanResults();
556 }
557 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
558 return Collections.emptyList();
559 }
560 }
561
562 /**
563 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
564 * {@link ScoredNetwork} associated with the current network. If no network is connected the
565 * returned list will be empty.
566 * <p>
567 * Note: this filter performs some internal caching for consistency and performance. The
568 * current network is determined at construction time and never changed. Also, the
569 * last filtered list is saved so if the same input is provided multiple times in a row
570 * the computation is only done once.
571 */
572 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800573 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800574 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800575
576 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
577 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
578 }
579
580 @Override
581 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
582 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
583 return Collections.emptyList();
584 }
585
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800586 for (int i = 0; i < scoredNetworks.size(); i++) {
587 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
588 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
589 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800590 }
591 }
592
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800593 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800594 }
595 }
596
597 /**
598 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
599 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
600 * If there are no {@link ScanResult}s the returned list will be empty.
601 * <p>
602 * Note: this filter performs some internal caching for consistency and performance. The
603 * current set of ScanResults is determined at construction time and never changed.
604 * Also, the last filtered list is saved so if the same input is provided multiple
605 * times in a row the computation is only done once.
606 */
607 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800608 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
609 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800610
611 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800612 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800613 final int size = scanResults.size();
614 mScanResultKeys = new ArraySet<>(size);
615 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800616 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800617 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
618 if (key != null) {
619 mScanResultKeys.add(key);
620 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800621 }
622 }
623
624 @Override
625 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
626 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
627 return Collections.emptyList();
628 }
629
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800630 List<ScoredNetwork> filteredScores = new ArrayList<>();
631 for (int i = 0; i < scoredNetworks.size(); i++) {
632 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
633 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
634 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800635 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800636 }
637
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800638 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800639 }
640 }
641
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700642 @Override
643 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800644 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700645 enforceSystemOrIsActiveScorer(getCallingUid());
646 final long token = Binder.clearCallingIdentity();
647 try {
648 clearInternal();
649 return true;
650 } finally {
651 Binder.restoreCallingIdentity(token);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700652 }
653 }
654
655 @Override
656 public boolean setActiveScorer(String packageName) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700657 enforceSystemOrHasScoreNetworks();
Jeremy Josline9052a32017-02-27 15:47:54 -0800658 return mNetworkScorerAppManager.setActiveScorer(packageName);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700659 }
660
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800661 /**
662 * Determine whether the application with the given UID is the enabled scorer.
663 *
664 * @param callingUid the UID to check
665 * @return true if the provided UID is the active scorer, false otherwise.
666 */
667 @Override
668 public boolean isCallerActiveScorer(int callingUid) {
669 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800670 return mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700671 && mServiceConnection.getAppData().packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800672 }
673 }
674
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700675 private void enforceSystemOnly() throws SecurityException {
676 // REQUEST_NETWORK_SCORES is a signature only permission.
677 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES,
678 "Caller must be granted REQUEST_NETWORK_SCORES.");
679 }
680
681 private void enforceSystemOrHasScoreNetworks() throws SecurityException {
682 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)
683 != PackageManager.PERMISSION_GRANTED
684 && mContext.checkCallingOrSelfPermission(permission.SCORE_NETWORKS)
685 != PackageManager.PERMISSION_GRANTED) {
686 throw new SecurityException(
687 "Caller is neither the system process or a network scorer.");
688 }
689 }
690
691 private void enforceSystemOrIsActiveScorer(int callingUid) throws SecurityException {
692 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)
693 != PackageManager.PERMISSION_GRANTED
694 && !isCallerActiveScorer(callingUid)) {
695 throw new SecurityException(
696 "Caller is neither the system process or the active network scorer.");
697 }
Jeremy Joslina5172f62017-02-02 14:27:05 -0800698 }
699
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800700 /**
701 * Obtain the package name of the current active network scorer.
702 *
703 * @return the full package name of the current active scorer, or null if there is no active
704 * scorer.
705 */
706 @Override
707 public String getActiveScorerPackage() {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700708 enforceSystemOrHasScoreNetworks();
709 synchronized (mServiceConnectionLock) {
710 if (mServiceConnection != null) {
711 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800712 }
713 }
714 return null;
715 }
716
Jeremy Joslina5172f62017-02-02 14:27:05 -0800717 /**
718 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
719 */
720 @Override
721 public NetworkScorerAppData getActiveScorer() {
722 // Only the system can access this data.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700723 enforceSystemOnly();
724 synchronized (mServiceConnectionLock) {
725 if (mServiceConnection != null) {
726 return mServiceConnection.getAppData();
Jeremy Joslina5172f62017-02-02 14:27:05 -0800727 }
Jeremy Joslina5172f62017-02-02 14:27:05 -0800728 }
729
730 return null;
731 }
732
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800733 /**
734 * Returns the list of available scorer apps. The list will be empty if there are
735 * no valid scorers.
736 */
737 @Override
738 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800739 // Only the system can access this data.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700740 enforceSystemOnly();
Jeremy Josline9052a32017-02-27 15:47:54 -0800741 return mNetworkScorerAppManager.getAllValidScorers();
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800742 }
743
Jeff Davidson26fd1432014-07-29 09:39:52 -0700744 @Override
745 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800746 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700747 enforceSystemOrIsActiveScorer(getCallingUid());
Jeremy Josline9052a32017-02-27 15:47:54 -0800748 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700749 }
750
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700751 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
752 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800753 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800754 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800755 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800756 try {
757 networkScoreCache.clearScores();
758 } catch (RemoteException e) {
759 if (Log.isLoggable(TAG, Log.VERBOSE)) {
760 Log.v(TAG, "Unable to clear scores", e);
761 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700762 }
763 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800764 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700765 }
766
767 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800768 public void registerNetworkScoreCache(int networkType,
769 INetworkScoreCache scoreCache,
770 int filterType) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700771 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800772 final long token = Binder.clearCallingIdentity();
773 try {
774 synchronized (mScoreCaches) {
775 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
776 if (callbackList == null) {
777 callbackList = new RemoteCallbackList<>();
778 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800779 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800780 if (!callbackList.register(scoreCache, filterType)) {
781 if (callbackList.getRegisteredCallbackCount() == 0) {
782 mScoreCaches.remove(networkType);
783 }
784 if (Log.isLoggable(TAG, Log.VERBOSE)) {
785 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
786 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800787 }
788 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800789 } finally {
790 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800791 }
792 }
793
794 @Override
795 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700796 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800797 final long token = Binder.clearCallingIdentity();
798 try {
799 synchronized (mScoreCaches) {
800 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
801 if (callbackList == null || !callbackList.unregister(scoreCache)) {
802 if (Log.isLoggable(TAG, Log.VERBOSE)) {
803 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
804 + networkType);
805 }
806 } else if (callbackList.getRegisteredCallbackCount() == 0) {
807 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800808 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800809 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800810 } finally {
811 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700812 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700813 }
814
815 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800816 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700817 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800818 final long token = Binder.clearCallingIdentity();
819 try {
820 final INetworkRecommendationProvider provider = getRecommendationProvider();
821 if (provider != null) {
822 try {
823 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800824 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800825 // prevent repeated requests for the same scores.
826 return true;
827 } catch (RemoteException e) {
828 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800829 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800830 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800831 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800832 return false;
833 } finally {
834 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800835 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800836 }
837
838 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800839 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600840 if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800841 final long token = Binder.clearCallingIdentity();
842 try {
843 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
844 if (currentScorer == null) {
845 writer.println("Scoring is disabled.");
846 return;
847 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800848 writer.println("Current scorer: " + currentScorer);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700849
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800850 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
851 @Override
852 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
853 try {
854 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
855 } catch (IOException | RemoteException e) {
856 writer.println("Failed to dump score cache: " + e);
857 }
858 }
859 }, getScoreCacheLists());
860
861 synchronized (mServiceConnectionLock) {
862 if (mServiceConnection != null) {
863 mServiceConnection.dump(fd, writer, args);
864 } else {
865 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800866 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700867 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800868 writer.flush();
869 } finally {
870 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700871 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700872 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700873
874 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800875 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700876 *
877 * <p>May be used to perform an action on all score caches without potentially strange behavior
878 * if a new scorer is registered during that action's execution.
879 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800880 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700881 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800882 return new ArrayList<>(mScoreCaches.values());
883 }
884 }
885
Jeremy Joslinba242732017-01-24 17:16:42 -0800886 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800887 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
888 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
889 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
890 final int count = callbackList.beginBroadcast();
891 try {
892 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800893 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800894 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800895 }
896 } finally {
897 callbackList.finishBroadcast();
898 }
899 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700900 }
901 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700902
Jeremy Joslin145c3432016-12-09 13:11:51 -0800903 @Nullable
904 private INetworkRecommendationProvider getRecommendationProvider() {
905 synchronized (mServiceConnectionLock) {
906 if (mServiceConnection != null) {
907 return mServiceConnection.getRecommendationProvider();
908 }
909 }
910 return null;
911 }
912
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700913 // The class and methods need to be public for Mockito to work.
914 @VisibleForTesting
915 public static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800916 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800917 private volatile boolean mBound = false;
918 private volatile boolean mConnected = false;
919 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700920
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800921 ScoringServiceConnection(NetworkScorerAppData appData) {
922 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700923 }
924
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700925 @VisibleForTesting
926 public void bind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700927 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800928 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800929 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700930 mBound = context.bindServiceAsUser(service, this,
931 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
932 UserHandle.SYSTEM);
933 if (!mBound) {
934 Log.w(TAG, "Bind call failed for " + service);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700935 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700936 } else {
937 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
938 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700939 }
940 }
941
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700942 @VisibleForTesting
943 public void unbind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700944 try {
945 if (mBound) {
946 mBound = false;
947 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700948 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700949 }
950 } catch (RuntimeException e) {
951 Log.e(TAG, "Unbind failed.", e);
952 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800953
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700954 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800955 mRecommendationProvider = null;
956 }
957
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700958 @VisibleForTesting
959 public NetworkScorerAppData getAppData() {
960 return mAppData;
961 }
962
963 @VisibleForTesting
964 public INetworkRecommendationProvider getRecommendationProvider() {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800965 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700966 }
967
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700968 @VisibleForTesting
969 public String getPackageName() {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800970 return mAppData.getRecommendationServiceComponent().getPackageName();
971 }
972
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700973 @VisibleForTesting
974 public boolean isAlive() {
975 return mBound && mConnected;
976 }
977
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700978 @Override
979 public void onServiceConnected(ComponentName name, IBinder service) {
980 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700981 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800982 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700983 }
984
985 @Override
986 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700987 if (DBG) {
988 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
989 }
990 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800991 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700992 }
993
994 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800995 writer.println("ScoringServiceConnection: "
996 + mAppData.getRecommendationServiceComponent()
997 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700998 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700999 }
1000 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001001
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001002 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001003 public final class ServiceHandler extends Handler {
Jeremy Joslinb1a01392017-04-14 13:35:48 -07001004 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 1;
1005 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 2;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001006
1007 public ServiceHandler(Looper looper) {
1008 super(looper);
1009 }
1010
1011 @Override
1012 public void handleMessage(Message msg) {
1013 final int what = msg.what;
1014 switch (what) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001015 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001016 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001017 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001018 break;
1019
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001020 default:
1021 Log.w(TAG,"Unknown message: " + what);
1022 }
1023 }
1024 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001025}