blob: b33538cbcb074d1656cd4bd3323080d61e400790 [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;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070038import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070039import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080040import android.net.RecommendationRequest;
41import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070042import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080043import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080044import android.net.wifi.ScanResult;
45import android.net.wifi.WifiInfo;
46import android.net.wifi.WifiManager;
47import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080048import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080049import android.os.Build;
Jeremy Joslin145c3432016-12-09 13:11:51 -080050import android.os.Bundle;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080051import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070052import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080053import android.os.IRemoteCallback;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080054import android.os.Looper;
55import android.os.Message;
Jeremy Joslina5172f62017-02-02 14:27:05 -080056import android.os.Process;
Jeremy Joslin998d7ca2016-12-28 15:56:46 -080057import android.os.RemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080058import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070059import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070060import android.os.UserHandle;
Jeremy Joslincb594f32017-01-03 17:31:23 -080061import android.provider.Settings;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080062import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080063import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080064import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070065import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080066import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080067import android.util.TimedRemoteCaller;
68
Jeff Davidson7842f642014-11-23 13:48:12 -080069import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080070import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070071import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070072import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080073
Jeff Davidson6a4b2202014-04-16 17:29:40 -070074import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070075import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070076import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070077import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080078import java.util.Collection;
79import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070080import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070081import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080082import java.util.Set;
Jeremy Joslin145c3432016-12-09 13:11:51 -080083import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080084import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080085import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080086import java.util.function.BiConsumer;
Jeremy Joslinba242732017-01-24 17:16:42 -080087import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080088import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070089
90/**
91 * Backing service for {@link android.net.NetworkScoreManager}.
92 * @hide
93 */
94public class NetworkScoreService extends INetworkScoreService.Stub {
95 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080096 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080097 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070098
Jeff Davidson6a4b2202014-04-16 17:29:40 -070099 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800100 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800101 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -0800102 @GuardedBy("mScoreCaches")
103 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700104 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800105 private final Object mPackageMonitorLock = new Object();
106 private final Object mServiceConnectionLock = new Object();
107 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800108 private final DispatchingContentObserver mContentObserver;
Jeff Davidson7842f642014-11-23 13:48:12 -0800109
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700110 @GuardedBy("mPackageMonitorLock")
111 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800112 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700113 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800114 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800115
Jeremy Joslin967b5812016-06-02 07:58:14 -0700116 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
117 @Override
118 public void onReceive(Context context, Intent intent) {
119 final String action = intent.getAction();
120 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
121 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
122 if (userId == UserHandle.USER_NULL) return;
123
124 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
125 onUserUnlocked(userId);
126 }
127 }
128 };
129
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700130 /**
131 * Clears scores when the active scorer package is no longer valid and
132 * manages the service connection.
133 */
134 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800135 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800136
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800137 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
138 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800139 }
140
141 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700142 public void onPackageAdded(String packageName, int uid) {
143 evaluateBinding(packageName, true /* forceUnbind */);
144 }
145
146 @Override
147 public void onPackageRemoved(String packageName, int uid) {
148 evaluateBinding(packageName, true /* forceUnbind */);
149 }
150
151 @Override
152 public void onPackageModified(String packageName) {
153 evaluateBinding(packageName, false /* forceUnbind */);
154 }
155
156 @Override
157 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
158 if (doit) { // "doit" means the force stop happened instead of just being queried for.
159 for (String packageName : packages) {
160 evaluateBinding(packageName, true /* forceUnbind */);
161 }
162 }
163 return super.onHandleForceStop(intent, packages, uid, doit);
164 }
165
166 @Override
167 public void onPackageUpdateFinished(String packageName, int uid) {
168 evaluateBinding(packageName, true /* forceUnbind */);
169 }
170
171 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800172 if (!mPackagesToWatch.contains(scorerPackageName)) {
173 // Early exit when we don't care about the package that has changed.
174 return;
175 }
176
177 if (DBG) {
178 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
179 + ", forceUnbind=" + forceUnbind);
180 }
181 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.");
186 unbindFromScoringServiceIfNeeded();
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800187 } else if (activeScorer.getRecommendationServicePackageName().equals(scorerPackageName))
188 {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800189 // The active scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700190 if (DBG) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800191 Log.d(TAG, "Possible change to the active scorer: "
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800192 + activeScorer.getRecommendationServicePackageName());
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700193 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800194 if (forceUnbind) {
195 unbindFromScoringServiceIfNeeded();
196 }
197 bindToScoringServiceIfNeeded(activeScorer);
198 } else {
199 // One of the scoring apps on the device has changed and we may no longer be
200 // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
201 // will sort that out to leave us bound to the most recent active scorer.
202 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800203 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
204 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800205 }
206 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800207 }
208 }
209 }
210
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800211 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800212 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800213 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800214 @VisibleForTesting
215 public static class DispatchingContentObserver extends ContentObserver {
216 final private Map<Uri, Integer> mUriEventMap;
217 final private Context mContext;
218 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800219
Jeremy Joslincb594f32017-01-03 17:31:23 -0800220 public DispatchingContentObserver(Context context, Handler handler) {
221 super(handler);
222 mContext = context;
223 mHandler = handler;
224 mUriEventMap = new ArrayMap<>();
225 }
226
227 void observe(Uri uri, int what) {
228 mUriEventMap.put(uri, what);
229 final ContentResolver resolver = mContext.getContentResolver();
230 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800231 }
232
233 @Override
234 public void onChange(boolean selfChange) {
235 onChange(selfChange, null);
236 }
237
238 @Override
239 public void onChange(boolean selfChange, Uri uri) {
240 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800241 final Integer what = mUriEventMap.get(uri);
242 if (what != null) {
243 mHandler.obtainMessage(what).sendToTarget();
244 } else {
245 Log.w(TAG, "No matching event to send for URI = " + uri);
246 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800247 }
248 }
249
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700250 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800251 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800252 }
253
254 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800255 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
256 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700257 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800258 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800259 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700260 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
261 // TODO: Need to update when we support per-user scorers. http://b/23422763
262 mContext.registerReceiverAsUser(
263 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
264 null /* scheduler */);
Jeremy Joslin3452b692017-01-17 15:48:13 -0800265 mReqRecommendationCallerRef = new AtomicReference<>(
266 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800267 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
268 mHandler = new ServiceHandler(looper);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800269 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700270 }
271
272 /** Called when the system is ready to run third-party code but before it actually does so. */
273 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700274 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700275 registerPackageMonitorIfNeeded();
Jeremy Joslincb594f32017-01-03 17:31:23 -0800276 registerRecommendationSettingsObserver();
277 refreshRecommendationRequestTimeoutMs();
Jeff Davidson7842f642014-11-23 13:48:12 -0800278 }
279
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700280 /** Called when the system is ready for us to start third-party code. */
281 void systemRunning() {
282 if (DBG) Log.d(TAG, "systemRunning");
283 bindToScoringServiceIfNeeded();
284 }
285
Jeremy Joslin967b5812016-06-02 07:58:14 -0700286 private void onUserUnlocked(int userId) {
287 registerPackageMonitorIfNeeded();
288 bindToScoringServiceIfNeeded();
289 }
290
Jeremy Joslincb594f32017-01-03 17:31:23 -0800291 private void registerRecommendationSettingsObserver() {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800292 final List<String> providerPackages =
293 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
294 if (!providerPackages.isEmpty()) {
Jeremy Joslincb594f32017-01-03 17:31:23 -0800295 final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
296 mContentObserver.observe(enabledUri,
297 ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800298 }
Jeremy Joslincb594f32017-01-03 17:31:23 -0800299
300 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
301 mContentObserver.observe(timeoutUri,
302 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800303 }
304
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700305 private void registerPackageMonitorIfNeeded() {
306 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800307 final List<String> providerPackages =
308 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700309 synchronized (mPackageMonitorLock) {
310 // Unregister the current monitor if needed.
311 if (mPackageMonitor != null) {
312 if (DBG) {
313 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800314 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800315 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700316 mPackageMonitor.unregister();
317 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800318 }
319
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800320 // Create and register the monitor if there are packages that could be providers.
321 if (!providerPackages.isEmpty()) {
322 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700323 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700324 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
325 false /* externalStorage */);
326 if (DBG) {
327 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800328 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800329 }
330 }
331 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700332 }
333
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700334 private void bindToScoringServiceIfNeeded() {
335 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800336 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700337 bindToScoringServiceIfNeeded(scorerData);
338 }
339
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800340 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
341 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
342 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800343 synchronized (mServiceConnectionLock) {
344 // If we're connected to a different component then drop it.
345 if (mServiceConnection != null
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800346 && !mServiceConnection.mAppData.equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800347 unbindFromScoringServiceIfNeeded();
348 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700349
Jeremy Joslin145c3432016-12-09 13:11:51 -0800350 // If we're not connected at all then create a new connection.
351 if (mServiceConnection == null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800352 mServiceConnection = new ScoringServiceConnection(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800353 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700354
Jeremy Joslin145c3432016-12-09 13:11:51 -0800355 // Make sure the connection is connected (idempotent)
356 mServiceConnection.connect(mContext);
357 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700358 } else { // otherwise make sure it isn't bound.
359 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700360 }
361 }
362
363 private void unbindFromScoringServiceIfNeeded() {
364 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800365 synchronized (mServiceConnectionLock) {
366 if (mServiceConnection != null) {
367 mServiceConnection.disconnect(mContext);
368 }
369 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700370 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800371 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700372 }
373
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700374 @Override
375 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800376 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700377 throw new SecurityException("Caller with UID " + getCallingUid() +
378 " is not the active scorer.");
379 }
380
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800381 final long token = Binder.clearCallingIdentity();
382 try {
383 // Separate networks by type.
384 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
385 for (ScoredNetwork network : networks) {
386 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
387 if (networkList == null) {
388 networkList = new ArrayList<>();
389 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800390 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800391 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800392 }
393
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800394 // Pass the scores of each type down to the appropriate network scorer.
395 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
396 final RemoteCallbackList<INetworkScoreCache> callbackList;
397 final boolean isEmpty;
398 synchronized (mScoreCaches) {
399 callbackList = mScoreCaches.get(entry.getKey());
400 isEmpty = callbackList == null
401 || callbackList.getRegisteredCallbackCount() == 0;
402 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800403
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800404 if (isEmpty) {
405 if (Log.isLoggable(TAG, Log.VERBOSE)) {
406 Log.v(TAG, "No scorer registered for type " + entry.getKey()
407 + ", discarding");
408 }
409 continue;
410 }
411
Jeremy Joslinba242732017-01-24 17:16:42 -0800412 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800413 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800414 entry.getKey());
415 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800416 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700417
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800418 return true;
419 } finally {
420 Binder.restoreCallingIdentity(token);
421 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700422 }
423
Jeremy Joslinba242732017-01-24 17:16:42 -0800424 /**
425 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
426 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
427 * accepted {@link INetworkScoreCache} implementation.
428 */
429 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800430 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800431 implements BiConsumer<INetworkScoreCache, Object> {
432 private final Context mContext;
433 private final List<ScoredNetwork> mScoredNetworkList;
434 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800435 // TODO: 1/23/17 - Consider a Map if we implement more filters.
436 // These are created on-demand to defer the construction cost until
437 // an instance is actually needed.
438 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
439 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800440
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800441 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800442 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800443 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
444 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800445 }
446
447 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800448 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800449 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800450 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
451 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800452 mContext = context;
453 mScoredNetworkList = scoredNetworkList;
454 mNetworkType = networkType;
455 mCurrentNetworkFilter = currentNetworkFilter;
456 mScanResultsFilter = scanResultsFilter;
457 }
458
459 @Override
460 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
461 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
462 if (cookie instanceof Integer) {
463 filterType = (Integer) cookie;
464 }
465
466 try {
467 final List<ScoredNetwork> filteredNetworkList =
468 filterScores(mScoredNetworkList, filterType);
469 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800470 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800471 }
472 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800473 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800474 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
475 }
476 }
477 }
478
479 /**
480 * Applies the appropriate filter and returns the filtered results.
481 */
482 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
483 int filterType) {
484 switch (filterType) {
485 case NetworkScoreManager.CACHE_FILTER_NONE:
486 return scoredNetworkList;
487
488 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
489 if (mCurrentNetworkFilter == null) {
490 mCurrentNetworkFilter =
491 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
492 }
493 return mCurrentNetworkFilter.apply(scoredNetworkList);
494
495 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
496 if (mScanResultsFilter == null) {
497 mScanResultsFilter = new ScanResultsScoreCacheFilter(
498 new ScanResultsSupplier(mContext));
499 }
500 return mScanResultsFilter.apply(scoredNetworkList);
501
502 default:
503 Log.w(TAG, "Unknown filter type: " + filterType);
504 return scoredNetworkList;
505 }
506 }
507 }
508
509 /**
510 * Helper class that improves the testability of the cache filter Functions.
511 */
512 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
513 private final Context mContext;
514
515 WifiInfoSupplier(Context context) {
516 mContext = context;
517 }
518
519 @Override
520 public WifiInfo get() {
521 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
522 if (wifiManager != null) {
523 return wifiManager.getConnectionInfo();
524 }
525 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
526 return null;
527 }
528 }
529
530 /**
531 * Helper class that improves the testability of the cache filter Functions.
532 */
533 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
534 private final Context mContext;
535
536 ScanResultsSupplier(Context context) {
537 mContext = context;
538 }
539
540 @Override
541 public List<ScanResult> get() {
542 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
543 if (wifiScanner != null) {
544 return wifiScanner.getSingleScanResults();
545 }
546 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
547 return Collections.emptyList();
548 }
549 }
550
551 /**
552 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
553 * {@link ScoredNetwork} associated with the current network. If no network is connected the
554 * returned list will be empty.
555 * <p>
556 * Note: this filter performs some internal caching for consistency and performance. The
557 * current network is determined at construction time and never changed. Also, the
558 * last filtered list is saved so if the same input is provided multiple times in a row
559 * the computation is only done once.
560 */
561 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800562 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800563 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800564
565 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
566 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
567 }
568
569 @Override
570 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
571 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
572 return Collections.emptyList();
573 }
574
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800575 for (int i = 0; i < scoredNetworks.size(); i++) {
576 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
577 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
578 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800579 }
580 }
581
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800582 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800583 }
584 }
585
586 /**
587 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
588 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
589 * If there are no {@link ScanResult}s the returned list will be empty.
590 * <p>
591 * Note: this filter performs some internal caching for consistency and performance. The
592 * current set of ScanResults is determined at construction time and never changed.
593 * Also, the last filtered list is saved so if the same input is provided multiple
594 * times in a row the computation is only done once.
595 */
596 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800597 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
598 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800599
600 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800601 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800602 final int size = scanResults.size();
603 mScanResultKeys = new ArraySet<>(size);
604 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800605 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800606 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
607 if (key != null) {
608 mScanResultKeys.add(key);
609 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800610 }
611 }
612
613 @Override
614 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
615 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
616 return Collections.emptyList();
617 }
618
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800619 List<ScoredNetwork> filteredScores = new ArrayList<>();
620 for (int i = 0; i < scoredNetworks.size(); i++) {
621 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
622 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
623 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800624 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800625 }
626
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800627 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800628 }
629 }
630
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800631 private boolean callerCanRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800632 // REQUEST_NETWORK_SCORES is a signature only permission.
633 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
634 PackageManager.PERMISSION_GRANTED;
635 }
636
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700637 @Override
638 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800639 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800640 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800641 final long token = Binder.clearCallingIdentity();
642 try {
643 clearInternal();
644 return true;
645 } finally {
646 Binder.restoreCallingIdentity(token);
647 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700648 } else {
649 throw new SecurityException(
650 "Caller is neither the active scorer nor the scorer manager.");
651 }
652 }
653
654 @Override
655 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800656 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
657 // to directly set the scorer app rather than having to use the consent dialog. The
658 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
659 // do the right thing and not enable this feature without explaining it to the user.
660 // In the future, should this API be opened to 3p apps, we will need to lock this down and
661 // figure out another way to streamline the UX.
662
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800663 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
664
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800665 // Scorers (recommendation providers) are selected and no longer set.
666 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700667 }
668
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800669 /**
670 * Determine whether the application with the given UID is the enabled scorer.
671 *
672 * @param callingUid the UID to check
673 * @return true if the provided UID is the active scorer, false otherwise.
674 */
675 @Override
676 public boolean isCallerActiveScorer(int callingUid) {
677 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800678 return mServiceConnection != null
679 && mServiceConnection.mAppData.packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800680 }
681 }
682
Jeremy Joslina5172f62017-02-02 14:27:05 -0800683 private boolean isCallerSystemProcess(int callingUid) {
684 return callingUid == Process.SYSTEM_UID;
685 }
686
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800687 /**
688 * Obtain the package name of the current active network scorer.
689 *
690 * @return the full package name of the current active scorer, or null if there is no active
691 * scorer.
692 */
693 @Override
694 public String getActiveScorerPackage() {
695 synchronized (mServiceConnectionLock) {
696 if (mServiceConnection != null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800697 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800698 }
699 }
700 return null;
701 }
702
Jeremy Joslina5172f62017-02-02 14:27:05 -0800703
704 /**
705 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
706 */
707 @Override
708 public NetworkScorerAppData getActiveScorer() {
709 // Only the system can access this data.
710 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
711 synchronized (mServiceConnectionLock) {
712 if (mServiceConnection != null) {
713 return mServiceConnection.mAppData;
714 }
715 }
716 } else {
717 throw new SecurityException(
718 "Caller is neither the system process nor a score requester.");
719 }
720
721 return null;
722 }
723
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800724 /**
725 * Returns the list of available scorer apps. The list will be empty if there are
726 * no valid scorers.
727 */
728 @Override
729 public List<NetworkScorerAppData> getAllValidScorers() {
730 return mNetworkScorerAppManager.getAllValidScorers();
731 }
732
Jeff Davidson26fd1432014-07-29 09:39:52 -0700733 @Override
734 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800735 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800736 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800737 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700738 } else {
739 throw new SecurityException(
740 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700741 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700742 }
743
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700744 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
745 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800746 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800747 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800748 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800749 try {
750 networkScoreCache.clearScores();
751 } catch (RemoteException e) {
752 if (Log.isLoggable(TAG, Log.VERBOSE)) {
753 Log.v(TAG, "Unable to clear scores", e);
754 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700755 }
756 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800757 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700758 }
759
760 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800761 public void registerNetworkScoreCache(int networkType,
762 INetworkScoreCache scoreCache,
763 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800764 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800765 final long token = Binder.clearCallingIdentity();
766 try {
767 synchronized (mScoreCaches) {
768 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
769 if (callbackList == null) {
770 callbackList = new RemoteCallbackList<>();
771 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800772 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800773 if (!callbackList.register(scoreCache, filterType)) {
774 if (callbackList.getRegisteredCallbackCount() == 0) {
775 mScoreCaches.remove(networkType);
776 }
777 if (Log.isLoggable(TAG, Log.VERBOSE)) {
778 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
779 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800780 }
781 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800782 } finally {
783 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800784 }
785 }
786
787 @Override
788 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800789 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800790 final long token = Binder.clearCallingIdentity();
791 try {
792 synchronized (mScoreCaches) {
793 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
794 if (callbackList == null || !callbackList.unregister(scoreCache)) {
795 if (Log.isLoggable(TAG, Log.VERBOSE)) {
796 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
797 + networkType);
798 }
799 } else if (callbackList.getRegisteredCallbackCount() == 0) {
800 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800801 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800802 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800803 } finally {
804 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700805 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700806 }
807
808 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800809 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800810 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800811 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800812 final long token = Binder.clearCallingIdentity();
813 try {
814 final INetworkRecommendationProvider provider = getRecommendationProvider();
815 if (provider != null) {
816 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800817 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
818 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800819 } catch (RemoteException | TimeoutException e) {
820 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800821 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800822 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800823 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800824
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800825 if (DBG) {
826 Log.d(TAG, "Returning the default network recommendation.");
827 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800828
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800829 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800830 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800831 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800832 }
833 return RecommendationResult.createDoNotConnectRecommendation();
834 } finally {
835 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800836 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800837 }
838
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800839 /**
840 * Request a recommendation for the best network to connect to
841 * taking into account the inputs from the {@link RecommendationRequest}.
842 *
843 * @param request a {@link RecommendationRequest} instance containing the details of the request
844 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
845 * is available.
846 * @throws SecurityException if the caller is not the system
847 */
848 @Override
849 public void requestRecommendationAsync(RecommendationRequest request,
850 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800851 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800852
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800853 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
854 final Pair<RecommendationRequest, OneTimeCallback> pair =
855 Pair.create(request, oneTimeCallback);
856 final Message timeoutMsg = mHandler.obtainMessage(
857 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
858 final INetworkRecommendationProvider provider = getRecommendationProvider();
859 final long token = Binder.clearCallingIdentity();
860 try {
861 if (provider != null) {
862 try {
863 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
864 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
865 @Override
866 public void sendResult(Bundle data) throws RemoteException {
867 // Remove the timeout message
868 mHandler.removeMessages(timeoutMsg.what, pair);
869 oneTimeCallback.sendResult(data);
870 }
871 }, 0 /*sequence*/);
872 return;
873 } catch (RemoteException e) {
874 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800875 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800876 // Remove the timeout message
877 mHandler.removeMessages(timeoutMsg.what, pair);
878 // Will fall through and send back the default recommendation.
879 }
880 }
881 } finally {
882 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800883 }
884
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800885 // Else send back the default recommendation.
886 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800887 }
888
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800889 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800890 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800891 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800892 final long token = Binder.clearCallingIdentity();
893 try {
894 final INetworkRecommendationProvider provider = getRecommendationProvider();
895 if (provider != null) {
896 try {
897 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800898 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800899 // prevent repeated requests for the same scores.
900 return true;
901 } catch (RemoteException e) {
902 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800903 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800904 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800905 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800906 return false;
907 } finally {
908 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800909 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800910 }
911
912 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800913 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700914 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800915 final long token = Binder.clearCallingIdentity();
916 try {
917 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
918 if (currentScorer == null) {
919 writer.println("Scoring is disabled.");
920 return;
921 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800922 writer.println("Current scorer: " + currentScorer);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -0800923 writer.println("RecommendationRequestTimeoutMs: " + mRecommendationRequestTimeoutMs);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700924
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800925 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
926 @Override
927 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
928 try {
929 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
930 } catch (IOException | RemoteException e) {
931 writer.println("Failed to dump score cache: " + e);
932 }
933 }
934 }, getScoreCacheLists());
935
936 synchronized (mServiceConnectionLock) {
937 if (mServiceConnection != null) {
938 mServiceConnection.dump(fd, writer, args);
939 } else {
940 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800941 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700942 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800943 writer.flush();
944 } finally {
945 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700946 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700947 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700948
949 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800950 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700951 *
952 * <p>May be used to perform an action on all score caches without potentially strange behavior
953 * if a new scorer is registered during that action's execution.
954 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800955 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700956 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800957 return new ArrayList<>(mScoreCaches.values());
958 }
959 }
960
Jeremy Joslinba242732017-01-24 17:16:42 -0800961 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800962 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
963 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
964 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
965 final int count = callbackList.beginBroadcast();
966 try {
967 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800968 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800969 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800970 }
971 } finally {
972 callbackList.finishBroadcast();
973 }
974 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700975 }
976 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700977
Jeremy Joslin145c3432016-12-09 13:11:51 -0800978 private void throwIfCalledOnMainThread() {
979 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
980 throw new RuntimeException("Cannot invoke on the main thread");
981 }
982 }
983
984 @Nullable
985 private INetworkRecommendationProvider getRecommendationProvider() {
986 synchronized (mServiceConnectionLock) {
987 if (mServiceConnection != null) {
988 return mServiceConnection.getRecommendationProvider();
989 }
990 }
991 return null;
992 }
993
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800994 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -0800995 public void refreshRecommendationRequestTimeoutMs() {
996 final ContentResolver cr = mContext.getContentResolver();
997 long timeoutMs = Settings.Global.getLong(cr,
998 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
999 if (timeoutMs < 0) {
1000 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
1001 }
1002 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
1003 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -08001004 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001005 }
1006
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001007 private static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001008 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001009 private volatile boolean mBound = false;
1010 private volatile boolean mConnected = false;
1011 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001012
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001013 ScoringServiceConnection(NetworkScorerAppData appData) {
1014 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001015 }
1016
1017 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001018 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -08001019 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001020 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001021 mBound = context.bindServiceAsUser(service, this,
1022 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
1023 UserHandle.SYSTEM);
1024 if (!mBound) {
1025 Log.w(TAG, "Bind call failed for " + service);
1026 } else {
1027 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1028 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001029 }
1030 }
1031
1032 void disconnect(Context context) {
1033 try {
1034 if (mBound) {
1035 mBound = false;
1036 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001037 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001038 }
1039 } catch (RuntimeException e) {
1040 Log.e(TAG, "Unbind failed.", e);
1041 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001042
1043 mRecommendationProvider = null;
1044 }
1045
1046 INetworkRecommendationProvider getRecommendationProvider() {
1047 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001048 }
1049
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001050 String getPackageName() {
1051 return mAppData.getRecommendationServiceComponent().getPackageName();
1052 }
1053
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001054 @Override
1055 public void onServiceConnected(ComponentName name, IBinder service) {
1056 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001057 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001058 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001059 }
1060
1061 @Override
1062 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001063 if (DBG) {
1064 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1065 }
1066 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001067 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001068 }
1069
1070 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001071 writer.println("ScoringServiceConnection: "
1072 + mAppData.getRecommendationServiceComponent()
1073 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001074 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001075 }
1076 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001077
1078 /**
1079 * Executes the async requestRecommendation() call with a timeout.
1080 */
1081 private static final class RequestRecommendationCaller
1082 extends TimedRemoteCaller<RecommendationResult> {
1083 private final IRemoteCallback mCallback;
1084
1085 RequestRecommendationCaller(long callTimeoutMillis) {
1086 super(callTimeoutMillis);
1087 mCallback = new IRemoteCallback.Stub() {
1088 @Override
1089 public void sendResult(Bundle data) throws RemoteException {
1090 final RecommendationResult result =
1091 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1092 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001093 if (VERBOSE) Log.v(TAG, "callback received for sequence " + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001094 onRemoteMethodResult(result, sequence);
1095 }
1096 };
1097 }
1098
1099 /**
1100 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1101 * instance.
1102 *
1103 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1104 * from
1105 * @param request the {@link RecommendationRequest} from the calling client
1106 * @return a {@link RecommendationResult} from the provider
1107 * @throws RemoteException if the call failed
1108 * @throws TimeoutException if the call took longer than the set timeout
1109 */
1110 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1111 RecommendationRequest request) throws RemoteException, TimeoutException {
1112 final int sequence = onBeforeRemoteCall();
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001113 if (VERBOSE) Log.v(TAG, "getRecommendationResult() seq=" + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001114 target.requestRecommendation(request, mCallback, sequence);
1115 return getResultTimed(sequence);
1116 }
1117 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001118
1119 /**
1120 * A wrapper around {@link RemoteCallback} that guarantees
1121 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1122 */
1123 @VisibleForTesting
1124 public static final class OneTimeCallback {
1125 private final RemoteCallback mRemoteCallback;
1126 private final AtomicBoolean mCallbackRun;
1127
1128 public OneTimeCallback(RemoteCallback remoteCallback) {
1129 mRemoteCallback = remoteCallback;
1130 mCallbackRun = new AtomicBoolean(false);
1131 }
1132
1133 public void sendResult(Bundle data) {
1134 if (mCallbackRun.compareAndSet(false, true)) {
1135 mRemoteCallback.sendResult(data);
1136 }
1137 }
1138 }
1139
1140 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1141 OneTimeCallback remoteCallback) {
1142 if (DBG) {
1143 Log.d(TAG, "Returning the default network recommendation.");
1144 }
1145
1146 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001147 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001148 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001149 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001150 } else {
1151 result = RecommendationResult.createDoNotConnectRecommendation();
1152 }
1153
1154 final Bundle data = new Bundle();
1155 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1156 remoteCallback.sendResult(data);
1157 }
1158
1159 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001160 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001161 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001162 public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2;
1163 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001164
1165 public ServiceHandler(Looper looper) {
1166 super(looper);
1167 }
1168
1169 @Override
1170 public void handleMessage(Message msg) {
1171 final int what = msg.what;
1172 switch (what) {
1173 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1174 if (DBG) {
1175 Log.d(TAG, "Network recommendation request timed out.");
1176 }
1177 final Pair<RecommendationRequest, OneTimeCallback> pair =
1178 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1179 final RecommendationRequest request = pair.first;
1180 final OneTimeCallback remoteCallback = pair.second;
1181 sendDefaultRecommendationResponse(request, remoteCallback);
1182 break;
1183
Jeremy Joslincb594f32017-01-03 17:31:23 -08001184 case MSG_RECOMMENDATIONS_ENABLED_CHANGED:
1185 bindToScoringServiceIfNeeded();
1186 break;
1187
1188 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1189 refreshRecommendationRequestTimeoutMs();
1190 break;
1191
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001192 default:
1193 Log.w(TAG,"Unknown message: " + what);
1194 }
1195 }
1196 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001197}