blob: dab4dfb41dec8dfc893ecb6724d116b6ac7c0bc4 [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 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;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070063import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080064import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080065import android.util.TimedRemoteCaller;
66
Jeff Davidson7842f642014-11-23 13:48:12 -080067import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080068import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070069import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070070import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080071
Jeff Davidson6a4b2202014-04-16 17:29:40 -070072import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070073import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070074import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070075import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080076import java.util.Collection;
77import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070078import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070079import java.util.Map;
Jeremy Joslin145c3432016-12-09 13:11:51 -080080import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080081import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080082import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080083import java.util.function.BiConsumer;
84import java.util.function.Function;
85import java.util.function.Supplier;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070086
87/**
88 * Backing service for {@link android.net.NetworkScoreManager}.
89 * @hide
90 */
91public class NetworkScoreService extends INetworkScoreService.Stub {
92 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080093 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070094
Jeff Davidson6a4b2202014-04-16 17:29:40 -070095 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080096 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -080097 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -080098 @GuardedBy("mScoreCaches")
99 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700100 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800101 private final Object mPackageMonitorLock = new Object();
102 private final Object mServiceConnectionLock = new Object();
103 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800104 private final DispatchingContentObserver mContentObserver;
Jeff Davidson7842f642014-11-23 13:48:12 -0800105
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700106 @GuardedBy("mPackageMonitorLock")
107 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800108 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700109 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800110 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800111
Jeremy Joslin967b5812016-06-02 07:58:14 -0700112 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
113 @Override
114 public void onReceive(Context context, Intent intent) {
115 final String action = intent.getAction();
116 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
117 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
118 if (userId == UserHandle.USER_NULL) return;
119
120 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
121 onUserUnlocked(userId);
122 }
123 }
124 };
125
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700126 /**
127 * Clears scores when the active scorer package is no longer valid and
128 * manages the service connection.
129 */
130 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800131 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800132
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800133 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
134 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800135 }
136
137 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700138 public void onPackageAdded(String packageName, int uid) {
139 evaluateBinding(packageName, true /* forceUnbind */);
140 }
141
142 @Override
143 public void onPackageRemoved(String packageName, int uid) {
144 evaluateBinding(packageName, true /* forceUnbind */);
145 }
146
147 @Override
148 public void onPackageModified(String packageName) {
149 evaluateBinding(packageName, false /* forceUnbind */);
150 }
151
152 @Override
153 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
154 if (doit) { // "doit" means the force stop happened instead of just being queried for.
155 for (String packageName : packages) {
156 evaluateBinding(packageName, true /* forceUnbind */);
157 }
158 }
159 return super.onHandleForceStop(intent, packages, uid, doit);
160 }
161
162 @Override
163 public void onPackageUpdateFinished(String packageName, int uid) {
164 evaluateBinding(packageName, true /* forceUnbind */);
165 }
166
167 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800168 if (!mPackagesToWatch.contains(scorerPackageName)) {
169 // Early exit when we don't care about the package that has changed.
170 return;
171 }
172
173 if (DBG) {
174 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
175 + ", forceUnbind=" + forceUnbind);
176 }
177 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
178 if (activeScorer == null) {
179 // Package change has invalidated a scorer, this will also unbind any service
180 // connection.
181 if (DBG) Log.d(TAG, "No active scorers available.");
182 unbindFromScoringServiceIfNeeded();
183 } else if (activeScorer.packageName.equals(scorerPackageName)) {
184 // The active scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700185 if (DBG) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800186 Log.d(TAG, "Possible change to the active scorer: "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800187 + activeScorer.packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700188 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800189 if (forceUnbind) {
190 unbindFromScoringServiceIfNeeded();
191 }
192 bindToScoringServiceIfNeeded(activeScorer);
193 } else {
194 // One of the scoring apps on the device has changed and we may no longer be
195 // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
196 // will sort that out to leave us bound to the most recent active scorer.
197 if (DBG) {
198 Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
199 }
200 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800201 }
202 }
203 }
204
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800205 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800206 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800207 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800208 @VisibleForTesting
209 public static class DispatchingContentObserver extends ContentObserver {
210 final private Map<Uri, Integer> mUriEventMap;
211 final private Context mContext;
212 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800213
Jeremy Joslincb594f32017-01-03 17:31:23 -0800214 public DispatchingContentObserver(Context context, Handler handler) {
215 super(handler);
216 mContext = context;
217 mHandler = handler;
218 mUriEventMap = new ArrayMap<>();
219 }
220
221 void observe(Uri uri, int what) {
222 mUriEventMap.put(uri, what);
223 final ContentResolver resolver = mContext.getContentResolver();
224 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800225 }
226
227 @Override
228 public void onChange(boolean selfChange) {
229 onChange(selfChange, null);
230 }
231
232 @Override
233 public void onChange(boolean selfChange, Uri uri) {
234 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800235 final Integer what = mUriEventMap.get(uri);
236 if (what != null) {
237 mHandler.obtainMessage(what).sendToTarget();
238 } else {
239 Log.w(TAG, "No matching event to send for URI = " + uri);
240 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800241 }
242 }
243
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700244 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800245 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800246 }
247
248 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800249 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
250 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700251 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800252 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800253 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700254 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
255 // TODO: Need to update when we support per-user scorers. http://b/23422763
256 mContext.registerReceiverAsUser(
257 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
258 null /* scheduler */);
Jeremy Joslin3452b692017-01-17 15:48:13 -0800259 mReqRecommendationCallerRef = new AtomicReference<>(
260 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800261 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
262 mHandler = new ServiceHandler(looper);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800263 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700264 }
265
266 /** Called when the system is ready to run third-party code but before it actually does so. */
267 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700268 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700269 registerPackageMonitorIfNeeded();
Jeremy Joslincb594f32017-01-03 17:31:23 -0800270 registerRecommendationSettingsObserver();
271 refreshRecommendationRequestTimeoutMs();
Jeff Davidson7842f642014-11-23 13:48:12 -0800272 }
273
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700274 /** Called when the system is ready for us to start third-party code. */
275 void systemRunning() {
276 if (DBG) Log.d(TAG, "systemRunning");
277 bindToScoringServiceIfNeeded();
278 }
279
Jeremy Joslin967b5812016-06-02 07:58:14 -0700280 private void onUserUnlocked(int userId) {
281 registerPackageMonitorIfNeeded();
282 bindToScoringServiceIfNeeded();
283 }
284
Jeremy Joslincb594f32017-01-03 17:31:23 -0800285 private void registerRecommendationSettingsObserver() {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800286 final List<String> providerPackages =
287 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
288 if (!providerPackages.isEmpty()) {
Jeremy Joslincb594f32017-01-03 17:31:23 -0800289 final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
290 mContentObserver.observe(enabledUri,
291 ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800292 }
Jeremy Joslincb594f32017-01-03 17:31:23 -0800293
294 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
295 mContentObserver.observe(timeoutUri,
296 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800297 }
298
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700299 private void registerPackageMonitorIfNeeded() {
300 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800301 final List<String> providerPackages =
302 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700303 synchronized (mPackageMonitorLock) {
304 // Unregister the current monitor if needed.
305 if (mPackageMonitor != null) {
306 if (DBG) {
307 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800308 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800309 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700310 mPackageMonitor.unregister();
311 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800312 }
313
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800314 // Create and register the monitor if there are packages that could be providers.
315 if (!providerPackages.isEmpty()) {
316 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700317 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700318 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
319 false /* externalStorage */);
320 if (DBG) {
321 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800322 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800323 }
324 }
325 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700326 }
327
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700328 private void bindToScoringServiceIfNeeded() {
329 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800330 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700331 bindToScoringServiceIfNeeded(scorerData);
332 }
333
334 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
335 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800336 if (scorerData != null && scorerData.recommendationServiceClassName != null) {
337 ComponentName componentName = new ComponentName(scorerData.packageName,
338 scorerData.recommendationServiceClassName);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800339 synchronized (mServiceConnectionLock) {
340 // If we're connected to a different component then drop it.
341 if (mServiceConnection != null
342 && !mServiceConnection.mComponentName.equals(componentName)) {
343 unbindFromScoringServiceIfNeeded();
344 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700345
Jeremy Joslin145c3432016-12-09 13:11:51 -0800346 // If we're not connected at all then create a new connection.
347 if (mServiceConnection == null) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800348 mServiceConnection = new ScoringServiceConnection(componentName,
349 scorerData.packageUid);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800350 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700351
Jeremy Joslin145c3432016-12-09 13:11:51 -0800352 // Make sure the connection is connected (idempotent)
353 mServiceConnection.connect(mContext);
354 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700355 } else { // otherwise make sure it isn't bound.
356 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700357 }
358 }
359
360 private void unbindFromScoringServiceIfNeeded() {
361 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800362 synchronized (mServiceConnectionLock) {
363 if (mServiceConnection != null) {
364 mServiceConnection.disconnect(mContext);
365 }
366 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700367 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800368 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700369 }
370
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700371 @Override
372 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800373 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700374 throw new SecurityException("Caller with UID " + getCallingUid() +
375 " is not the active scorer.");
376 }
377
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800378 final long token = Binder.clearCallingIdentity();
379 try {
380 // Separate networks by type.
381 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
382 for (ScoredNetwork network : networks) {
383 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
384 if (networkList == null) {
385 networkList = new ArrayList<>();
386 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800387 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800388 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800389 }
390
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800391 // Pass the scores of each type down to the appropriate network scorer.
392 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
393 final RemoteCallbackList<INetworkScoreCache> callbackList;
394 final boolean isEmpty;
395 synchronized (mScoreCaches) {
396 callbackList = mScoreCaches.get(entry.getKey());
397 isEmpty = callbackList == null
398 || callbackList.getRegisteredCallbackCount() == 0;
399 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800400
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800401 if (isEmpty) {
402 if (Log.isLoggable(TAG, Log.VERBOSE)) {
403 Log.v(TAG, "No scorer registered for type " + entry.getKey()
404 + ", discarding");
405 }
406 continue;
407 }
408
Jeremy Joslinba242732017-01-24 17:16:42 -0800409 final BiConsumer<INetworkScoreCache, Object> consumer =
410 new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
411 entry.getKey());
412 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800413 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700414
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800415 return true;
416 } finally {
417 Binder.restoreCallingIdentity(token);
418 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700419 }
420
Jeremy Joslinba242732017-01-24 17:16:42 -0800421 /**
422 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
423 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
424 * accepted {@link INetworkScoreCache} implementation.
425 */
426 @VisibleForTesting
427 public static class FilteringCacheUpdatingConsumer
428 implements BiConsumer<INetworkScoreCache, Object> {
429 private final Context mContext;
430 private final List<ScoredNetwork> mScoredNetworkList;
431 private final int mNetworkType;
432 // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
433 private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
434 private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
435
436 public FilteringCacheUpdatingConsumer(Context context,
437 List<ScoredNetwork> scoredNetworkList, int networkType) {
438 this(context, scoredNetworkList, networkType, null, null);
439 }
440
441 @VisibleForTesting
442 public FilteringCacheUpdatingConsumer(Context context,
443 List<ScoredNetwork> scoredNetworkList, int networkType,
444 Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
445 Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
446 mContext = context;
447 mScoredNetworkList = scoredNetworkList;
448 mNetworkType = networkType;
449 mCurrentNetworkFilter = currentNetworkFilter;
450 mScanResultsFilter = scanResultsFilter;
451 }
452
453 @Override
454 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
455 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
456 if (cookie instanceof Integer) {
457 filterType = (Integer) cookie;
458 }
459
460 try {
461 final List<ScoredNetwork> filteredNetworkList =
462 filterScores(mScoredNetworkList, filterType);
463 if (!filteredNetworkList.isEmpty()) {
464 networkScoreCache.updateScores(
465 Collections.unmodifiableList(filteredNetworkList));
466 }
467 } catch (RemoteException e) {
468 if (Log.isLoggable(TAG, Log.VERBOSE)) {
469 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
470 }
471 }
472 }
473
474 /**
475 * Applies the appropriate filter and returns the filtered results.
476 */
477 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
478 int filterType) {
479 switch (filterType) {
480 case NetworkScoreManager.CACHE_FILTER_NONE:
481 return scoredNetworkList;
482
483 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
484 if (mCurrentNetworkFilter == null) {
485 mCurrentNetworkFilter =
486 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
487 }
488 return mCurrentNetworkFilter.apply(scoredNetworkList);
489
490 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
491 if (mScanResultsFilter == null) {
492 mScanResultsFilter = new ScanResultsScoreCacheFilter(
493 new ScanResultsSupplier(mContext));
494 }
495 return mScanResultsFilter.apply(scoredNetworkList);
496
497 default:
498 Log.w(TAG, "Unknown filter type: " + filterType);
499 return scoredNetworkList;
500 }
501 }
502 }
503
504 /**
505 * Helper class that improves the testability of the cache filter Functions.
506 */
507 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
508 private final Context mContext;
509
510 WifiInfoSupplier(Context context) {
511 mContext = context;
512 }
513
514 @Override
515 public WifiInfo get() {
516 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
517 if (wifiManager != null) {
518 return wifiManager.getConnectionInfo();
519 }
520 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
521 return null;
522 }
523 }
524
525 /**
526 * Helper class that improves the testability of the cache filter Functions.
527 */
528 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
529 private final Context mContext;
530
531 ScanResultsSupplier(Context context) {
532 mContext = context;
533 }
534
535 @Override
536 public List<ScanResult> get() {
537 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
538 if (wifiScanner != null) {
539 return wifiScanner.getSingleScanResults();
540 }
541 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
542 return Collections.emptyList();
543 }
544 }
545
546 /**
547 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
548 * {@link ScoredNetwork} associated with the current network. If no network is connected the
549 * returned list will be empty.
550 * <p>
551 * Note: this filter performs some internal caching for consistency and performance. The
552 * current network is determined at construction time and never changed. Also, the
553 * last filtered list is saved so if the same input is provided multiple times in a row
554 * the computation is only done once.
555 */
556 @VisibleForTesting
557 public static class CurrentNetworkScoreCacheFilter
558 implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
559 private final NetworkKey mCurrentNetwork;
560 private Pair<List<ScoredNetwork>, Integer> mCache;
561
562 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
563 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
564 }
565
566 @Override
567 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
568 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
569 return Collections.emptyList();
570 }
571
572 final int inputListHash = scoredNetworks.hashCode();
573 if (mCache == null || mCache.second != inputListHash) {
574 ScoredNetwork currentScore = null;
575 for (int i = 0; i < scoredNetworks.size(); i++) {
576 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
577 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
578 currentScore = scoredNetwork;
579 break;
580 }
581 }
582
583 if (currentScore == null) {
584 mCache = Pair.create(Collections.emptyList(), inputListHash);
585 } else {
586 mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
587 }
588 }
589
590 return mCache.first;
591 }
592 }
593
594 /**
595 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
596 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
597 * If there are no {@link ScanResult}s the returned list will be empty.
598 * <p>
599 * Note: this filter performs some internal caching for consistency and performance. The
600 * current set of ScanResults is determined at construction time and never changed.
601 * Also, the last filtered list is saved so if the same input is provided multiple
602 * times in a row the computation is only done once.
603 */
604 @VisibleForTesting
605 public static class ScanResultsScoreCacheFilter
606 implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
607 private final List<NetworkKey> mScanResultKeys;
608 private Pair<List<ScoredNetwork>, Integer> mCache;
609
610 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
611 mScanResultKeys = new ArrayList<>();
612 List<ScanResult> scanResults = resultsSupplier.get();
613 for (int i = 0; i < scanResults.size(); i++) {
614 ScanResult scanResult = scanResults.get(i);
615 mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
616 }
617 }
618
619 @Override
620 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
621 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
622 return Collections.emptyList();
623 }
624
625 final int inputListHash = scoredNetworks.hashCode();
626 if (mCache == null || mCache.second != inputListHash) {
627 List<ScoredNetwork> filteredScores = new ArrayList<>();
628 for (int i = 0; i < scoredNetworks.size(); i++) {
629 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
630 for (int j = 0; j < mScanResultKeys.size(); j++) {
631 final NetworkKey scanResultKey = mScanResultKeys.get(j);
632 if (scanResultKey.equals(scoredNetwork.networkKey)) {
633 filteredScores.add(scoredNetwork);
634 }
635 }
636 }
637 mCache = Pair.create(filteredScores, inputListHash);
638 }
639
640 return mCache.first;
641 }
642 }
643
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800644 private boolean isCallerSystemUid() {
645 // 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 Joslin134c9d32017-01-09 16:22:20 -0800653 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
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) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800669 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
670 // to directly set the scorer app rather than having to use the consent dialog. The
671 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
672 // do the right thing and not enable this feature without explaining it to the user.
673 // In the future, should this API be opened to 3p apps, we will need to lock this down and
674 // figure out another way to streamline the UX.
675
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800676 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
677
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800678 // Scorers (recommendation providers) are selected and no longer set.
679 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700680 }
681
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800682 /**
683 * Determine whether the application with the given UID is the enabled scorer.
684 *
685 * @param callingUid the UID to check
686 * @return true if the provided UID is the active scorer, false otherwise.
687 */
688 @Override
689 public boolean isCallerActiveScorer(int callingUid) {
690 synchronized (mServiceConnectionLock) {
691 return mServiceConnection != null && mServiceConnection.mScoringAppUid == callingUid;
692 }
693 }
694
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800695 /**
696 * Obtain the package name of the current active network scorer.
697 *
698 * @return the full package name of the current active scorer, or null if there is no active
699 * scorer.
700 */
701 @Override
702 public String getActiveScorerPackage() {
703 synchronized (mServiceConnectionLock) {
704 if (mServiceConnection != null) {
705 return mServiceConnection.mComponentName.getPackageName();
706 }
707 }
708 return null;
709 }
710
Jeff Davidson26fd1432014-07-29 09:39:52 -0700711 @Override
712 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800713 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800714 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800715 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700716 } else {
717 throw new SecurityException(
718 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700719 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700720 }
721
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700722 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
723 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800724 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800725 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800726 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800727 try {
728 networkScoreCache.clearScores();
729 } catch (RemoteException e) {
730 if (Log.isLoggable(TAG, Log.VERBOSE)) {
731 Log.v(TAG, "Unable to clear scores", e);
732 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700733 }
734 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800735 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700736 }
737
738 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800739 public void registerNetworkScoreCache(int networkType,
740 INetworkScoreCache scoreCache,
741 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800742 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800743 final long token = Binder.clearCallingIdentity();
744 try {
745 synchronized (mScoreCaches) {
746 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
747 if (callbackList == null) {
748 callbackList = new RemoteCallbackList<>();
749 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800750 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800751 if (!callbackList.register(scoreCache, filterType)) {
752 if (callbackList.getRegisteredCallbackCount() == 0) {
753 mScoreCaches.remove(networkType);
754 }
755 if (Log.isLoggable(TAG, Log.VERBOSE)) {
756 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
757 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800758 }
759 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800760 } finally {
761 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800762 }
763 }
764
765 @Override
766 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800767 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800768 final long token = Binder.clearCallingIdentity();
769 try {
770 synchronized (mScoreCaches) {
771 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
772 if (callbackList == null || !callbackList.unregister(scoreCache)) {
773 if (Log.isLoggable(TAG, Log.VERBOSE)) {
774 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
775 + networkType);
776 }
777 } else if (callbackList.getRegisteredCallbackCount() == 0) {
778 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800779 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800780 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800781 } finally {
782 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700783 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700784 }
785
786 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800787 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800788 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800789 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800790 final long token = Binder.clearCallingIdentity();
791 try {
792 final INetworkRecommendationProvider provider = getRecommendationProvider();
793 if (provider != null) {
794 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800795 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
796 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800797 } catch (RemoteException | TimeoutException e) {
798 Log.w(TAG, "Failed to request a recommendation.", e);
799 // TODO(jjoslin): 12/15/16 - Keep track of failures.
800 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800801 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800802
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800803 if (DBG) {
804 Log.d(TAG, "Returning the default network recommendation.");
805 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800806
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800807 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800808 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800809 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800810 }
811 return RecommendationResult.createDoNotConnectRecommendation();
812 } finally {
813 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800814 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800815 }
816
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800817 /**
818 * Request a recommendation for the best network to connect to
819 * taking into account the inputs from the {@link RecommendationRequest}.
820 *
821 * @param request a {@link RecommendationRequest} instance containing the details of the request
822 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
823 * is available.
824 * @throws SecurityException if the caller is not the system
825 */
826 @Override
827 public void requestRecommendationAsync(RecommendationRequest request,
828 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800829 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800830
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800831 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
832 final Pair<RecommendationRequest, OneTimeCallback> pair =
833 Pair.create(request, oneTimeCallback);
834 final Message timeoutMsg = mHandler.obtainMessage(
835 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
836 final INetworkRecommendationProvider provider = getRecommendationProvider();
837 final long token = Binder.clearCallingIdentity();
838 try {
839 if (provider != null) {
840 try {
841 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
842 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
843 @Override
844 public void sendResult(Bundle data) throws RemoteException {
845 // Remove the timeout message
846 mHandler.removeMessages(timeoutMsg.what, pair);
847 oneTimeCallback.sendResult(data);
848 }
849 }, 0 /*sequence*/);
850 return;
851 } catch (RemoteException e) {
852 Log.w(TAG, "Failed to request a recommendation.", e);
853 // TODO(jjoslin): 12/15/16 - Keep track of failures.
854 // Remove the timeout message
855 mHandler.removeMessages(timeoutMsg.what, pair);
856 // Will fall through and send back the default recommendation.
857 }
858 }
859 } finally {
860 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800861 }
862
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800863 // Else send back the default recommendation.
864 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800865 }
866
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800867 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800868 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800869 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800870 final long token = Binder.clearCallingIdentity();
871 try {
872 final INetworkRecommendationProvider provider = getRecommendationProvider();
873 if (provider != null) {
874 try {
875 provider.requestScores(networks);
876 // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
877 // prevent repeated requests for the same scores.
878 return true;
879 } catch (RemoteException e) {
880 Log.w(TAG, "Failed to request scores.", e);
881 // TODO(jjoslin): 12/15/16 - Keep track of failures.
882 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800883 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800884 return false;
885 } finally {
886 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800887 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800888 }
889
890 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800891 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700892 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800893 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700894 if (currentScorer == null) {
895 writer.println("Scoring is disabled.");
896 return;
897 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800898 writer.println("Current scorer: " + currentScorer.packageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700899
Jeremy Joslinba242732017-01-24 17:16:42 -0800900 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800901 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800902 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800903 try {
904 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
905 } catch (IOException | RemoteException e) {
906 writer.println("Failed to dump score cache: " + e);
907 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700908 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800909 }, getScoreCacheLists());
910
Jeremy Joslin145c3432016-12-09 13:11:51 -0800911 synchronized (mServiceConnectionLock) {
912 if (mServiceConnection != null) {
913 mServiceConnection.dump(fd, writer, args);
914 } else {
915 writer.println("ScoringServiceConnection: null");
916 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700917 }
918 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700919 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700920
921 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800922 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700923 *
924 * <p>May be used to perform an action on all score caches without potentially strange behavior
925 * if a new scorer is registered during that action's execution.
926 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800927 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700928 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800929 return new ArrayList<>(mScoreCaches.values());
930 }
931 }
932
Jeremy Joslinba242732017-01-24 17:16:42 -0800933 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800934 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
935 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
936 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
937 final int count = callbackList.beginBroadcast();
938 try {
939 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800940 consumer.accept(callbackList.getBroadcastItem(i),
941 callbackList.getRegisteredCallbackCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800942 }
943 } finally {
944 callbackList.finishBroadcast();
945 }
946 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700947 }
948 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700949
Jeremy Joslin145c3432016-12-09 13:11:51 -0800950 private void throwIfCalledOnMainThread() {
951 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
952 throw new RuntimeException("Cannot invoke on the main thread");
953 }
954 }
955
956 @Nullable
957 private INetworkRecommendationProvider getRecommendationProvider() {
958 synchronized (mServiceConnectionLock) {
959 if (mServiceConnection != null) {
960 return mServiceConnection.getRecommendationProvider();
961 }
962 }
963 return null;
964 }
965
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800966 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -0800967 public void refreshRecommendationRequestTimeoutMs() {
968 final ContentResolver cr = mContext.getContentResolver();
969 long timeoutMs = Settings.Global.getLong(cr,
970 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
971 if (timeoutMs < 0) {
972 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
973 }
974 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
975 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800976 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800977 }
978
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700979 private static class ScoringServiceConnection implements ServiceConnection {
980 private final ComponentName mComponentName;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800981 private final int mScoringAppUid;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800982 private volatile boolean mBound = false;
983 private volatile boolean mConnected = false;
984 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700985
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800986 ScoringServiceConnection(ComponentName componentName, int scoringAppUid) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700987 mComponentName = componentName;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800988 mScoringAppUid = scoringAppUid;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700989 }
990
991 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700992 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800993 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700994 service.setComponent(mComponentName);
995 mBound = context.bindServiceAsUser(service, this,
996 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
997 UserHandle.SYSTEM);
998 if (!mBound) {
999 Log.w(TAG, "Bind call failed for " + service);
1000 } else {
1001 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1002 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001003 }
1004 }
1005
1006 void disconnect(Context context) {
1007 try {
1008 if (mBound) {
1009 mBound = false;
1010 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001011 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001012 }
1013 } catch (RuntimeException e) {
1014 Log.e(TAG, "Unbind failed.", e);
1015 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001016
1017 mRecommendationProvider = null;
1018 }
1019
1020 INetworkRecommendationProvider getRecommendationProvider() {
1021 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001022 }
1023
1024 @Override
1025 public void onServiceConnected(ComponentName name, IBinder service) {
1026 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001027 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001028 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001029 }
1030
1031 @Override
1032 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001033 if (DBG) {
1034 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1035 }
1036 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001037 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001038 }
1039
1040 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001041 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
1042 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001043 }
1044 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001045
1046 /**
1047 * Executes the async requestRecommendation() call with a timeout.
1048 */
1049 private static final class RequestRecommendationCaller
1050 extends TimedRemoteCaller<RecommendationResult> {
1051 private final IRemoteCallback mCallback;
1052
1053 RequestRecommendationCaller(long callTimeoutMillis) {
1054 super(callTimeoutMillis);
1055 mCallback = new IRemoteCallback.Stub() {
1056 @Override
1057 public void sendResult(Bundle data) throws RemoteException {
1058 final RecommendationResult result =
1059 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1060 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
1061 onRemoteMethodResult(result, sequence);
1062 }
1063 };
1064 }
1065
1066 /**
1067 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1068 * instance.
1069 *
1070 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1071 * from
1072 * @param request the {@link RecommendationRequest} from the calling client
1073 * @return a {@link RecommendationResult} from the provider
1074 * @throws RemoteException if the call failed
1075 * @throws TimeoutException if the call took longer than the set timeout
1076 */
1077 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1078 RecommendationRequest request) throws RemoteException, TimeoutException {
1079 final int sequence = onBeforeRemoteCall();
1080 target.requestRecommendation(request, mCallback, sequence);
1081 return getResultTimed(sequence);
1082 }
1083 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001084
1085 /**
1086 * A wrapper around {@link RemoteCallback} that guarantees
1087 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1088 */
1089 @VisibleForTesting
1090 public static final class OneTimeCallback {
1091 private final RemoteCallback mRemoteCallback;
1092 private final AtomicBoolean mCallbackRun;
1093
1094 public OneTimeCallback(RemoteCallback remoteCallback) {
1095 mRemoteCallback = remoteCallback;
1096 mCallbackRun = new AtomicBoolean(false);
1097 }
1098
1099 public void sendResult(Bundle data) {
1100 if (mCallbackRun.compareAndSet(false, true)) {
1101 mRemoteCallback.sendResult(data);
1102 }
1103 }
1104 }
1105
1106 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1107 OneTimeCallback remoteCallback) {
1108 if (DBG) {
1109 Log.d(TAG, "Returning the default network recommendation.");
1110 }
1111
1112 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001113 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001114 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001115 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001116 } else {
1117 result = RecommendationResult.createDoNotConnectRecommendation();
1118 }
1119
1120 final Bundle data = new Bundle();
1121 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1122 remoteCallback.sendResult(data);
1123 }
1124
1125 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001126 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001127 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001128 public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2;
1129 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001130
1131 public ServiceHandler(Looper looper) {
1132 super(looper);
1133 }
1134
1135 @Override
1136 public void handleMessage(Message msg) {
1137 final int what = msg.what;
1138 switch (what) {
1139 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1140 if (DBG) {
1141 Log.d(TAG, "Network recommendation request timed out.");
1142 }
1143 final Pair<RecommendationRequest, OneTimeCallback> pair =
1144 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1145 final RecommendationRequest request = pair.first;
1146 final OneTimeCallback remoteCallback = pair.second;
1147 sendDefaultRecommendationResponse(request, remoteCallback);
1148 break;
1149
Jeremy Joslincb594f32017-01-03 17:31:23 -08001150 case MSG_RECOMMENDATIONS_ENABLED_CHANGED:
1151 bindToScoringServiceIfNeeded();
1152 break;
1153
1154 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1155 refreshRecommendationRequestTimeoutMs();
1156 break;
1157
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001158 default:
1159 Log.w(TAG,"Unknown message: " + what);
1160 }
1161 }
1162 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001163}