blob: 80d7ac93111198fa5b4e19bf21dccbec3a18a283 [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;
Eric Enslen1e423b92017-12-18 11:30:21 -080029import android.content.pm.PackageManagerInternal;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080030import android.database.ContentObserver;
Jeremy Joslin59502eb2017-07-14 15:00:53 -070031import android.location.LocationManager;
Jeremy Joslin145c3432016-12-09 13:11:51 -080032import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070033import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070034import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080035import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080036import android.net.NetworkScoreManager;
Jeremy Joslinf621bc92017-02-16 11:11:57 -080037import android.net.NetworkScorerAppData;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070038import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080039import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080040import android.net.wifi.ScanResult;
41import android.net.wifi.WifiInfo;
42import android.net.wifi.WifiManager;
43import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080044import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080045import android.os.Build;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080046import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070047import android.os.IBinder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080048import android.os.Looper;
49import android.os.Message;
Amin Shaikh972e2362016-12-07 14:08:09 -080050import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070051import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070052import android.os.UserHandle;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080053import android.provider.Settings.Global;
Eric Enslen1e423b92017-12-18 11:30:21 -080054import android.text.TextUtils;
Amin Shaikh972e2362016-12-07 14:08:09 -080055import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080056import android.util.ArraySet;
Eric Enslen1e423b92017-12-18 11:30:21 -080057import android.util.IntArray;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070058import android.util.Log;
Jeremy Joslin145c3432016-12-09 13:11:51 -080059
Jeff Davidson7842f642014-11-23 13:48:12 -080060import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080061import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070062import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070063import com.android.internal.os.TransferPipe;
Eric Enslen1e423b92017-12-18 11:30:21 -080064import com.android.internal.telephony.SmsApplication;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060065import com.android.internal.util.DumpUtils;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066
Jeff Davidson6a4b2202014-04-16 17:29:40 -070067import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070068import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070069import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070070import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080071import java.util.Collection;
72import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070073import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070074import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080075import java.util.Set;
Jeremy Joslinba242732017-01-24 17:16:42 -080076import java.util.function.BiConsumer;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070077import java.util.function.Function;
Jeremy Joslinba242732017-01-24 17:16:42 -080078import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080079import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070080
81/**
82 * Backing service for {@link android.net.NetworkScoreManager}.
83 * @hide
84 */
85public class NetworkScoreService extends INetworkScoreService.Stub {
86 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080087 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080088 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070089
Jeff Davidson6a4b2202014-04-16 17:29:40 -070090 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080091 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080092 @GuardedBy("mScoreCaches")
93 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070094 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -080095 private final Object mPackageMonitorLock = new Object();
96 private final Object mServiceConnectionLock = new Object();
97 private final Handler mHandler;
Eric Enslen1e423b92017-12-18 11:30:21 -080098 private final DispatchingContentObserver mRecommendationSettingsObserver;
99 private final ContentObserver mUseOpenWifiPackageObserver;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700100 private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer;
Jeff Davidson7842f642014-11-23 13:48:12 -0800101
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700102 @GuardedBy("mPackageMonitorLock")
103 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800104 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700105 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -0800106
Jeremy Joslin967b5812016-06-02 07:58:14 -0700107 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
108 @Override
109 public void onReceive(Context context, Intent intent) {
110 final String action = intent.getAction();
111 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
112 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
113 if (userId == UserHandle.USER_NULL) return;
114
115 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
116 onUserUnlocked(userId);
117 }
118 }
119 };
120
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700121 private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
122 @Override
123 public void onReceive(Context context, Intent intent) {
124 final String action = intent.getAction();
125 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
126 refreshBinding();
127 }
128 }
129 };
130
Mehdi Alizadehce2d9632018-03-28 17:34:24 -0700131 public static final class Lifecycle extends SystemService {
132 private final NetworkScoreService mService;
133
134 public Lifecycle(Context context) {
135 super(context);
136 mService = new NetworkScoreService(context);
137 }
138
139 @Override
140 public void onStart() {
141 Log.i(TAG, "Registering " + Context.NETWORK_SCORE_SERVICE);
142 publishBinderService(Context.NETWORK_SCORE_SERVICE, mService);
143 }
144
145 @Override
146 public void onBootPhase(int phase) {
147 if (phase == PHASE_SYSTEM_SERVICES_READY) {
148 mService.systemReady();
149 } else if (phase == PHASE_BOOT_COMPLETED) {
150 mService.systemRunning();
151 }
152 }
153 }
154
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700155 /**
156 * Clears scores when the active scorer package is no longer valid and
157 * manages the service connection.
158 */
159 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800160 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800161
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800162 private NetworkScorerPackageMonitor(String packageToWatch) {
163 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800164 }
165
166 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700167 public void onPackageAdded(String packageName, int uid) {
168 evaluateBinding(packageName, true /* forceUnbind */);
169 }
170
171 @Override
172 public void onPackageRemoved(String packageName, int uid) {
173 evaluateBinding(packageName, true /* forceUnbind */);
174 }
175
176 @Override
177 public void onPackageModified(String packageName) {
178 evaluateBinding(packageName, false /* forceUnbind */);
179 }
180
181 @Override
182 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
183 if (doit) { // "doit" means the force stop happened instead of just being queried for.
184 for (String packageName : packages) {
185 evaluateBinding(packageName, true /* forceUnbind */);
186 }
187 }
188 return super.onHandleForceStop(intent, packages, uid, doit);
189 }
190
191 @Override
192 public void onPackageUpdateFinished(String packageName, int uid) {
193 evaluateBinding(packageName, true /* forceUnbind */);
194 }
195
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800196 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
197 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800198 // Early exit when we don't care about the package that has changed.
199 return;
200 }
201
202 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800203 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800204 + ", forceUnbind=" + forceUnbind);
205 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800206
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800207 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
208 if (activeScorer == null) {
209 // Package change has invalidated a scorer, this will also unbind any service
210 // connection.
211 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800212 refreshBinding();
213 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800214 if (forceUnbind) {
215 unbindFromScoringServiceIfNeeded();
216 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800217 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800218 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
219 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800220 }
221 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800222 }
223 }
224 }
225
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800226 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800227 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800228 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800229 @VisibleForTesting
230 public static class DispatchingContentObserver extends ContentObserver {
231 final private Map<Uri, Integer> mUriEventMap;
232 final private Context mContext;
233 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800234
Jeremy Joslincb594f32017-01-03 17:31:23 -0800235 public DispatchingContentObserver(Context context, Handler handler) {
236 super(handler);
237 mContext = context;
238 mHandler = handler;
239 mUriEventMap = new ArrayMap<>();
240 }
241
242 void observe(Uri uri, int what) {
243 mUriEventMap.put(uri, what);
244 final ContentResolver resolver = mContext.getContentResolver();
245 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800246 }
247
248 @Override
249 public void onChange(boolean selfChange) {
250 onChange(selfChange, null);
251 }
252
253 @Override
254 public void onChange(boolean selfChange, Uri uri) {
255 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800256 final Integer what = mUriEventMap.get(uri);
257 if (what != null) {
258 mHandler.obtainMessage(what).sendToTarget();
259 } else {
260 Log.w(TAG, "No matching event to send for URI = " + uri);
261 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800262 }
263 }
264
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700265 public NetworkScoreService(Context context) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700266 this(context, new NetworkScorerAppManager(context),
267 ScoringServiceConnection::new, Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800268 }
269
270 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800271 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700272 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer,
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800273 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700274 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800275 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800276 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700277 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
278 // TODO: Need to update when we support per-user scorers. http://b/23422763
279 mContext.registerReceiverAsUser(
280 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
281 null /* scheduler */);
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800282 mHandler = new ServiceHandler(looper);
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700283 IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
284 mContext.registerReceiverAsUser(
285 mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
286 null /* broadcastPermission*/, mHandler);
Eric Enslen1e423b92017-12-18 11:30:21 -0800287 mRecommendationSettingsObserver = new DispatchingContentObserver(context, mHandler);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700288 mServiceConnProducer = serviceConnProducer;
Eric Enslen1e423b92017-12-18 11:30:21 -0800289 mUseOpenWifiPackageObserver = new ContentObserver(mHandler) {
290 @Override
291 public void onChange(boolean selfChange, Uri uri, int userId) {
292 Uri useOpenWifiPkgUri = Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE);
293 if (useOpenWifiPkgUri.equals(uri)) {
294 String useOpenWifiPackage = Global.getString(mContext.getContentResolver(),
295 Global.USE_OPEN_WIFI_PACKAGE);
296 if (!TextUtils.isEmpty(useOpenWifiPackage)) {
297 LocalServices.getService(PackageManagerInternal.class)
298 .grantDefaultPermissionsToDefaultUseOpenWifiApp(useOpenWifiPackage,
299 userId);
300 }
301 }
302 }
303 };
304 mContext.getContentResolver().registerContentObserver(
305 Global.getUriFor(Global.USE_OPEN_WIFI_PACKAGE),
306 false /*notifyForDescendants*/,
307 mUseOpenWifiPackageObserver);
308 // Set a callback for the package manager to query the use open wifi app.
309 LocalServices.getService(PackageManagerInternal.class).setUseOpenWifiAppPackagesProvider(
310 new PackageManagerInternal.PackagesProvider() {
311 @Override
312 public String[] getPackages(int userId) {
313 String useOpenWifiPackage = Global.getString(mContext.getContentResolver(),
314 Global.USE_OPEN_WIFI_PACKAGE);
315 if (!TextUtils.isEmpty(useOpenWifiPackage)) {
316 return new String[]{useOpenWifiPackage};
317 }
318 return null;
319 }
320 });
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700321 }
322
323 /** Called when the system is ready to run third-party code but before it actually does so. */
324 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700325 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800326 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800327 }
328
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700329 /** Called when the system is ready for us to start third-party code. */
330 void systemRunning() {
331 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700332 }
333
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800334 @VisibleForTesting
335 void onUserUnlocked(int userId) {
336 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
337 refreshBinding();
338 }
339
340 private void refreshBinding() {
341 if (DBG) Log.d(TAG, "refreshBinding()");
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800342 // Make sure the scorer is up-to-date
343 mNetworkScorerAppManager.updateState();
Jeremy Joslinb0fe2172017-03-31 10:38:31 -0700344 mNetworkScorerAppManager.migrateNetworkScorerAppSettingIfNeeded();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700345 registerPackageMonitorIfNeeded();
346 bindToScoringServiceIfNeeded();
347 }
348
Jeremy Joslincb594f32017-01-03 17:31:23 -0800349 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800350 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
Eric Enslen1e423b92017-12-18 11:30:21 -0800351 mRecommendationSettingsObserver.observe(packageNameUri,
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800352 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800353
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800354 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
Eric Enslen1e423b92017-12-18 11:30:21 -0800355 mRecommendationSettingsObserver.observe(settingUri,
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800356 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800357 }
358
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800359 /**
360 * Ensures the package manager is registered to monitor the current active scorer.
361 * If a discrepancy is found any previous monitor will be cleaned up
362 * and a new monitor will be created.
363 *
364 * This method is idempotent.
365 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700366 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800367 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
368 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700369 synchronized (mPackageMonitorLock) {
370 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800371 if (mPackageMonitor != null && (appData == null
372 || !appData.getRecommendationServicePackageName().equals(
373 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700374 if (DBG) {
375 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800376 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800377 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700378 mPackageMonitor.unregister();
379 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800380 }
381
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800382 // Create and register the monitor if a scorer is active.
383 if (appData != null && mPackageMonitor == null) {
384 mPackageMonitor = new NetworkScorerPackageMonitor(
385 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700386 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700387 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
388 false /* externalStorage */);
389 if (DBG) {
390 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800391 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800392 }
393 }
394 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700395 }
396
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700397 private void bindToScoringServiceIfNeeded() {
398 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800399 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700400 bindToScoringServiceIfNeeded(scorerData);
401 }
402
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800403 /**
404 * Ensures the service connection is bound to the current active scorer.
405 * If a discrepancy is found any previous connection will be cleaned up
406 * and a new connection will be created.
407 *
408 * This method is idempotent.
409 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800410 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
411 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
412 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800413 synchronized (mServiceConnectionLock) {
414 // If we're connected to a different component then drop it.
415 if (mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700416 && !mServiceConnection.getAppData().equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800417 unbindFromScoringServiceIfNeeded();
418 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700419
Jeremy Joslin145c3432016-12-09 13:11:51 -0800420 // If we're not connected at all then create a new connection.
421 if (mServiceConnection == null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700422 mServiceConnection = mServiceConnProducer.apply(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800423 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700424
Jeremy Joslin145c3432016-12-09 13:11:51 -0800425 // Make sure the connection is connected (idempotent)
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700426 mServiceConnection.bind(mContext);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800427 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700428 } else { // otherwise make sure it isn't bound.
429 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700430 }
431 }
432
433 private void unbindFromScoringServiceIfNeeded() {
434 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800435 synchronized (mServiceConnectionLock) {
436 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700437 mServiceConnection.unbind(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800438 if (DBG) Log.d(TAG, "Disconnected from: "
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700439 + mServiceConnection.getAppData().getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800440 }
441 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700442 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800443 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700444 }
445
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700446 @Override
447 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800448 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700449 throw new SecurityException("Caller with UID " + getCallingUid() +
450 " is not the active scorer.");
451 }
452
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800453 final long token = Binder.clearCallingIdentity();
454 try {
455 // Separate networks by type.
456 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
457 for (ScoredNetwork network : networks) {
458 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
459 if (networkList == null) {
460 networkList = new ArrayList<>();
461 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800462 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800463 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800464 }
465
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800466 // Pass the scores of each type down to the appropriate network scorer.
467 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
468 final RemoteCallbackList<INetworkScoreCache> callbackList;
469 final boolean isEmpty;
470 synchronized (mScoreCaches) {
471 callbackList = mScoreCaches.get(entry.getKey());
472 isEmpty = callbackList == null
473 || callbackList.getRegisteredCallbackCount() == 0;
474 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800475
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800476 if (isEmpty) {
477 if (Log.isLoggable(TAG, Log.VERBOSE)) {
478 Log.v(TAG, "No scorer registered for type " + entry.getKey()
479 + ", discarding");
480 }
481 continue;
482 }
483
Jeremy Joslinba242732017-01-24 17:16:42 -0800484 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800485 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800486 entry.getKey());
487 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800488 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700489
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800490 return true;
491 } finally {
492 Binder.restoreCallingIdentity(token);
493 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700494 }
495
Jeremy Joslinba242732017-01-24 17:16:42 -0800496 /**
497 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
498 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
499 * accepted {@link INetworkScoreCache} implementation.
500 */
501 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800502 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800503 implements BiConsumer<INetworkScoreCache, Object> {
504 private final Context mContext;
505 private final List<ScoredNetwork> mScoredNetworkList;
506 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800507 // TODO: 1/23/17 - Consider a Map if we implement more filters.
508 // These are created on-demand to defer the construction cost until
509 // an instance is actually needed.
510 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
511 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800512
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800513 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800514 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800515 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
516 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800517 }
518
519 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800520 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800521 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800522 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
523 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800524 mContext = context;
525 mScoredNetworkList = scoredNetworkList;
526 mNetworkType = networkType;
527 mCurrentNetworkFilter = currentNetworkFilter;
528 mScanResultsFilter = scanResultsFilter;
529 }
530
531 @Override
532 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
533 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
534 if (cookie instanceof Integer) {
535 filterType = (Integer) cookie;
536 }
537
538 try {
539 final List<ScoredNetwork> filteredNetworkList =
540 filterScores(mScoredNetworkList, filterType);
541 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800542 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800543 }
544 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800545 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800546 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
547 }
548 }
549 }
550
551 /**
552 * Applies the appropriate filter and returns the filtered results.
553 */
554 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
555 int filterType) {
556 switch (filterType) {
557 case NetworkScoreManager.CACHE_FILTER_NONE:
558 return scoredNetworkList;
559
560 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
561 if (mCurrentNetworkFilter == null) {
562 mCurrentNetworkFilter =
563 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
564 }
565 return mCurrentNetworkFilter.apply(scoredNetworkList);
566
567 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
568 if (mScanResultsFilter == null) {
569 mScanResultsFilter = new ScanResultsScoreCacheFilter(
570 new ScanResultsSupplier(mContext));
571 }
572 return mScanResultsFilter.apply(scoredNetworkList);
573
574 default:
575 Log.w(TAG, "Unknown filter type: " + filterType);
576 return scoredNetworkList;
577 }
578 }
579 }
580
581 /**
582 * Helper class that improves the testability of the cache filter Functions.
583 */
584 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
585 private final Context mContext;
586
587 WifiInfoSupplier(Context context) {
588 mContext = context;
589 }
590
591 @Override
592 public WifiInfo get() {
593 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
594 if (wifiManager != null) {
595 return wifiManager.getConnectionInfo();
596 }
597 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
598 return null;
599 }
600 }
601
602 /**
603 * Helper class that improves the testability of the cache filter Functions.
604 */
605 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
606 private final Context mContext;
607
608 ScanResultsSupplier(Context context) {
609 mContext = context;
610 }
611
612 @Override
613 public List<ScanResult> get() {
614 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
615 if (wifiScanner != null) {
616 return wifiScanner.getSingleScanResults();
617 }
618 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
619 return Collections.emptyList();
620 }
621 }
622
623 /**
624 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
625 * {@link ScoredNetwork} associated with the current network. If no network is connected the
626 * returned list will be empty.
627 * <p>
628 * Note: this filter performs some internal caching for consistency and performance. The
629 * current network is determined at construction time and never changed. Also, the
630 * last filtered list is saved so if the same input is provided multiple times in a row
631 * the computation is only done once.
632 */
633 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800634 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800635 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800636
637 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
638 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
639 }
640
641 @Override
642 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
643 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
644 return Collections.emptyList();
645 }
646
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800647 for (int i = 0; i < scoredNetworks.size(); i++) {
648 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
649 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
650 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800651 }
652 }
653
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800654 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800655 }
656 }
657
658 /**
659 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
660 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
661 * If there are no {@link ScanResult}s the returned list will be empty.
662 * <p>
663 * Note: this filter performs some internal caching for consistency and performance. The
664 * current set of ScanResults is determined at construction time and never changed.
665 * Also, the last filtered list is saved so if the same input is provided multiple
666 * times in a row the computation is only done once.
667 */
668 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800669 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
670 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800671
672 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800673 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800674 final int size = scanResults.size();
675 mScanResultKeys = new ArraySet<>(size);
676 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800677 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800678 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
679 if (key != null) {
680 mScanResultKeys.add(key);
681 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800682 }
683 }
684
685 @Override
686 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
687 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
688 return Collections.emptyList();
689 }
690
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800691 List<ScoredNetwork> filteredScores = new ArrayList<>();
692 for (int i = 0; i < scoredNetworks.size(); i++) {
693 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
694 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
695 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800696 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800697 }
698
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800699 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800700 }
701 }
702
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700703 @Override
704 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800705 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700706 enforceSystemOrIsActiveScorer(getCallingUid());
707 final long token = Binder.clearCallingIdentity();
708 try {
709 clearInternal();
710 return true;
711 } finally {
712 Binder.restoreCallingIdentity(token);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700713 }
714 }
715
716 @Override
717 public boolean setActiveScorer(String packageName) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700718 enforceSystemOrHasScoreNetworks();
Jeremy Josline9052a32017-02-27 15:47:54 -0800719 return mNetworkScorerAppManager.setActiveScorer(packageName);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700720 }
721
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800722 /**
723 * Determine whether the application with the given UID is the enabled scorer.
724 *
725 * @param callingUid the UID to check
726 * @return true if the provided UID is the active scorer, false otherwise.
727 */
728 @Override
729 public boolean isCallerActiveScorer(int callingUid) {
730 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800731 return mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700732 && mServiceConnection.getAppData().packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800733 }
734 }
735
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700736 private void enforceSystemOnly() throws SecurityException {
737 // REQUEST_NETWORK_SCORES is a signature only permission.
738 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES,
739 "Caller must be granted REQUEST_NETWORK_SCORES.");
740 }
741
742 private void enforceSystemOrHasScoreNetworks() throws SecurityException {
743 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)
744 != PackageManager.PERMISSION_GRANTED
745 && mContext.checkCallingOrSelfPermission(permission.SCORE_NETWORKS)
746 != PackageManager.PERMISSION_GRANTED) {
747 throw new SecurityException(
748 "Caller is neither the system process or a network scorer.");
749 }
750 }
751
752 private void enforceSystemOrIsActiveScorer(int callingUid) throws SecurityException {
753 if (mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)
754 != PackageManager.PERMISSION_GRANTED
755 && !isCallerActiveScorer(callingUid)) {
756 throw new SecurityException(
757 "Caller is neither the system process or the active network scorer.");
758 }
Jeremy Joslina5172f62017-02-02 14:27:05 -0800759 }
760
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800761 /**
762 * Obtain the package name of the current active network scorer.
763 *
764 * @return the full package name of the current active scorer, or null if there is no active
765 * scorer.
766 */
767 @Override
768 public String getActiveScorerPackage() {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700769 enforceSystemOrHasScoreNetworks();
770 synchronized (mServiceConnectionLock) {
771 if (mServiceConnection != null) {
772 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800773 }
774 }
775 return null;
776 }
777
Jeremy Joslina5172f62017-02-02 14:27:05 -0800778 /**
779 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
780 */
781 @Override
782 public NetworkScorerAppData getActiveScorer() {
783 // Only the system can access this data.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700784 enforceSystemOnly();
785 synchronized (mServiceConnectionLock) {
786 if (mServiceConnection != null) {
787 return mServiceConnection.getAppData();
Jeremy Joslina5172f62017-02-02 14:27:05 -0800788 }
Jeremy Joslina5172f62017-02-02 14:27:05 -0800789 }
790
791 return null;
792 }
793
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800794 /**
795 * Returns the list of available scorer apps. The list will be empty if there are
796 * no valid scorers.
797 */
798 @Override
799 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800800 // Only the system can access this data.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700801 enforceSystemOnly();
Jeremy Josline9052a32017-02-27 15:47:54 -0800802 return mNetworkScorerAppManager.getAllValidScorers();
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800803 }
804
Jeff Davidson26fd1432014-07-29 09:39:52 -0700805 @Override
806 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800807 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700808 enforceSystemOrIsActiveScorer(getCallingUid());
Jeremy Josline9052a32017-02-27 15:47:54 -0800809 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700810 }
811
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700812 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
813 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800814 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800815 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800816 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800817 try {
818 networkScoreCache.clearScores();
819 } catch (RemoteException e) {
820 if (Log.isLoggable(TAG, Log.VERBOSE)) {
821 Log.v(TAG, "Unable to clear scores", e);
822 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700823 }
824 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800825 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700826 }
827
828 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800829 public void registerNetworkScoreCache(int networkType,
830 INetworkScoreCache scoreCache,
831 int filterType) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700832 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800833 final long token = Binder.clearCallingIdentity();
834 try {
835 synchronized (mScoreCaches) {
836 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
837 if (callbackList == null) {
838 callbackList = new RemoteCallbackList<>();
839 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800840 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800841 if (!callbackList.register(scoreCache, filterType)) {
842 if (callbackList.getRegisteredCallbackCount() == 0) {
843 mScoreCaches.remove(networkType);
844 }
845 if (Log.isLoggable(TAG, Log.VERBOSE)) {
846 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
847 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800848 }
849 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800850 } finally {
851 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800852 }
853 }
854
855 @Override
856 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700857 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800858 final long token = Binder.clearCallingIdentity();
859 try {
860 synchronized (mScoreCaches) {
861 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
862 if (callbackList == null || !callbackList.unregister(scoreCache)) {
863 if (Log.isLoggable(TAG, Log.VERBOSE)) {
864 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
865 + networkType);
866 }
867 } else if (callbackList.getRegisteredCallbackCount() == 0) {
868 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800869 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800870 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800871 } finally {
872 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700873 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700874 }
875
876 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800877 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin1588b3b2017-08-11 15:35:12 -0700878 enforceSystemOnly();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800879 final long token = Binder.clearCallingIdentity();
880 try {
881 final INetworkRecommendationProvider provider = getRecommendationProvider();
882 if (provider != null) {
883 try {
884 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800885 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800886 // prevent repeated requests for the same scores.
887 return true;
888 } catch (RemoteException e) {
889 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800890 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800891 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800892 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800893 return false;
894 } finally {
895 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800896 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800897 }
898
899 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800900 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600901 if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800902 final long token = Binder.clearCallingIdentity();
903 try {
904 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
905 if (currentScorer == null) {
906 writer.println("Scoring is disabled.");
907 return;
908 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800909 writer.println("Current scorer: " + currentScorer);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700910
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800911 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
912 @Override
913 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
914 try {
915 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
916 } catch (IOException | RemoteException e) {
917 writer.println("Failed to dump score cache: " + e);
918 }
919 }
920 }, getScoreCacheLists());
921
922 synchronized (mServiceConnectionLock) {
923 if (mServiceConnection != null) {
924 mServiceConnection.dump(fd, writer, args);
925 } else {
926 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800927 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700928 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800929 writer.flush();
930 } finally {
931 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700932 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700933 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700934
935 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800936 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700937 *
938 * <p>May be used to perform an action on all score caches without potentially strange behavior
939 * if a new scorer is registered during that action's execution.
940 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800941 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700942 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800943 return new ArrayList<>(mScoreCaches.values());
944 }
945 }
946
Jeremy Joslinba242732017-01-24 17:16:42 -0800947 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800948 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
949 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
950 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
951 final int count = callbackList.beginBroadcast();
952 try {
953 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800954 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800955 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800956 }
957 } finally {
958 callbackList.finishBroadcast();
959 }
960 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700961 }
962 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700963
Jeremy Joslin145c3432016-12-09 13:11:51 -0800964 @Nullable
965 private INetworkRecommendationProvider getRecommendationProvider() {
966 synchronized (mServiceConnectionLock) {
967 if (mServiceConnection != null) {
968 return mServiceConnection.getRecommendationProvider();
969 }
970 }
971 return null;
972 }
973
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700974 // The class and methods need to be public for Mockito to work.
975 @VisibleForTesting
976 public static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800977 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800978 private volatile boolean mBound = false;
979 private volatile boolean mConnected = false;
980 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700981
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800982 ScoringServiceConnection(NetworkScorerAppData appData) {
983 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700984 }
985
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700986 @VisibleForTesting
987 public void bind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700988 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800989 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800990 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700991 mBound = context.bindServiceAsUser(service, this,
992 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
993 UserHandle.SYSTEM);
994 if (!mBound) {
995 Log.w(TAG, "Bind call failed for " + service);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700996 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700997 } else {
998 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
999 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001000 }
1001 }
1002
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001003 @VisibleForTesting
1004 public void unbind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001005 try {
1006 if (mBound) {
1007 mBound = false;
1008 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001009 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001010 }
1011 } catch (RuntimeException e) {
1012 Log.e(TAG, "Unbind failed.", e);
1013 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001014
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001015 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001016 mRecommendationProvider = null;
1017 }
1018
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001019 @VisibleForTesting
1020 public NetworkScorerAppData getAppData() {
1021 return mAppData;
1022 }
1023
1024 @VisibleForTesting
1025 public INetworkRecommendationProvider getRecommendationProvider() {
Jeremy Joslin145c3432016-12-09 13:11:51 -08001026 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001027 }
1028
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001029 @VisibleForTesting
1030 public String getPackageName() {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001031 return mAppData.getRecommendationServiceComponent().getPackageName();
1032 }
1033
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001034 @VisibleForTesting
1035 public boolean isAlive() {
1036 return mBound && mConnected;
1037 }
1038
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001039 @Override
1040 public void onServiceConnected(ComponentName name, IBinder service) {
1041 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001042 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001043 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001044 }
1045
1046 @Override
1047 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001048 if (DBG) {
1049 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1050 }
1051 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001052 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001053 }
1054
1055 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001056 writer.println("ScoringServiceConnection: "
1057 + mAppData.getRecommendationServiceComponent()
1058 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001059 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001060 }
1061 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001062
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001063 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001064 public final class ServiceHandler extends Handler {
Jeremy Joslinb1a01392017-04-14 13:35:48 -07001065 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 1;
1066 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 2;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001067
1068 public ServiceHandler(Looper looper) {
1069 super(looper);
1070 }
1071
1072 @Override
1073 public void handleMessage(Message msg) {
1074 final int what = msg.what;
1075 switch (what) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001076 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001077 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001078 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001079 break;
1080
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001081 default:
1082 Log.w(TAG,"Unknown message: " + what);
1083 }
1084 }
1085 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001086}