blob: 5fe8b1a8ae9dbce2bee969e2c34cf46d1eb8a2cb [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;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080063import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080065import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066import android.util.TimedRemoteCaller;
67
Jeff Davidson7842f642014-11-23 13:48:12 -080068import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080069import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070070import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070071import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080072
Jeff Davidson6a4b2202014-04-16 17:29:40 -070073import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070074import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070075import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070076import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080077import java.util.Collection;
78import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070079import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070080import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080081import java.util.Set;
Jeremy Joslin145c3432016-12-09 13:11:51 -080082import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080083import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080084import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080085import java.util.function.BiConsumer;
Jeremy Joslinba242732017-01-24 17:16:42 -080086import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080087import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070088
89/**
90 * Backing service for {@link android.net.NetworkScoreManager}.
91 * @hide
92 */
93public class NetworkScoreService extends INetworkScoreService.Stub {
94 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080095 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080096 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070097
Jeff Davidson6a4b2202014-04-16 17:29:40 -070098 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080099 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800100 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -0800101 @GuardedBy("mScoreCaches")
102 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700103 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800104 private final Object mPackageMonitorLock = new Object();
105 private final Object mServiceConnectionLock = new Object();
106 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800107 private final DispatchingContentObserver mContentObserver;
Jeff Davidson7842f642014-11-23 13:48:12 -0800108
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700109 @GuardedBy("mPackageMonitorLock")
110 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800111 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700112 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800113 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800114
Jeremy Joslin967b5812016-06-02 07:58:14 -0700115 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
116 @Override
117 public void onReceive(Context context, Intent intent) {
118 final String action = intent.getAction();
119 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
120 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
121 if (userId == UserHandle.USER_NULL) return;
122
123 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
124 onUserUnlocked(userId);
125 }
126 }
127 };
128
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700129 /**
130 * Clears scores when the active scorer package is no longer valid and
131 * manages the service connection.
132 */
133 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800134 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800135
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800136 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
137 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800138 }
139
140 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700141 public void onPackageAdded(String packageName, int uid) {
142 evaluateBinding(packageName, true /* forceUnbind */);
143 }
144
145 @Override
146 public void onPackageRemoved(String packageName, int uid) {
147 evaluateBinding(packageName, true /* forceUnbind */);
148 }
149
150 @Override
151 public void onPackageModified(String packageName) {
152 evaluateBinding(packageName, false /* forceUnbind */);
153 }
154
155 @Override
156 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
157 if (doit) { // "doit" means the force stop happened instead of just being queried for.
158 for (String packageName : packages) {
159 evaluateBinding(packageName, true /* forceUnbind */);
160 }
161 }
162 return super.onHandleForceStop(intent, packages, uid, doit);
163 }
164
165 @Override
166 public void onPackageUpdateFinished(String packageName, int uid) {
167 evaluateBinding(packageName, true /* forceUnbind */);
168 }
169
170 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800171 if (!mPackagesToWatch.contains(scorerPackageName)) {
172 // Early exit when we don't care about the package that has changed.
173 return;
174 }
175
176 if (DBG) {
177 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
178 + ", forceUnbind=" + forceUnbind);
179 }
180 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
181 if (activeScorer == null) {
182 // Package change has invalidated a scorer, this will also unbind any service
183 // connection.
184 if (DBG) Log.d(TAG, "No active scorers available.");
185 unbindFromScoringServiceIfNeeded();
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800186 } else if (activeScorer.getRecommendationServicePackageName().equals(scorerPackageName))
187 {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800188 // The active scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700189 if (DBG) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800190 Log.d(TAG, "Possible change to the active scorer: "
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800191 + activeScorer.getRecommendationServicePackageName());
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700192 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800193 if (forceUnbind) {
194 unbindFromScoringServiceIfNeeded();
195 }
196 bindToScoringServiceIfNeeded(activeScorer);
197 } else {
198 // One of the scoring apps on the device has changed and we may no longer be
199 // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
200 // will sort that out to leave us bound to the most recent active scorer.
201 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800202 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
203 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800204 }
205 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800206 }
207 }
208 }
209
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800210 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800211 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800212 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800213 @VisibleForTesting
214 public static class DispatchingContentObserver extends ContentObserver {
215 final private Map<Uri, Integer> mUriEventMap;
216 final private Context mContext;
217 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800218
Jeremy Joslincb594f32017-01-03 17:31:23 -0800219 public DispatchingContentObserver(Context context, Handler handler) {
220 super(handler);
221 mContext = context;
222 mHandler = handler;
223 mUriEventMap = new ArrayMap<>();
224 }
225
226 void observe(Uri uri, int what) {
227 mUriEventMap.put(uri, what);
228 final ContentResolver resolver = mContext.getContentResolver();
229 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800230 }
231
232 @Override
233 public void onChange(boolean selfChange) {
234 onChange(selfChange, null);
235 }
236
237 @Override
238 public void onChange(boolean selfChange, Uri uri) {
239 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800240 final Integer what = mUriEventMap.get(uri);
241 if (what != null) {
242 mHandler.obtainMessage(what).sendToTarget();
243 } else {
244 Log.w(TAG, "No matching event to send for URI = " + uri);
245 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800246 }
247 }
248
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700249 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800250 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800251 }
252
253 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800254 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
255 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700256 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800257 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800258 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700259 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
260 // TODO: Need to update when we support per-user scorers. http://b/23422763
261 mContext.registerReceiverAsUser(
262 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
263 null /* scheduler */);
Jeremy Joslin3452b692017-01-17 15:48:13 -0800264 mReqRecommendationCallerRef = new AtomicReference<>(
265 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800266 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
267 mHandler = new ServiceHandler(looper);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800268 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700269 }
270
271 /** Called when the system is ready to run third-party code but before it actually does so. */
272 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700273 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700274 registerPackageMonitorIfNeeded();
Jeremy Joslincb594f32017-01-03 17:31:23 -0800275 registerRecommendationSettingsObserver();
276 refreshRecommendationRequestTimeoutMs();
Jeff Davidson7842f642014-11-23 13:48:12 -0800277 }
278
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700279 /** Called when the system is ready for us to start third-party code. */
280 void systemRunning() {
281 if (DBG) Log.d(TAG, "systemRunning");
282 bindToScoringServiceIfNeeded();
283 }
284
Jeremy Joslin967b5812016-06-02 07:58:14 -0700285 private void onUserUnlocked(int userId) {
286 registerPackageMonitorIfNeeded();
287 bindToScoringServiceIfNeeded();
288 }
289
Jeremy Joslincb594f32017-01-03 17:31:23 -0800290 private void registerRecommendationSettingsObserver() {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800291 final List<String> providerPackages =
292 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
293 if (!providerPackages.isEmpty()) {
Jeremy Joslincb594f32017-01-03 17:31:23 -0800294 final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
295 mContentObserver.observe(enabledUri,
296 ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800297 }
Jeremy Joslincb594f32017-01-03 17:31:23 -0800298
299 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
300 mContentObserver.observe(timeoutUri,
301 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800302 }
303
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700304 private void registerPackageMonitorIfNeeded() {
305 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800306 final List<String> providerPackages =
307 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700308 synchronized (mPackageMonitorLock) {
309 // Unregister the current monitor if needed.
310 if (mPackageMonitor != null) {
311 if (DBG) {
312 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800313 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800314 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700315 mPackageMonitor.unregister();
316 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800317 }
318
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800319 // Create and register the monitor if there are packages that could be providers.
320 if (!providerPackages.isEmpty()) {
321 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700322 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700323 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
324 false /* externalStorage */);
325 if (DBG) {
326 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800327 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800328 }
329 }
330 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700331 }
332
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700333 private void bindToScoringServiceIfNeeded() {
334 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800335 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700336 bindToScoringServiceIfNeeded(scorerData);
337 }
338
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800339 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
340 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
341 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800342 synchronized (mServiceConnectionLock) {
343 // If we're connected to a different component then drop it.
344 if (mServiceConnection != null
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800345 && !mServiceConnection.mAppData.equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800346 unbindFromScoringServiceIfNeeded();
347 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700348
Jeremy Joslin145c3432016-12-09 13:11:51 -0800349 // If we're not connected at all then create a new connection.
350 if (mServiceConnection == null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800351 mServiceConnection = new ScoringServiceConnection(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800352 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700353
Jeremy Joslin145c3432016-12-09 13:11:51 -0800354 // Make sure the connection is connected (idempotent)
355 mServiceConnection.connect(mContext);
356 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700357 } else { // otherwise make sure it isn't bound.
358 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700359 }
360 }
361
362 private void unbindFromScoringServiceIfNeeded() {
363 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800364 synchronized (mServiceConnectionLock) {
365 if (mServiceConnection != null) {
366 mServiceConnection.disconnect(mContext);
367 }
368 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700369 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800370 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700371 }
372
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700373 @Override
374 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800375 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700376 throw new SecurityException("Caller with UID " + getCallingUid() +
377 " is not the active scorer.");
378 }
379
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800380 final long token = Binder.clearCallingIdentity();
381 try {
382 // Separate networks by type.
383 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
384 for (ScoredNetwork network : networks) {
385 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
386 if (networkList == null) {
387 networkList = new ArrayList<>();
388 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800389 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800390 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800391 }
392
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800393 // Pass the scores of each type down to the appropriate network scorer.
394 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
395 final RemoteCallbackList<INetworkScoreCache> callbackList;
396 final boolean isEmpty;
397 synchronized (mScoreCaches) {
398 callbackList = mScoreCaches.get(entry.getKey());
399 isEmpty = callbackList == null
400 || callbackList.getRegisteredCallbackCount() == 0;
401 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800402
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800403 if (isEmpty) {
404 if (Log.isLoggable(TAG, Log.VERBOSE)) {
405 Log.v(TAG, "No scorer registered for type " + entry.getKey()
406 + ", discarding");
407 }
408 continue;
409 }
410
Jeremy Joslinba242732017-01-24 17:16:42 -0800411 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800412 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800413 entry.getKey());
414 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800415 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700416
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800417 return true;
418 } finally {
419 Binder.restoreCallingIdentity(token);
420 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700421 }
422
Jeremy Joslinba242732017-01-24 17:16:42 -0800423 /**
424 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
425 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
426 * accepted {@link INetworkScoreCache} implementation.
427 */
428 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800429 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800430 implements BiConsumer<INetworkScoreCache, Object> {
431 private final Context mContext;
432 private final List<ScoredNetwork> mScoredNetworkList;
433 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800434 // TODO: 1/23/17 - Consider a Map if we implement more filters.
435 // These are created on-demand to defer the construction cost until
436 // an instance is actually needed.
437 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
438 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800439
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800440 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800441 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800442 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
443 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800444 }
445
446 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800447 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800448 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800449 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
450 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800451 mContext = context;
452 mScoredNetworkList = scoredNetworkList;
453 mNetworkType = networkType;
454 mCurrentNetworkFilter = currentNetworkFilter;
455 mScanResultsFilter = scanResultsFilter;
456 }
457
458 @Override
459 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
460 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
461 if (cookie instanceof Integer) {
462 filterType = (Integer) cookie;
463 }
464
465 try {
466 final List<ScoredNetwork> filteredNetworkList =
467 filterScores(mScoredNetworkList, filterType);
468 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800469 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800470 }
471 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800472 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800473 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
474 }
475 }
476 }
477
478 /**
479 * Applies the appropriate filter and returns the filtered results.
480 */
481 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
482 int filterType) {
483 switch (filterType) {
484 case NetworkScoreManager.CACHE_FILTER_NONE:
485 return scoredNetworkList;
486
487 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
488 if (mCurrentNetworkFilter == null) {
489 mCurrentNetworkFilter =
490 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
491 }
492 return mCurrentNetworkFilter.apply(scoredNetworkList);
493
494 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
495 if (mScanResultsFilter == null) {
496 mScanResultsFilter = new ScanResultsScoreCacheFilter(
497 new ScanResultsSupplier(mContext));
498 }
499 return mScanResultsFilter.apply(scoredNetworkList);
500
501 default:
502 Log.w(TAG, "Unknown filter type: " + filterType);
503 return scoredNetworkList;
504 }
505 }
506 }
507
508 /**
509 * Helper class that improves the testability of the cache filter Functions.
510 */
511 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
512 private final Context mContext;
513
514 WifiInfoSupplier(Context context) {
515 mContext = context;
516 }
517
518 @Override
519 public WifiInfo get() {
520 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
521 if (wifiManager != null) {
522 return wifiManager.getConnectionInfo();
523 }
524 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
525 return null;
526 }
527 }
528
529 /**
530 * Helper class that improves the testability of the cache filter Functions.
531 */
532 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
533 private final Context mContext;
534
535 ScanResultsSupplier(Context context) {
536 mContext = context;
537 }
538
539 @Override
540 public List<ScanResult> get() {
541 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
542 if (wifiScanner != null) {
543 return wifiScanner.getSingleScanResults();
544 }
545 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
546 return Collections.emptyList();
547 }
548 }
549
550 /**
551 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
552 * {@link ScoredNetwork} associated with the current network. If no network is connected the
553 * returned list will be empty.
554 * <p>
555 * Note: this filter performs some internal caching for consistency and performance. The
556 * current network is determined at construction time and never changed. Also, the
557 * last filtered list is saved so if the same input is provided multiple times in a row
558 * the computation is only done once.
559 */
560 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800561 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800562 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800563
564 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
565 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
566 }
567
568 @Override
569 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
570 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
571 return Collections.emptyList();
572 }
573
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800574 for (int i = 0; i < scoredNetworks.size(); i++) {
575 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
576 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
577 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800578 }
579 }
580
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800581 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800582 }
583 }
584
585 /**
586 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
587 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
588 * If there are no {@link ScanResult}s the returned list will be empty.
589 * <p>
590 * Note: this filter performs some internal caching for consistency and performance. The
591 * current set of ScanResults is determined at construction time and never changed.
592 * Also, the last filtered list is saved so if the same input is provided multiple
593 * times in a row the computation is only done once.
594 */
595 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800596 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
597 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800598
599 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800600 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800601 final int size = scanResults.size();
602 mScanResultKeys = new ArraySet<>(size);
603 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800604 ScanResult scanResult = scanResults.get(i);
605 mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
606 }
607 }
608
609 @Override
610 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
611 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
612 return Collections.emptyList();
613 }
614
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800615 List<ScoredNetwork> filteredScores = new ArrayList<>();
616 for (int i = 0; i < scoredNetworks.size(); i++) {
617 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
618 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
619 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800620 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800621 }
622
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800623 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800624 }
625 }
626
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800627 private boolean callerCanRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800628 // REQUEST_NETWORK_SCORES is a signature only permission.
629 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
630 PackageManager.PERMISSION_GRANTED;
631 }
632
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700633 @Override
634 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800635 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800636 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800637 final long token = Binder.clearCallingIdentity();
638 try {
639 clearInternal();
640 return true;
641 } finally {
642 Binder.restoreCallingIdentity(token);
643 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700644 } else {
645 throw new SecurityException(
646 "Caller is neither the active scorer nor the scorer manager.");
647 }
648 }
649
650 @Override
651 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800652 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
653 // to directly set the scorer app rather than having to use the consent dialog. The
654 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
655 // do the right thing and not enable this feature without explaining it to the user.
656 // In the future, should this API be opened to 3p apps, we will need to lock this down and
657 // figure out another way to streamline the UX.
658
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800659 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
660
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800661 // Scorers (recommendation providers) are selected and no longer set.
662 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700663 }
664
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800665 /**
666 * Determine whether the application with the given UID is the enabled scorer.
667 *
668 * @param callingUid the UID to check
669 * @return true if the provided UID is the active scorer, false otherwise.
670 */
671 @Override
672 public boolean isCallerActiveScorer(int callingUid) {
673 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800674 return mServiceConnection != null
675 && mServiceConnection.mAppData.packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800676 }
677 }
678
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800679 /**
680 * Obtain the package name of the current active network scorer.
681 *
682 * @return the full package name of the current active scorer, or null if there is no active
683 * scorer.
684 */
685 @Override
686 public String getActiveScorerPackage() {
687 synchronized (mServiceConnectionLock) {
688 if (mServiceConnection != null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800689 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800690 }
691 }
692 return null;
693 }
694
Jeff Davidson26fd1432014-07-29 09:39:52 -0700695 @Override
696 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800697 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800698 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800699 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700700 } else {
701 throw new SecurityException(
702 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700703 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700704 }
705
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700706 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
707 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800708 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800709 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800710 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800711 try {
712 networkScoreCache.clearScores();
713 } catch (RemoteException e) {
714 if (Log.isLoggable(TAG, Log.VERBOSE)) {
715 Log.v(TAG, "Unable to clear scores", e);
716 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700717 }
718 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800719 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700720 }
721
722 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800723 public void registerNetworkScoreCache(int networkType,
724 INetworkScoreCache scoreCache,
725 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800726 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800727 final long token = Binder.clearCallingIdentity();
728 try {
729 synchronized (mScoreCaches) {
730 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
731 if (callbackList == null) {
732 callbackList = new RemoteCallbackList<>();
733 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800734 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800735 if (!callbackList.register(scoreCache, filterType)) {
736 if (callbackList.getRegisteredCallbackCount() == 0) {
737 mScoreCaches.remove(networkType);
738 }
739 if (Log.isLoggable(TAG, Log.VERBOSE)) {
740 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
741 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800742 }
743 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800744 } finally {
745 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800746 }
747 }
748
749 @Override
750 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800751 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800752 final long token = Binder.clearCallingIdentity();
753 try {
754 synchronized (mScoreCaches) {
755 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
756 if (callbackList == null || !callbackList.unregister(scoreCache)) {
757 if (Log.isLoggable(TAG, Log.VERBOSE)) {
758 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
759 + networkType);
760 }
761 } else if (callbackList.getRegisteredCallbackCount() == 0) {
762 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800763 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800764 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800765 } finally {
766 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700767 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700768 }
769
770 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800771 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800772 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800773 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800774 final long token = Binder.clearCallingIdentity();
775 try {
776 final INetworkRecommendationProvider provider = getRecommendationProvider();
777 if (provider != null) {
778 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800779 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
780 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800781 } catch (RemoteException | TimeoutException e) {
782 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800783 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800784 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800785 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800786
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800787 if (DBG) {
788 Log.d(TAG, "Returning the default network recommendation.");
789 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800790
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800791 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800792 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800793 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800794 }
795 return RecommendationResult.createDoNotConnectRecommendation();
796 } finally {
797 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800798 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800799 }
800
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800801 /**
802 * Request a recommendation for the best network to connect to
803 * taking into account the inputs from the {@link RecommendationRequest}.
804 *
805 * @param request a {@link RecommendationRequest} instance containing the details of the request
806 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
807 * is available.
808 * @throws SecurityException if the caller is not the system
809 */
810 @Override
811 public void requestRecommendationAsync(RecommendationRequest request,
812 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800813 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800814
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800815 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
816 final Pair<RecommendationRequest, OneTimeCallback> pair =
817 Pair.create(request, oneTimeCallback);
818 final Message timeoutMsg = mHandler.obtainMessage(
819 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
820 final INetworkRecommendationProvider provider = getRecommendationProvider();
821 final long token = Binder.clearCallingIdentity();
822 try {
823 if (provider != null) {
824 try {
825 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
826 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
827 @Override
828 public void sendResult(Bundle data) throws RemoteException {
829 // Remove the timeout message
830 mHandler.removeMessages(timeoutMsg.what, pair);
831 oneTimeCallback.sendResult(data);
832 }
833 }, 0 /*sequence*/);
834 return;
835 } catch (RemoteException e) {
836 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800837 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800838 // Remove the timeout message
839 mHandler.removeMessages(timeoutMsg.what, pair);
840 // Will fall through and send back the default recommendation.
841 }
842 }
843 } finally {
844 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800845 }
846
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800847 // Else send back the default recommendation.
848 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800849 }
850
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800851 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800852 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800853 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800854 final long token = Binder.clearCallingIdentity();
855 try {
856 final INetworkRecommendationProvider provider = getRecommendationProvider();
857 if (provider != null) {
858 try {
859 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800860 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800861 // prevent repeated requests for the same scores.
862 return true;
863 } catch (RemoteException e) {
864 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800865 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800866 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800867 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800868 return false;
869 } finally {
870 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800871 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800872 }
873
874 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800875 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700876 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800877 final long token = Binder.clearCallingIdentity();
878 try {
879 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
880 if (currentScorer == null) {
881 writer.println("Scoring is disabled.");
882 return;
883 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800884 writer.println("Current scorer: " + currentScorer);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700885
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800886 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
887 @Override
888 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
889 try {
890 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
891 } catch (IOException | RemoteException e) {
892 writer.println("Failed to dump score cache: " + e);
893 }
894 }
895 }, getScoreCacheLists());
896
897 synchronized (mServiceConnectionLock) {
898 if (mServiceConnection != null) {
899 mServiceConnection.dump(fd, writer, args);
900 } else {
901 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800902 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700903 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800904 writer.flush();
905 } finally {
906 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700907 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700908 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700909
910 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800911 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700912 *
913 * <p>May be used to perform an action on all score caches without potentially strange behavior
914 * if a new scorer is registered during that action's execution.
915 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800916 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700917 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800918 return new ArrayList<>(mScoreCaches.values());
919 }
920 }
921
Jeremy Joslinba242732017-01-24 17:16:42 -0800922 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800923 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
924 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
925 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
926 final int count = callbackList.beginBroadcast();
927 try {
928 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800929 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800930 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800931 }
932 } finally {
933 callbackList.finishBroadcast();
934 }
935 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700936 }
937 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700938
Jeremy Joslin145c3432016-12-09 13:11:51 -0800939 private void throwIfCalledOnMainThread() {
940 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
941 throw new RuntimeException("Cannot invoke on the main thread");
942 }
943 }
944
945 @Nullable
946 private INetworkRecommendationProvider getRecommendationProvider() {
947 synchronized (mServiceConnectionLock) {
948 if (mServiceConnection != null) {
949 return mServiceConnection.getRecommendationProvider();
950 }
951 }
952 return null;
953 }
954
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800955 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -0800956 public void refreshRecommendationRequestTimeoutMs() {
957 final ContentResolver cr = mContext.getContentResolver();
958 long timeoutMs = Settings.Global.getLong(cr,
959 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
960 if (timeoutMs < 0) {
961 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
962 }
963 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
964 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800965 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800966 }
967
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700968 private static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800969 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800970 private volatile boolean mBound = false;
971 private volatile boolean mConnected = false;
972 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700973
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800974 ScoringServiceConnection(NetworkScorerAppData appData) {
975 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700976 }
977
978 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700979 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800980 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800981 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700982 mBound = context.bindServiceAsUser(service, this,
983 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
984 UserHandle.SYSTEM);
985 if (!mBound) {
986 Log.w(TAG, "Bind call failed for " + service);
987 } else {
988 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
989 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700990 }
991 }
992
993 void disconnect(Context context) {
994 try {
995 if (mBound) {
996 mBound = false;
997 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700998 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700999 }
1000 } catch (RuntimeException e) {
1001 Log.e(TAG, "Unbind failed.", e);
1002 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001003
1004 mRecommendationProvider = null;
1005 }
1006
1007 INetworkRecommendationProvider getRecommendationProvider() {
1008 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001009 }
1010
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001011 String getPackageName() {
1012 return mAppData.getRecommendationServiceComponent().getPackageName();
1013 }
1014
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001015 @Override
1016 public void onServiceConnected(ComponentName name, IBinder service) {
1017 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001018 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001019 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001020 }
1021
1022 @Override
1023 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001024 if (DBG) {
1025 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1026 }
1027 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001028 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001029 }
1030
1031 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001032 writer.println("ScoringServiceConnection: "
1033 + mAppData.getRecommendationServiceComponent()
1034 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001035 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001036 }
1037 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001038
1039 /**
1040 * Executes the async requestRecommendation() call with a timeout.
1041 */
1042 private static final class RequestRecommendationCaller
1043 extends TimedRemoteCaller<RecommendationResult> {
1044 private final IRemoteCallback mCallback;
1045
1046 RequestRecommendationCaller(long callTimeoutMillis) {
1047 super(callTimeoutMillis);
1048 mCallback = new IRemoteCallback.Stub() {
1049 @Override
1050 public void sendResult(Bundle data) throws RemoteException {
1051 final RecommendationResult result =
1052 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1053 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
1054 onRemoteMethodResult(result, sequence);
1055 }
1056 };
1057 }
1058
1059 /**
1060 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1061 * instance.
1062 *
1063 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1064 * from
1065 * @param request the {@link RecommendationRequest} from the calling client
1066 * @return a {@link RecommendationResult} from the provider
1067 * @throws RemoteException if the call failed
1068 * @throws TimeoutException if the call took longer than the set timeout
1069 */
1070 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1071 RecommendationRequest request) throws RemoteException, TimeoutException {
1072 final int sequence = onBeforeRemoteCall();
1073 target.requestRecommendation(request, mCallback, sequence);
1074 return getResultTimed(sequence);
1075 }
1076 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001077
1078 /**
1079 * A wrapper around {@link RemoteCallback} that guarantees
1080 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1081 */
1082 @VisibleForTesting
1083 public static final class OneTimeCallback {
1084 private final RemoteCallback mRemoteCallback;
1085 private final AtomicBoolean mCallbackRun;
1086
1087 public OneTimeCallback(RemoteCallback remoteCallback) {
1088 mRemoteCallback = remoteCallback;
1089 mCallbackRun = new AtomicBoolean(false);
1090 }
1091
1092 public void sendResult(Bundle data) {
1093 if (mCallbackRun.compareAndSet(false, true)) {
1094 mRemoteCallback.sendResult(data);
1095 }
1096 }
1097 }
1098
1099 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1100 OneTimeCallback remoteCallback) {
1101 if (DBG) {
1102 Log.d(TAG, "Returning the default network recommendation.");
1103 }
1104
1105 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001106 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001107 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001108 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001109 } else {
1110 result = RecommendationResult.createDoNotConnectRecommendation();
1111 }
1112
1113 final Bundle data = new Bundle();
1114 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1115 remoteCallback.sendResult(data);
1116 }
1117
1118 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001119 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001120 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001121 public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2;
1122 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001123
1124 public ServiceHandler(Looper looper) {
1125 super(looper);
1126 }
1127
1128 @Override
1129 public void handleMessage(Message msg) {
1130 final int what = msg.what;
1131 switch (what) {
1132 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1133 if (DBG) {
1134 Log.d(TAG, "Network recommendation request timed out.");
1135 }
1136 final Pair<RecommendationRequest, OneTimeCallback> pair =
1137 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1138 final RecommendationRequest request = pair.first;
1139 final OneTimeCallback remoteCallback = pair.second;
1140 sendDefaultRecommendationResponse(request, remoteCallback);
1141 break;
1142
Jeremy Joslincb594f32017-01-03 17:31:23 -08001143 case MSG_RECOMMENDATIONS_ENABLED_CHANGED:
1144 bindToScoringServiceIfNeeded();
1145 break;
1146
1147 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1148 refreshRecommendationRequestTimeoutMs();
1149 break;
1150
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001151 default:
1152 Log.w(TAG,"Unknown message: " + what);
1153 }
1154 }
1155 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001156}