blob: 46c9f25dbccfb033cc286e7fd4b6a5be550bc70a [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
Jeremy Joslin145c3432016-12-09 13:11:51 -080019import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
20import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
21
Jeff Davidson6a4b2202014-04-16 17:29:40 -070022import android.Manifest.permission;
Jeremy Joslin145c3432016-12-09 13:11:51 -080023import android.annotation.Nullable;
Jeremy Joslin967b5812016-06-02 07:58:14 -070024import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070025import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070026import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070028import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070029import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070030import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.content.pm.PackageManager;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080032import android.database.ContentObserver;
Jeremy Joslin145c3432016-12-09 13:11:51 -080033import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070034import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070035import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080036import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080037import android.net.NetworkScoreManager;
Jeremy Joslinf621bc92017-02-16 11:11:57 -080038import android.net.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080039import android.net.RecommendationRequest;
40import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070041import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080042import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080043import android.net.wifi.ScanResult;
44import android.net.wifi.WifiInfo;
45import android.net.wifi.WifiManager;
46import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080047import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080048import android.os.Build;
Jeremy Joslin145c3432016-12-09 13:11:51 -080049import android.os.Bundle;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080050import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070051import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080052import android.os.IRemoteCallback;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080053import android.os.Looper;
54import android.os.Message;
Jeremy Joslina5172f62017-02-02 14:27:05 -080055import android.os.Process;
Jeremy Joslin998d7ca2016-12-28 15:56:46 -080056import android.os.RemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080057import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070058import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070059import android.os.UserHandle;
Jeremy Joslincb594f32017-01-03 17:31:23 -080060import android.provider.Settings;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080061import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080062import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080063import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080065import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066import android.util.TimedRemoteCaller;
67
Jeff Davidson7842f642014-11-23 13:48:12 -080068import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080069import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070070import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070071import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080072
Jeff Davidson6a4b2202014-04-16 17:29:40 -070073import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070074import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070075import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070076import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080077import java.util.Collection;
78import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070079import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070080import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080081import java.util.Set;
Jeremy Joslin145c3432016-12-09 13:11:51 -080082import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080083import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080084import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080085import java.util.function.BiConsumer;
Jeremy Joslinba242732017-01-24 17:16:42 -080086import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080087import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070088
89/**
90 * Backing service for {@link android.net.NetworkScoreManager}.
91 * @hide
92 */
93public class NetworkScoreService extends INetworkScoreService.Stub {
94 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080095 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080096 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070097
Jeff Davidson6a4b2202014-04-16 17:29:40 -070098 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080099 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800100 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -0800101 @GuardedBy("mScoreCaches")
102 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700103 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800104 private final Object mPackageMonitorLock = new Object();
105 private final Object mServiceConnectionLock = new Object();
106 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800107 private final DispatchingContentObserver mContentObserver;
Jeff Davidson7842f642014-11-23 13:48:12 -0800108
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700109 @GuardedBy("mPackageMonitorLock")
110 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800111 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700112 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800113 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800114
Jeremy Joslin967b5812016-06-02 07:58:14 -0700115 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
116 @Override
117 public void onReceive(Context context, Intent intent) {
118 final String action = intent.getAction();
119 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
120 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
121 if (userId == UserHandle.USER_NULL) return;
122
123 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
124 onUserUnlocked(userId);
125 }
126 }
127 };
128
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700129 /**
130 * Clears scores when the active scorer package is no longer valid and
131 * manages the service connection.
132 */
133 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800134 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800135
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800136 private NetworkScorerPackageMonitor(String packageToWatch) {
137 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800138 }
139
140 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700141 public void onPackageAdded(String packageName, int uid) {
142 evaluateBinding(packageName, true /* forceUnbind */);
143 }
144
145 @Override
146 public void onPackageRemoved(String packageName, int uid) {
147 evaluateBinding(packageName, true /* forceUnbind */);
148 }
149
150 @Override
151 public void onPackageModified(String packageName) {
152 evaluateBinding(packageName, false /* forceUnbind */);
153 }
154
155 @Override
156 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
157 if (doit) { // "doit" means the force stop happened instead of just being queried for.
158 for (String packageName : packages) {
159 evaluateBinding(packageName, true /* forceUnbind */);
160 }
161 }
162 return super.onHandleForceStop(intent, packages, uid, doit);
163 }
164
165 @Override
166 public void onPackageUpdateFinished(String packageName, int uid) {
167 evaluateBinding(packageName, true /* forceUnbind */);
168 }
169
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800170 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
171 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800172 // Early exit when we don't care about the package that has changed.
173 return;
174 }
175
176 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800177 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800178 + ", forceUnbind=" + forceUnbind);
179 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800180
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800181 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
182 if (activeScorer == null) {
183 // Package change has invalidated a scorer, this will also unbind any service
184 // connection.
185 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800186 refreshBinding();
187 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800188 if (forceUnbind) {
189 unbindFromScoringServiceIfNeeded();
190 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800191 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800192 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
193 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800194 }
195 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800196 }
197 }
198 }
199
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800200 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800201 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800202 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800203 @VisibleForTesting
204 public static class DispatchingContentObserver extends ContentObserver {
205 final private Map<Uri, Integer> mUriEventMap;
206 final private Context mContext;
207 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800208
Jeremy Joslincb594f32017-01-03 17:31:23 -0800209 public DispatchingContentObserver(Context context, Handler handler) {
210 super(handler);
211 mContext = context;
212 mHandler = handler;
213 mUriEventMap = new ArrayMap<>();
214 }
215
216 void observe(Uri uri, int what) {
217 mUriEventMap.put(uri, what);
218 final ContentResolver resolver = mContext.getContentResolver();
219 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800220 }
221
222 @Override
223 public void onChange(boolean selfChange) {
224 onChange(selfChange, null);
225 }
226
227 @Override
228 public void onChange(boolean selfChange, Uri uri) {
229 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800230 final Integer what = mUriEventMap.get(uri);
231 if (what != null) {
232 mHandler.obtainMessage(what).sendToTarget();
233 } else {
234 Log.w(TAG, "No matching event to send for URI = " + uri);
235 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800236 }
237 }
238
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700239 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800240 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800241 }
242
243 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800244 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
245 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 Joslin3452b692017-01-17 15:48:13 -0800254 mReqRecommendationCallerRef = new AtomicReference<>(
255 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800256 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
257 mHandler = new ServiceHandler(looper);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800258 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700259 }
260
261 /** Called when the system is ready to run third-party code but before it actually does so. */
262 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700263 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800264 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800265 }
266
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700267 /** Called when the system is ready for us to start third-party code. */
268 void systemRunning() {
269 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700270 }
271
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800272 @VisibleForTesting
273 void onUserUnlocked(int userId) {
274 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
275 refreshBinding();
276 }
277
278 private void refreshBinding() {
279 if (DBG) Log.d(TAG, "refreshBinding()");
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800280 // Make sure the scorer is up-to-date
281 mNetworkScorerAppManager.updateState();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700282 registerPackageMonitorIfNeeded();
283 bindToScoringServiceIfNeeded();
284 }
285
Jeremy Joslincb594f32017-01-03 17:31:23 -0800286 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800287 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
288 mContentObserver.observe(packageNameUri,
289 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800290
291 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
292 mContentObserver.observe(timeoutUri,
293 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800294
295 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
296 mContentObserver.observe(settingUri,
297 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800298 }
299
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800300 /**
301 * Ensures the package manager is registered to monitor the current active scorer.
302 * If a discrepancy is found any previous monitor will be cleaned up
303 * and a new monitor will be created.
304 *
305 * This method is idempotent.
306 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700307 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800308 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
309 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700310 synchronized (mPackageMonitorLock) {
311 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800312 if (mPackageMonitor != null && (appData == null
313 || !appData.getRecommendationServicePackageName().equals(
314 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700315 if (DBG) {
316 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800317 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800318 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700319 mPackageMonitor.unregister();
320 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800321 }
322
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800323 // Create and register the monitor if a scorer is active.
324 if (appData != null && mPackageMonitor == null) {
325 mPackageMonitor = new NetworkScorerPackageMonitor(
326 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700327 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700328 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
329 false /* externalStorage */);
330 if (DBG) {
331 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800332 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800333 }
334 }
335 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700336 }
337
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700338 private void bindToScoringServiceIfNeeded() {
339 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800340 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700341 bindToScoringServiceIfNeeded(scorerData);
342 }
343
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800344 /**
345 * Ensures the service connection is bound to the current active scorer.
346 * If a discrepancy is found any previous connection will be cleaned up
347 * and a new connection will be created.
348 *
349 * This method is idempotent.
350 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800351 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
352 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
353 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800354 synchronized (mServiceConnectionLock) {
355 // If we're connected to a different component then drop it.
356 if (mServiceConnection != null
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800357 && !mServiceConnection.mAppData.equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800358 unbindFromScoringServiceIfNeeded();
359 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700360
Jeremy Joslin145c3432016-12-09 13:11:51 -0800361 // If we're not connected at all then create a new connection.
362 if (mServiceConnection == null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800363 mServiceConnection = new ScoringServiceConnection(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800364 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700365
Jeremy Joslin145c3432016-12-09 13:11:51 -0800366 // Make sure the connection is connected (idempotent)
367 mServiceConnection.connect(mContext);
368 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700369 } else { // otherwise make sure it isn't bound.
370 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700371 }
372 }
373
374 private void unbindFromScoringServiceIfNeeded() {
375 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800376 synchronized (mServiceConnectionLock) {
377 if (mServiceConnection != null) {
378 mServiceConnection.disconnect(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800379 if (DBG) Log.d(TAG, "Disconnected from: "
380 + mServiceConnection.mAppData.getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800381 }
382 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700383 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800384 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700385 }
386
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700387 @Override
388 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800389 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700390 throw new SecurityException("Caller with UID " + getCallingUid() +
391 " is not the active scorer.");
392 }
393
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800394 final long token = Binder.clearCallingIdentity();
395 try {
396 // Separate networks by type.
397 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
398 for (ScoredNetwork network : networks) {
399 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
400 if (networkList == null) {
401 networkList = new ArrayList<>();
402 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800403 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800404 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800405 }
406
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800407 // Pass the scores of each type down to the appropriate network scorer.
408 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
409 final RemoteCallbackList<INetworkScoreCache> callbackList;
410 final boolean isEmpty;
411 synchronized (mScoreCaches) {
412 callbackList = mScoreCaches.get(entry.getKey());
413 isEmpty = callbackList == null
414 || callbackList.getRegisteredCallbackCount() == 0;
415 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800416
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800417 if (isEmpty) {
418 if (Log.isLoggable(TAG, Log.VERBOSE)) {
419 Log.v(TAG, "No scorer registered for type " + entry.getKey()
420 + ", discarding");
421 }
422 continue;
423 }
424
Jeremy Joslinba242732017-01-24 17:16:42 -0800425 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800426 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800427 entry.getKey());
428 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800429 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700430
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800431 return true;
432 } finally {
433 Binder.restoreCallingIdentity(token);
434 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700435 }
436
Jeremy Joslinba242732017-01-24 17:16:42 -0800437 /**
438 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
439 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
440 * accepted {@link INetworkScoreCache} implementation.
441 */
442 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800443 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800444 implements BiConsumer<INetworkScoreCache, Object> {
445 private final Context mContext;
446 private final List<ScoredNetwork> mScoredNetworkList;
447 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800448 // TODO: 1/23/17 - Consider a Map if we implement more filters.
449 // These are created on-demand to defer the construction cost until
450 // an instance is actually needed.
451 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
452 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800453
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800454 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800455 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800456 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
457 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800458 }
459
460 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800461 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800462 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800463 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
464 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800465 mContext = context;
466 mScoredNetworkList = scoredNetworkList;
467 mNetworkType = networkType;
468 mCurrentNetworkFilter = currentNetworkFilter;
469 mScanResultsFilter = scanResultsFilter;
470 }
471
472 @Override
473 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
474 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
475 if (cookie instanceof Integer) {
476 filterType = (Integer) cookie;
477 }
478
479 try {
480 final List<ScoredNetwork> filteredNetworkList =
481 filterScores(mScoredNetworkList, filterType);
482 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800483 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800484 }
485 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800486 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800487 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
488 }
489 }
490 }
491
492 /**
493 * Applies the appropriate filter and returns the filtered results.
494 */
495 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
496 int filterType) {
497 switch (filterType) {
498 case NetworkScoreManager.CACHE_FILTER_NONE:
499 return scoredNetworkList;
500
501 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
502 if (mCurrentNetworkFilter == null) {
503 mCurrentNetworkFilter =
504 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
505 }
506 return mCurrentNetworkFilter.apply(scoredNetworkList);
507
508 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
509 if (mScanResultsFilter == null) {
510 mScanResultsFilter = new ScanResultsScoreCacheFilter(
511 new ScanResultsSupplier(mContext));
512 }
513 return mScanResultsFilter.apply(scoredNetworkList);
514
515 default:
516 Log.w(TAG, "Unknown filter type: " + filterType);
517 return scoredNetworkList;
518 }
519 }
520 }
521
522 /**
523 * Helper class that improves the testability of the cache filter Functions.
524 */
525 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
526 private final Context mContext;
527
528 WifiInfoSupplier(Context context) {
529 mContext = context;
530 }
531
532 @Override
533 public WifiInfo get() {
534 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
535 if (wifiManager != null) {
536 return wifiManager.getConnectionInfo();
537 }
538 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
539 return null;
540 }
541 }
542
543 /**
544 * Helper class that improves the testability of the cache filter Functions.
545 */
546 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
547 private final Context mContext;
548
549 ScanResultsSupplier(Context context) {
550 mContext = context;
551 }
552
553 @Override
554 public List<ScanResult> get() {
555 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
556 if (wifiScanner != null) {
557 return wifiScanner.getSingleScanResults();
558 }
559 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
560 return Collections.emptyList();
561 }
562 }
563
564 /**
565 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
566 * {@link ScoredNetwork} associated with the current network. If no network is connected the
567 * returned list will be empty.
568 * <p>
569 * Note: this filter performs some internal caching for consistency and performance. The
570 * current network is determined at construction time and never changed. Also, the
571 * last filtered list is saved so if the same input is provided multiple times in a row
572 * the computation is only done once.
573 */
574 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800575 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800576 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800577
578 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
579 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
580 }
581
582 @Override
583 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
584 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
585 return Collections.emptyList();
586 }
587
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800588 for (int i = 0; i < scoredNetworks.size(); i++) {
589 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
590 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
591 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800592 }
593 }
594
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800595 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800596 }
597 }
598
599 /**
600 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
601 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
602 * If there are no {@link ScanResult}s the returned list will be empty.
603 * <p>
604 * Note: this filter performs some internal caching for consistency and performance. The
605 * current set of ScanResults is determined at construction time and never changed.
606 * Also, the last filtered list is saved so if the same input is provided multiple
607 * times in a row the computation is only done once.
608 */
609 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800610 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
611 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800612
613 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800614 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800615 final int size = scanResults.size();
616 mScanResultKeys = new ArraySet<>(size);
617 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800618 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800619 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
620 if (key != null) {
621 mScanResultKeys.add(key);
622 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800623 }
624 }
625
626 @Override
627 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
628 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
629 return Collections.emptyList();
630 }
631
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800632 List<ScoredNetwork> filteredScores = new ArrayList<>();
633 for (int i = 0; i < scoredNetworks.size(); i++) {
634 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
635 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
636 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800637 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800638 }
639
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800640 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800641 }
642 }
643
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800644 private boolean callerCanRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800645 // REQUEST_NETWORK_SCORES is a signature only permission.
646 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
647 PackageManager.PERMISSION_GRANTED;
648 }
649
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700650 @Override
651 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800652 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800653 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800654 final long token = Binder.clearCallingIdentity();
655 try {
656 clearInternal();
657 return true;
658 } finally {
659 Binder.restoreCallingIdentity(token);
660 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700661 } else {
662 throw new SecurityException(
663 "Caller is neither the active scorer nor the scorer manager.");
664 }
665 }
666
667 @Override
668 public boolean setActiveScorer(String packageName) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800669 // Only the system can set the active scorer
Jeremy Josline9052a32017-02-27 15:47:54 -0800670 if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800671 throw new SecurityException(
672 "Caller is neither the system process nor a score requester.");
673 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800674
675 return mNetworkScorerAppManager.setActiveScorer(packageName);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700676 }
677
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800678 /**
679 * Determine whether the application with the given UID is the enabled scorer.
680 *
681 * @param callingUid the UID to check
682 * @return true if the provided UID is the active scorer, false otherwise.
683 */
684 @Override
685 public boolean isCallerActiveScorer(int callingUid) {
686 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800687 return mServiceConnection != null
688 && mServiceConnection.mAppData.packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800689 }
690 }
691
Jeremy Joslina5172f62017-02-02 14:27:05 -0800692 private boolean isCallerSystemProcess(int callingUid) {
693 return callingUid == Process.SYSTEM_UID;
694 }
695
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800696 /**
697 * Obtain the package name of the current active network scorer.
698 *
699 * @return the full package name of the current active scorer, or null if there is no active
700 * scorer.
701 */
702 @Override
703 public String getActiveScorerPackage() {
704 synchronized (mServiceConnectionLock) {
705 if (mServiceConnection != null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800706 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800707 }
708 }
709 return null;
710 }
711
Jeremy Joslina5172f62017-02-02 14:27:05 -0800712 /**
713 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
714 */
715 @Override
716 public NetworkScorerAppData getActiveScorer() {
717 // Only the system can access this data.
718 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
719 synchronized (mServiceConnectionLock) {
720 if (mServiceConnection != null) {
721 return mServiceConnection.mAppData;
722 }
723 }
724 } else {
725 throw new SecurityException(
726 "Caller is neither the system process nor a score requester.");
727 }
728
729 return null;
730 }
731
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800732 /**
733 * Returns the list of available scorer apps. The list will be empty if there are
734 * no valid scorers.
735 */
736 @Override
737 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800738 // Only the system can access this data.
Jeremy Josline9052a32017-02-27 15:47:54 -0800739 if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800740 throw new SecurityException(
741 "Caller is neither the system process nor a score requester.");
742 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800743
744 return mNetworkScorerAppManager.getAllValidScorers();
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800745 }
746
Jeff Davidson26fd1432014-07-29 09:39:52 -0700747 @Override
748 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800749 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Josline9052a32017-02-27 15:47:54 -0800750 if (!isCallerActiveScorer(getCallingUid()) || !callerCanRequestScores()) {
Jeff Davidson26fd1432014-07-29 09:39:52 -0700751 throw new SecurityException(
752 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700753 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800754
755 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700756 }
757
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700758 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
759 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800760 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800761 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800762 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800763 try {
764 networkScoreCache.clearScores();
765 } catch (RemoteException e) {
766 if (Log.isLoggable(TAG, Log.VERBOSE)) {
767 Log.v(TAG, "Unable to clear scores", e);
768 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700769 }
770 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800771 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700772 }
773
774 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800775 public void registerNetworkScoreCache(int networkType,
776 INetworkScoreCache scoreCache,
777 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800778 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800779 final long token = Binder.clearCallingIdentity();
780 try {
781 synchronized (mScoreCaches) {
782 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
783 if (callbackList == null) {
784 callbackList = new RemoteCallbackList<>();
785 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800786 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800787 if (!callbackList.register(scoreCache, filterType)) {
788 if (callbackList.getRegisteredCallbackCount() == 0) {
789 mScoreCaches.remove(networkType);
790 }
791 if (Log.isLoggable(TAG, Log.VERBOSE)) {
792 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
793 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800794 }
795 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800796 } finally {
797 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800798 }
799 }
800
801 @Override
802 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800803 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800804 final long token = Binder.clearCallingIdentity();
805 try {
806 synchronized (mScoreCaches) {
807 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
808 if (callbackList == null || !callbackList.unregister(scoreCache)) {
809 if (Log.isLoggable(TAG, Log.VERBOSE)) {
810 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
811 + networkType);
812 }
813 } else if (callbackList.getRegisteredCallbackCount() == 0) {
814 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800815 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800816 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800817 } finally {
818 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700819 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700820 }
821
822 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800823 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800824 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800825 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800826 final long token = Binder.clearCallingIdentity();
827 try {
828 final INetworkRecommendationProvider provider = getRecommendationProvider();
829 if (provider != null) {
830 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800831 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
832 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800833 } catch (RemoteException | TimeoutException e) {
834 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800835 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800836 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800837 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800838
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800839 if (DBG) {
840 Log.d(TAG, "Returning the default network recommendation.");
841 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800842
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800843 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800844 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800845 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800846 }
847 return RecommendationResult.createDoNotConnectRecommendation();
848 } finally {
849 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800850 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800851 }
852
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800853 /**
854 * Request a recommendation for the best network to connect to
855 * taking into account the inputs from the {@link RecommendationRequest}.
856 *
857 * @param request a {@link RecommendationRequest} instance containing the details of the request
858 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
859 * is available.
860 * @throws SecurityException if the caller is not the system
861 */
862 @Override
863 public void requestRecommendationAsync(RecommendationRequest request,
864 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800865 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800866
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800867 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
868 final Pair<RecommendationRequest, OneTimeCallback> pair =
869 Pair.create(request, oneTimeCallback);
870 final Message timeoutMsg = mHandler.obtainMessage(
871 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
872 final INetworkRecommendationProvider provider = getRecommendationProvider();
873 final long token = Binder.clearCallingIdentity();
874 try {
875 if (provider != null) {
876 try {
877 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
878 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
879 @Override
880 public void sendResult(Bundle data) throws RemoteException {
881 // Remove the timeout message
882 mHandler.removeMessages(timeoutMsg.what, pair);
883 oneTimeCallback.sendResult(data);
884 }
885 }, 0 /*sequence*/);
886 return;
887 } catch (RemoteException e) {
888 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800889 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800890 // Remove the timeout message
891 mHandler.removeMessages(timeoutMsg.what, pair);
892 // Will fall through and send back the default recommendation.
893 }
894 }
895 } finally {
896 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800897 }
898
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800899 // Else send back the default recommendation.
900 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800901 }
902
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800903 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800904 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800905 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800906 final long token = Binder.clearCallingIdentity();
907 try {
908 final INetworkRecommendationProvider provider = getRecommendationProvider();
909 if (provider != null) {
910 try {
911 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800912 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800913 // prevent repeated requests for the same scores.
914 return true;
915 } catch (RemoteException e) {
916 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800917 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800918 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800919 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800920 return false;
921 } finally {
922 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800923 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800924 }
925
926 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800927 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700928 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800929 final long token = Binder.clearCallingIdentity();
930 try {
931 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
932 if (currentScorer == null) {
933 writer.println("Scoring is disabled.");
934 return;
935 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800936 writer.println("Current scorer: " + currentScorer);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -0800937 writer.println("RecommendationRequestTimeoutMs: " + mRecommendationRequestTimeoutMs);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700938
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800939 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
940 @Override
941 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
942 try {
943 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
944 } catch (IOException | RemoteException e) {
945 writer.println("Failed to dump score cache: " + e);
946 }
947 }
948 }, getScoreCacheLists());
949
950 synchronized (mServiceConnectionLock) {
951 if (mServiceConnection != null) {
952 mServiceConnection.dump(fd, writer, args);
953 } else {
954 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800955 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700956 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800957 writer.flush();
958 } finally {
959 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700960 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700961 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700962
963 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800964 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700965 *
966 * <p>May be used to perform an action on all score caches without potentially strange behavior
967 * if a new scorer is registered during that action's execution.
968 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800969 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700970 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800971 return new ArrayList<>(mScoreCaches.values());
972 }
973 }
974
Jeremy Joslinba242732017-01-24 17:16:42 -0800975 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800976 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
977 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
978 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
979 final int count = callbackList.beginBroadcast();
980 try {
981 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800982 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800983 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800984 }
985 } finally {
986 callbackList.finishBroadcast();
987 }
988 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700989 }
990 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700991
Jeremy Joslin145c3432016-12-09 13:11:51 -0800992 private void throwIfCalledOnMainThread() {
993 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
994 throw new RuntimeException("Cannot invoke on the main thread");
995 }
996 }
997
998 @Nullable
999 private INetworkRecommendationProvider getRecommendationProvider() {
1000 synchronized (mServiceConnectionLock) {
1001 if (mServiceConnection != null) {
1002 return mServiceConnection.getRecommendationProvider();
1003 }
1004 }
1005 return null;
1006 }
1007
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001008 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001009 public void refreshRecommendationRequestTimeoutMs() {
1010 final ContentResolver cr = mContext.getContentResolver();
1011 long timeoutMs = Settings.Global.getLong(cr,
1012 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
1013 if (timeoutMs < 0) {
1014 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
1015 }
1016 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
1017 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -08001018 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001019 }
1020
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001021 private static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001022 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001023 private volatile boolean mBound = false;
1024 private volatile boolean mConnected = false;
1025 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001026
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001027 ScoringServiceConnection(NetworkScorerAppData appData) {
1028 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001029 }
1030
1031 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001032 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -08001033 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001034 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001035 mBound = context.bindServiceAsUser(service, this,
1036 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
1037 UserHandle.SYSTEM);
1038 if (!mBound) {
1039 Log.w(TAG, "Bind call failed for " + service);
1040 } else {
1041 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1042 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001043 }
1044 }
1045
1046 void disconnect(Context context) {
1047 try {
1048 if (mBound) {
1049 mBound = false;
1050 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001051 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001052 }
1053 } catch (RuntimeException e) {
1054 Log.e(TAG, "Unbind failed.", e);
1055 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001056
1057 mRecommendationProvider = null;
1058 }
1059
1060 INetworkRecommendationProvider getRecommendationProvider() {
1061 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001062 }
1063
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001064 String getPackageName() {
1065 return mAppData.getRecommendationServiceComponent().getPackageName();
1066 }
1067
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001068 @Override
1069 public void onServiceConnected(ComponentName name, IBinder service) {
1070 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001071 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001072 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001073 }
1074
1075 @Override
1076 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001077 if (DBG) {
1078 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1079 }
1080 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001081 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001082 }
1083
1084 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001085 writer.println("ScoringServiceConnection: "
1086 + mAppData.getRecommendationServiceComponent()
1087 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001088 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001089 }
1090 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001091
1092 /**
1093 * Executes the async requestRecommendation() call with a timeout.
1094 */
1095 private static final class RequestRecommendationCaller
1096 extends TimedRemoteCaller<RecommendationResult> {
1097 private final IRemoteCallback mCallback;
1098
1099 RequestRecommendationCaller(long callTimeoutMillis) {
1100 super(callTimeoutMillis);
1101 mCallback = new IRemoteCallback.Stub() {
1102 @Override
1103 public void sendResult(Bundle data) throws RemoteException {
1104 final RecommendationResult result =
1105 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1106 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001107 if (VERBOSE) Log.v(TAG, "callback received for sequence " + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001108 onRemoteMethodResult(result, sequence);
1109 }
1110 };
1111 }
1112
1113 /**
1114 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1115 * instance.
1116 *
1117 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1118 * from
1119 * @param request the {@link RecommendationRequest} from the calling client
1120 * @return a {@link RecommendationResult} from the provider
1121 * @throws RemoteException if the call failed
1122 * @throws TimeoutException if the call took longer than the set timeout
1123 */
1124 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1125 RecommendationRequest request) throws RemoteException, TimeoutException {
1126 final int sequence = onBeforeRemoteCall();
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001127 if (VERBOSE) Log.v(TAG, "getRecommendationResult() seq=" + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001128 target.requestRecommendation(request, mCallback, sequence);
1129 return getResultTimed(sequence);
1130 }
1131 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001132
1133 /**
1134 * A wrapper around {@link RemoteCallback} that guarantees
1135 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1136 */
1137 @VisibleForTesting
1138 public static final class OneTimeCallback {
1139 private final RemoteCallback mRemoteCallback;
1140 private final AtomicBoolean mCallbackRun;
1141
1142 public OneTimeCallback(RemoteCallback remoteCallback) {
1143 mRemoteCallback = remoteCallback;
1144 mCallbackRun = new AtomicBoolean(false);
1145 }
1146
1147 public void sendResult(Bundle data) {
1148 if (mCallbackRun.compareAndSet(false, true)) {
1149 mRemoteCallback.sendResult(data);
1150 }
1151 }
1152 }
1153
1154 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1155 OneTimeCallback remoteCallback) {
1156 if (DBG) {
1157 Log.d(TAG, "Returning the default network recommendation.");
1158 }
1159
1160 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001161 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001162 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001163 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001164 } else {
1165 result = RecommendationResult.createDoNotConnectRecommendation();
1166 }
1167
1168 final Bundle data = new Bundle();
1169 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1170 remoteCallback.sendResult(data);
1171 }
1172
1173 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001174 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001175 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001176 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 2;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001177 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001178 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 4;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001179
1180 public ServiceHandler(Looper looper) {
1181 super(looper);
1182 }
1183
1184 @Override
1185 public void handleMessage(Message msg) {
1186 final int what = msg.what;
1187 switch (what) {
1188 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1189 if (DBG) {
1190 Log.d(TAG, "Network recommendation request timed out.");
1191 }
1192 final Pair<RecommendationRequest, OneTimeCallback> pair =
1193 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1194 final RecommendationRequest request = pair.first;
1195 final OneTimeCallback remoteCallback = pair.second;
1196 sendDefaultRecommendationResponse(request, remoteCallback);
1197 break;
1198
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001199 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001200 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001201 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001202 break;
1203
1204 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1205 refreshRecommendationRequestTimeoutMs();
1206 break;
1207
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001208 default:
1209 Log.w(TAG,"Unknown message: " + what);
1210 }
1211 }
1212 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001213}