blob: e438620faeb7767266a53d402358cd56afa91db8 [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;
Jeremy Joslina5172f62017-02-02 14:27:05 -080049import android.os.Process;
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;
Amin Shaikh972e2362016-12-07 14:08:09 -080054import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080055import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070056import android.util.Log;
Jeremy Joslin145c3432016-12-09 13:11:51 -080057
Jeff Davidson7842f642014-11-23 13:48:12 -080058import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080059import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070060import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070061import com.android.internal.os.TransferPipe;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060062import com.android.internal.util.DumpUtils;
Jeremy Joslin145c3432016-12-09 13:11:51 -080063
Jeff Davidson6a4b2202014-04-16 17:29:40 -070064import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070065import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070066import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070067import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080068import java.util.Collection;
69import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070070import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070071import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080072import java.util.Set;
Jeremy Joslinba242732017-01-24 17:16:42 -080073import java.util.function.BiConsumer;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070074import java.util.function.Function;
Jeremy Joslinba242732017-01-24 17:16:42 -080075import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080076import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070077
78/**
79 * Backing service for {@link android.net.NetworkScoreManager}.
80 * @hide
81 */
82public class NetworkScoreService extends INetworkScoreService.Stub {
83 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080084 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080085 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070086
Jeff Davidson6a4b2202014-04-16 17:29:40 -070087 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080088 private final NetworkScorerAppManager mNetworkScorerAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -080089 @GuardedBy("mScoreCaches")
90 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070091 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -080092 private final Object mPackageMonitorLock = new Object();
93 private final Object mServiceConnectionLock = new Object();
94 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -080095 private final DispatchingContentObserver mContentObserver;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070096 private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer;
Jeff Davidson7842f642014-11-23 13:48:12 -080097
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070098 @GuardedBy("mPackageMonitorLock")
99 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800100 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700101 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -0800102
Jeremy Joslin967b5812016-06-02 07:58:14 -0700103 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
104 @Override
105 public void onReceive(Context context, Intent intent) {
106 final String action = intent.getAction();
107 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
108 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
109 if (userId == UserHandle.USER_NULL) return;
110
111 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
112 onUserUnlocked(userId);
113 }
114 }
115 };
116
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700117 private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
118 @Override
119 public void onReceive(Context context, Intent intent) {
120 final String action = intent.getAction();
121 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
122 refreshBinding();
123 }
124 }
125 };
126
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700127 /**
128 * Clears scores when the active scorer package is no longer valid and
129 * manages the service connection.
130 */
131 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800132 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800133
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800134 private NetworkScorerPackageMonitor(String packageToWatch) {
135 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800136 }
137
138 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700139 public void onPackageAdded(String packageName, int uid) {
140 evaluateBinding(packageName, true /* forceUnbind */);
141 }
142
143 @Override
144 public void onPackageRemoved(String packageName, int uid) {
145 evaluateBinding(packageName, true /* forceUnbind */);
146 }
147
148 @Override
149 public void onPackageModified(String packageName) {
150 evaluateBinding(packageName, false /* forceUnbind */);
151 }
152
153 @Override
154 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
155 if (doit) { // "doit" means the force stop happened instead of just being queried for.
156 for (String packageName : packages) {
157 evaluateBinding(packageName, true /* forceUnbind */);
158 }
159 }
160 return super.onHandleForceStop(intent, packages, uid, doit);
161 }
162
163 @Override
164 public void onPackageUpdateFinished(String packageName, int uid) {
165 evaluateBinding(packageName, true /* forceUnbind */);
166 }
167
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800168 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
169 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800170 // Early exit when we don't care about the package that has changed.
171 return;
172 }
173
174 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800175 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800176 + ", forceUnbind=" + forceUnbind);
177 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800178
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800179 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
180 if (activeScorer == null) {
181 // Package change has invalidated a scorer, this will also unbind any service
182 // connection.
183 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800184 refreshBinding();
185 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800186 if (forceUnbind) {
187 unbindFromScoringServiceIfNeeded();
188 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800189 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800190 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
191 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800192 }
193 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800194 }
195 }
196 }
197
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800198 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800199 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800200 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800201 @VisibleForTesting
202 public static class DispatchingContentObserver extends ContentObserver {
203 final private Map<Uri, Integer> mUriEventMap;
204 final private Context mContext;
205 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800206
Jeremy Joslincb594f32017-01-03 17:31:23 -0800207 public DispatchingContentObserver(Context context, Handler handler) {
208 super(handler);
209 mContext = context;
210 mHandler = handler;
211 mUriEventMap = new ArrayMap<>();
212 }
213
214 void observe(Uri uri, int what) {
215 mUriEventMap.put(uri, what);
216 final ContentResolver resolver = mContext.getContentResolver();
217 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800218 }
219
220 @Override
221 public void onChange(boolean selfChange) {
222 onChange(selfChange, null);
223 }
224
225 @Override
226 public void onChange(boolean selfChange, Uri uri) {
227 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800228 final Integer what = mUriEventMap.get(uri);
229 if (what != null) {
230 mHandler.obtainMessage(what).sendToTarget();
231 } else {
232 Log.w(TAG, "No matching event to send for URI = " + uri);
233 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800234 }
235 }
236
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700237 public NetworkScoreService(Context context) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700238 this(context, new NetworkScorerAppManager(context),
239 ScoringServiceConnection::new, Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800240 }
241
242 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800243 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700244 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer,
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800245 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700246 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800247 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800248 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700249 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
250 // TODO: Need to update when we support per-user scorers. http://b/23422763
251 mContext.registerReceiverAsUser(
252 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
253 null /* scheduler */);
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800254 mHandler = new ServiceHandler(looper);
Jeremy Joslin59502eb2017-07-14 15:00:53 -0700255 IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
256 mContext.registerReceiverAsUser(
257 mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
258 null /* broadcastPermission*/, mHandler);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800259 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700260 mServiceConnProducer = serviceConnProducer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700261 }
262
263 /** Called when the system is ready to run third-party code but before it actually does so. */
264 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700265 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800266 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800267 }
268
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700269 /** Called when the system is ready for us to start third-party code. */
270 void systemRunning() {
271 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700272 }
273
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800274 @VisibleForTesting
275 void onUserUnlocked(int userId) {
276 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
277 refreshBinding();
278 }
279
280 private void refreshBinding() {
281 if (DBG) Log.d(TAG, "refreshBinding()");
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800282 // Make sure the scorer is up-to-date
283 mNetworkScorerAppManager.updateState();
Jeremy Joslinb0fe2172017-03-31 10:38:31 -0700284 mNetworkScorerAppManager.migrateNetworkScorerAppSettingIfNeeded();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700285 registerPackageMonitorIfNeeded();
286 bindToScoringServiceIfNeeded();
287 }
288
Jeremy Joslincb594f32017-01-03 17:31:23 -0800289 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800290 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
291 mContentObserver.observe(packageNameUri,
292 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800293
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800294 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
295 mContentObserver.observe(settingUri,
296 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800297 }
298
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800299 /**
300 * Ensures the package manager is registered to monitor the current active scorer.
301 * If a discrepancy is found any previous monitor will be cleaned up
302 * and a new monitor will be created.
303 *
304 * This method is idempotent.
305 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700306 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800307 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
308 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700309 synchronized (mPackageMonitorLock) {
310 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800311 if (mPackageMonitor != null && (appData == null
312 || !appData.getRecommendationServicePackageName().equals(
313 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700314 if (DBG) {
315 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800316 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800317 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700318 mPackageMonitor.unregister();
319 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800320 }
321
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800322 // Create and register the monitor if a scorer is active.
323 if (appData != null && mPackageMonitor == null) {
324 mPackageMonitor = new NetworkScorerPackageMonitor(
325 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700326 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700327 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
328 false /* externalStorage */);
329 if (DBG) {
330 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800331 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800332 }
333 }
334 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700335 }
336
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700337 private void bindToScoringServiceIfNeeded() {
338 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800339 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700340 bindToScoringServiceIfNeeded(scorerData);
341 }
342
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800343 /**
344 * Ensures the service connection is bound to the current active scorer.
345 * If a discrepancy is found any previous connection will be cleaned up
346 * and a new connection will be created.
347 *
348 * This method is idempotent.
349 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800350 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
351 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
352 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800353 synchronized (mServiceConnectionLock) {
354 // If we're connected to a different component then drop it.
355 if (mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700356 && !mServiceConnection.getAppData().equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800357 unbindFromScoringServiceIfNeeded();
358 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700359
Jeremy Joslin145c3432016-12-09 13:11:51 -0800360 // If we're not connected at all then create a new connection.
361 if (mServiceConnection == null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700362 mServiceConnection = mServiceConnProducer.apply(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800363 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700364
Jeremy Joslin145c3432016-12-09 13:11:51 -0800365 // Make sure the connection is connected (idempotent)
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700366 mServiceConnection.bind(mContext);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800367 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700368 } else { // otherwise make sure it isn't bound.
369 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700370 }
371 }
372
373 private void unbindFromScoringServiceIfNeeded() {
374 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800375 synchronized (mServiceConnectionLock) {
376 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700377 mServiceConnection.unbind(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800378 if (DBG) Log.d(TAG, "Disconnected from: "
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700379 + mServiceConnection.getAppData().getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800380 }
381 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700382 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800383 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700384 }
385
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700386 @Override
387 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800388 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700389 throw new SecurityException("Caller with UID " + getCallingUid() +
390 " is not the active scorer.");
391 }
392
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800393 final long token = Binder.clearCallingIdentity();
394 try {
395 // Separate networks by type.
396 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
397 for (ScoredNetwork network : networks) {
398 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
399 if (networkList == null) {
400 networkList = new ArrayList<>();
401 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800402 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800403 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800404 }
405
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800406 // Pass the scores of each type down to the appropriate network scorer.
407 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
408 final RemoteCallbackList<INetworkScoreCache> callbackList;
409 final boolean isEmpty;
410 synchronized (mScoreCaches) {
411 callbackList = mScoreCaches.get(entry.getKey());
412 isEmpty = callbackList == null
413 || callbackList.getRegisteredCallbackCount() == 0;
414 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800415
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800416 if (isEmpty) {
417 if (Log.isLoggable(TAG, Log.VERBOSE)) {
418 Log.v(TAG, "No scorer registered for type " + entry.getKey()
419 + ", discarding");
420 }
421 continue;
422 }
423
Jeremy Joslinba242732017-01-24 17:16:42 -0800424 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800425 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800426 entry.getKey());
427 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800428 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700429
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800430 return true;
431 } finally {
432 Binder.restoreCallingIdentity(token);
433 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700434 }
435
Jeremy Joslinba242732017-01-24 17:16:42 -0800436 /**
437 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
438 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
439 * accepted {@link INetworkScoreCache} implementation.
440 */
441 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800442 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800443 implements BiConsumer<INetworkScoreCache, Object> {
444 private final Context mContext;
445 private final List<ScoredNetwork> mScoredNetworkList;
446 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800447 // TODO: 1/23/17 - Consider a Map if we implement more filters.
448 // These are created on-demand to defer the construction cost until
449 // an instance is actually needed.
450 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
451 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800452
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800453 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800454 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800455 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
456 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800457 }
458
459 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800460 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800461 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800462 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
463 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800464 mContext = context;
465 mScoredNetworkList = scoredNetworkList;
466 mNetworkType = networkType;
467 mCurrentNetworkFilter = currentNetworkFilter;
468 mScanResultsFilter = scanResultsFilter;
469 }
470
471 @Override
472 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
473 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
474 if (cookie instanceof Integer) {
475 filterType = (Integer) cookie;
476 }
477
478 try {
479 final List<ScoredNetwork> filteredNetworkList =
480 filterScores(mScoredNetworkList, filterType);
481 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800482 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800483 }
484 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800485 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800486 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
487 }
488 }
489 }
490
491 /**
492 * Applies the appropriate filter and returns the filtered results.
493 */
494 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
495 int filterType) {
496 switch (filterType) {
497 case NetworkScoreManager.CACHE_FILTER_NONE:
498 return scoredNetworkList;
499
500 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
501 if (mCurrentNetworkFilter == null) {
502 mCurrentNetworkFilter =
503 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
504 }
505 return mCurrentNetworkFilter.apply(scoredNetworkList);
506
507 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
508 if (mScanResultsFilter == null) {
509 mScanResultsFilter = new ScanResultsScoreCacheFilter(
510 new ScanResultsSupplier(mContext));
511 }
512 return mScanResultsFilter.apply(scoredNetworkList);
513
514 default:
515 Log.w(TAG, "Unknown filter type: " + filterType);
516 return scoredNetworkList;
517 }
518 }
519 }
520
521 /**
522 * Helper class that improves the testability of the cache filter Functions.
523 */
524 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
525 private final Context mContext;
526
527 WifiInfoSupplier(Context context) {
528 mContext = context;
529 }
530
531 @Override
532 public WifiInfo get() {
533 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
534 if (wifiManager != null) {
535 return wifiManager.getConnectionInfo();
536 }
537 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
538 return null;
539 }
540 }
541
542 /**
543 * Helper class that improves the testability of the cache filter Functions.
544 */
545 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
546 private final Context mContext;
547
548 ScanResultsSupplier(Context context) {
549 mContext = context;
550 }
551
552 @Override
553 public List<ScanResult> get() {
554 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
555 if (wifiScanner != null) {
556 return wifiScanner.getSingleScanResults();
557 }
558 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
559 return Collections.emptyList();
560 }
561 }
562
563 /**
564 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
565 * {@link ScoredNetwork} associated with the current network. If no network is connected the
566 * returned list will be empty.
567 * <p>
568 * Note: this filter performs some internal caching for consistency and performance. The
569 * current network is determined at construction time and never changed. Also, the
570 * last filtered list is saved so if the same input is provided multiple times in a row
571 * the computation is only done once.
572 */
573 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800574 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800575 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800576
577 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
578 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
579 }
580
581 @Override
582 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
583 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
584 return Collections.emptyList();
585 }
586
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800587 for (int i = 0; i < scoredNetworks.size(); i++) {
588 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
589 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
590 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800591 }
592 }
593
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800594 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800595 }
596 }
597
598 /**
599 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
600 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
601 * If there are no {@link ScanResult}s the returned list will be empty.
602 * <p>
603 * Note: this filter performs some internal caching for consistency and performance. The
604 * current set of ScanResults is determined at construction time and never changed.
605 * Also, the last filtered list is saved so if the same input is provided multiple
606 * times in a row the computation is only done once.
607 */
608 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800609 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
610 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800611
612 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800613 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800614 final int size = scanResults.size();
615 mScanResultKeys = new ArraySet<>(size);
616 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800617 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800618 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
619 if (key != null) {
620 mScanResultKeys.add(key);
621 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800622 }
623 }
624
625 @Override
626 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
627 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
628 return Collections.emptyList();
629 }
630
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800631 List<ScoredNetwork> filteredScores = new ArrayList<>();
632 for (int i = 0; i < scoredNetworks.size(); i++) {
633 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
634 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
635 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800636 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800637 }
638
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800639 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800640 }
641 }
642
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700643 private boolean canCallerRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800644 // REQUEST_NETWORK_SCORES is a signature only permission.
645 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
646 PackageManager.PERMISSION_GRANTED;
647 }
648
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700649 private boolean canCallerScoreNetworks() {
Jeremy Joslina9f933e2017-04-28 13:08:17 -0700650 return mContext.checkCallingOrSelfPermission(permission.SCORE_NETWORKS) ==
651 PackageManager.PERMISSION_GRANTED;
652 }
653
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700654 @Override
655 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800656 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700657 if (isCallerActiveScorer(getCallingUid()) || canCallerRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800658 final long token = Binder.clearCallingIdentity();
659 try {
660 clearInternal();
661 return true;
662 } finally {
663 Binder.restoreCallingIdentity(token);
664 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700665 } else {
666 throw new SecurityException(
667 "Caller is neither the active scorer nor the scorer manager.");
668 }
669 }
670
671 @Override
672 public boolean setActiveScorer(String packageName) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800673 // Only the system can set the active scorer
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700674 if (!isCallerSystemProcess(getCallingUid()) && !canCallerScoreNetworks()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800675 throw new SecurityException(
Jeremy Joslina9f933e2017-04-28 13:08:17 -0700676 "Caller is neither the system process or a network scorer.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800677 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800678
679 return mNetworkScorerAppManager.setActiveScorer(packageName);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700680 }
681
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800682 /**
683 * Determine whether the application with the given UID is the enabled scorer.
684 *
685 * @param callingUid the UID to check
686 * @return true if the provided UID is the active scorer, false otherwise.
687 */
688 @Override
689 public boolean isCallerActiveScorer(int callingUid) {
690 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800691 return mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700692 && mServiceConnection.getAppData().packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800693 }
694 }
695
Jeremy Joslina5172f62017-02-02 14:27:05 -0800696 private boolean isCallerSystemProcess(int callingUid) {
697 return callingUid == Process.SYSTEM_UID;
698 }
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 Joslin2d55b182017-07-11 15:54:14 -0700708 if (canCallerRequestScores() || canCallerScoreNetworks()) {
709 synchronized (mServiceConnectionLock) {
710 if (mServiceConnection != null) {
711 return mServiceConnection.getPackageName();
712 }
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800713 }
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700714 } else {
715 throw new SecurityException(
716 "Caller is not a network scorer/requester.");
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800717 }
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700718
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800719 return null;
720 }
721
Jeremy Joslina5172f62017-02-02 14:27:05 -0800722 /**
723 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
724 */
725 @Override
726 public NetworkScorerAppData getActiveScorer() {
727 // Only the system can access this data.
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700728 if (isCallerSystemProcess(getCallingUid()) || canCallerRequestScores()) {
Jeremy Joslina5172f62017-02-02 14:27:05 -0800729 synchronized (mServiceConnectionLock) {
730 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700731 return mServiceConnection.getAppData();
Jeremy Joslina5172f62017-02-02 14:27:05 -0800732 }
733 }
734 } else {
735 throw new SecurityException(
736 "Caller is neither the system process nor a score requester.");
737 }
738
739 return null;
740 }
741
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800742 /**
743 * Returns the list of available scorer apps. The list will be empty if there are
744 * no valid scorers.
745 */
746 @Override
747 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800748 // Only the system can access this data.
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700749 if (!isCallerSystemProcess(getCallingUid()) && !canCallerRequestScores()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800750 throw new SecurityException(
751 "Caller is neither the system process nor a score requester.");
752 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800753
754 return mNetworkScorerAppManager.getAllValidScorers();
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800755 }
756
Jeff Davidson26fd1432014-07-29 09:39:52 -0700757 @Override
758 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800759 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin2d55b182017-07-11 15:54:14 -0700760 if (!isCallerActiveScorer(getCallingUid()) && !canCallerRequestScores()) {
Jeff Davidson26fd1432014-07-29 09:39:52 -0700761 throw new SecurityException(
762 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700763 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800764
765 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700766 }
767
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700768 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
769 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800770 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800771 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800772 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800773 try {
774 networkScoreCache.clearScores();
775 } catch (RemoteException e) {
776 if (Log.isLoggable(TAG, Log.VERBOSE)) {
777 Log.v(TAG, "Unable to clear scores", e);
778 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700779 }
780 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800781 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700782 }
783
784 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800785 public void registerNetworkScoreCache(int networkType,
786 INetworkScoreCache scoreCache,
787 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800788 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800789 final long token = Binder.clearCallingIdentity();
790 try {
791 synchronized (mScoreCaches) {
792 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
793 if (callbackList == null) {
794 callbackList = new RemoteCallbackList<>();
795 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800796 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800797 if (!callbackList.register(scoreCache, filterType)) {
798 if (callbackList.getRegisteredCallbackCount() == 0) {
799 mScoreCaches.remove(networkType);
800 }
801 if (Log.isLoggable(TAG, Log.VERBOSE)) {
802 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
803 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800804 }
805 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800806 } finally {
807 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800808 }
809 }
810
811 @Override
812 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800813 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800814 final long token = Binder.clearCallingIdentity();
815 try {
816 synchronized (mScoreCaches) {
817 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
818 if (callbackList == null || !callbackList.unregister(scoreCache)) {
819 if (Log.isLoggable(TAG, Log.VERBOSE)) {
820 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
821 + networkType);
822 }
823 } else if (callbackList.getRegisteredCallbackCount() == 0) {
824 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800825 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800826 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800827 } finally {
828 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700829 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700830 }
831
832 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800833 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800834 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800835 final long token = Binder.clearCallingIdentity();
836 try {
837 final INetworkRecommendationProvider provider = getRecommendationProvider();
838 if (provider != null) {
839 try {
840 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800841 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800842 // prevent repeated requests for the same scores.
843 return true;
844 } catch (RemoteException e) {
845 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800846 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800847 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800848 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800849 return false;
850 } finally {
851 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800852 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800853 }
854
855 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800856 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600857 if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800858 final long token = Binder.clearCallingIdentity();
859 try {
860 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
861 if (currentScorer == null) {
862 writer.println("Scoring is disabled.");
863 return;
864 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800865 writer.println("Current scorer: " + currentScorer);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700866
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800867 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
868 @Override
869 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
870 try {
871 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
872 } catch (IOException | RemoteException e) {
873 writer.println("Failed to dump score cache: " + e);
874 }
875 }
876 }, getScoreCacheLists());
877
878 synchronized (mServiceConnectionLock) {
879 if (mServiceConnection != null) {
880 mServiceConnection.dump(fd, writer, args);
881 } else {
882 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800883 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700884 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800885 writer.flush();
886 } finally {
887 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700888 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700889 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700890
891 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800892 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700893 *
894 * <p>May be used to perform an action on all score caches without potentially strange behavior
895 * if a new scorer is registered during that action's execution.
896 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800897 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700898 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800899 return new ArrayList<>(mScoreCaches.values());
900 }
901 }
902
Jeremy Joslinba242732017-01-24 17:16:42 -0800903 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800904 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
905 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
906 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
907 final int count = callbackList.beginBroadcast();
908 try {
909 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800910 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800911 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800912 }
913 } finally {
914 callbackList.finishBroadcast();
915 }
916 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700917 }
918 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700919
Jeremy Joslin145c3432016-12-09 13:11:51 -0800920 @Nullable
921 private INetworkRecommendationProvider getRecommendationProvider() {
922 synchronized (mServiceConnectionLock) {
923 if (mServiceConnection != null) {
924 return mServiceConnection.getRecommendationProvider();
925 }
926 }
927 return null;
928 }
929
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700930 // The class and methods need to be public for Mockito to work.
931 @VisibleForTesting
932 public static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800933 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800934 private volatile boolean mBound = false;
935 private volatile boolean mConnected = false;
936 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700937
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800938 ScoringServiceConnection(NetworkScorerAppData appData) {
939 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700940 }
941
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700942 @VisibleForTesting
943 public void bind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700944 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800945 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800946 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700947 mBound = context.bindServiceAsUser(service, this,
948 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
949 UserHandle.SYSTEM);
950 if (!mBound) {
951 Log.w(TAG, "Bind call failed for " + service);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700952 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700953 } else {
954 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
955 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700956 }
957 }
958
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700959 @VisibleForTesting
960 public void unbind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700961 try {
962 if (mBound) {
963 mBound = false;
964 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700965 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700966 }
967 } catch (RuntimeException e) {
968 Log.e(TAG, "Unbind failed.", e);
969 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800970
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700971 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800972 mRecommendationProvider = null;
973 }
974
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700975 @VisibleForTesting
976 public NetworkScorerAppData getAppData() {
977 return mAppData;
978 }
979
980 @VisibleForTesting
981 public INetworkRecommendationProvider getRecommendationProvider() {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800982 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700983 }
984
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700985 @VisibleForTesting
986 public String getPackageName() {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800987 return mAppData.getRecommendationServiceComponent().getPackageName();
988 }
989
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700990 @VisibleForTesting
991 public boolean isAlive() {
992 return mBound && mConnected;
993 }
994
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700995 @Override
996 public void onServiceConnected(ComponentName name, IBinder service) {
997 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700998 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800999 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001000 }
1001
1002 @Override
1003 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001004 if (DBG) {
1005 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1006 }
1007 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001008 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001009 }
1010
1011 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001012 writer.println("ScoringServiceConnection: "
1013 + mAppData.getRecommendationServiceComponent()
1014 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001015 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001016 }
1017 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001018
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001019 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001020 public final class ServiceHandler extends Handler {
Jeremy Joslinb1a01392017-04-14 13:35:48 -07001021 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 1;
1022 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 2;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001023
1024 public ServiceHandler(Looper looper) {
1025 super(looper);
1026 }
1027
1028 @Override
1029 public void handleMessage(Message msg) {
1030 final int what = msg.what;
1031 switch (what) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001032 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001033 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001034 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001035 break;
1036
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001037 default:
1038 Log.w(TAG,"Unknown message: " + what);
1039 }
1040 }
1041 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001042}