blob: d25b3cc682219339ad12fb4a8c5b7ee48cac6c1f [file] [log] [blame]
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server;
18
Jeremy Joslin145c3432016-12-09 13:11:51 -080019import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
20import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
21
Jeff Davidson6a4b2202014-04-16 17:29:40 -070022import android.Manifest.permission;
Jeremy Joslin145c3432016-12-09 13:11:51 -080023import android.annotation.Nullable;
Jeremy Joslin967b5812016-06-02 07:58:14 -070024import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070025import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070026import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070028import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070029import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070030import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.content.pm.PackageManager;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080032import android.database.ContentObserver;
Jeremy Joslin145c3432016-12-09 13:11:51 -080033import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070034import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070035import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080036import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080037import android.net.NetworkScoreManager;
Jeremy Joslinf621bc92017-02-16 11:11:57 -080038import android.net.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080039import android.net.RecommendationRequest;
40import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070041import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080042import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080043import android.net.wifi.ScanResult;
44import android.net.wifi.WifiInfo;
45import android.net.wifi.WifiManager;
46import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080047import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080048import android.os.Build;
Jeremy Joslin145c3432016-12-09 13:11:51 -080049import android.os.Bundle;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080050import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070051import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080052import android.os.IRemoteCallback;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080053import android.os.Looper;
54import android.os.Message;
Jeremy Joslina5172f62017-02-02 14:27:05 -080055import android.os.Process;
Jeremy Joslin998d7ca2016-12-28 15:56:46 -080056import android.os.RemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080057import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070058import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070059import android.os.UserHandle;
Jeremy Joslincb594f32017-01-03 17:31:23 -080060import android.provider.Settings;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080061import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080062import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080063import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080065import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066import android.util.TimedRemoteCaller;
67
Jeff Davidson7842f642014-11-23 13:48:12 -080068import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080069import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070070import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070071import com.android.internal.os.TransferPipe;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060072import com.android.internal.util.DumpUtils;
Jeremy Joslin145c3432016-12-09 13:11:51 -080073
Jeff Davidson6a4b2202014-04-16 17:29:40 -070074import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070075import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070076import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070077import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080078import java.util.Collection;
79import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070080import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070081import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080082import java.util.Set;
Jeremy Joslin145c3432016-12-09 13:11:51 -080083import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080084import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080085import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080086import java.util.function.BiConsumer;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -070087import java.util.function.Function;
Jeremy Joslinba242732017-01-24 17:16:42 -080088import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080089import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070090
91/**
92 * Backing service for {@link android.net.NetworkScoreManager}.
93 * @hide
94 */
95public class NetworkScoreService extends INetworkScoreService.Stub {
96 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080097 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080098 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070099
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700100 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800101 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800102 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -0800103 @GuardedBy("mScoreCaches")
104 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700105 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800106 private final Object mPackageMonitorLock = new Object();
107 private final Object mServiceConnectionLock = new Object();
108 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800109 private final DispatchingContentObserver mContentObserver;
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700110 private final Function<NetworkScorerAppData, ScoringServiceConnection> mServiceConnProducer;
Jeff Davidson7842f642014-11-23 13:48:12 -0800111
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700112 @GuardedBy("mPackageMonitorLock")
113 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800114 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700115 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800116 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800117
Jeremy Joslin967b5812016-06-02 07:58:14 -0700118 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
119 @Override
120 public void onReceive(Context context, Intent intent) {
121 final String action = intent.getAction();
122 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
123 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
124 if (userId == UserHandle.USER_NULL) return;
125
126 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
127 onUserUnlocked(userId);
128 }
129 }
130 };
131
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700132 /**
133 * Clears scores when the active scorer package is no longer valid and
134 * manages the service connection.
135 */
136 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800137 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800138
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800139 private NetworkScorerPackageMonitor(String packageToWatch) {
140 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800141 }
142
143 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700144 public void onPackageAdded(String packageName, int uid) {
145 evaluateBinding(packageName, true /* forceUnbind */);
146 }
147
148 @Override
149 public void onPackageRemoved(String packageName, int uid) {
150 evaluateBinding(packageName, true /* forceUnbind */);
151 }
152
153 @Override
154 public void onPackageModified(String packageName) {
155 evaluateBinding(packageName, false /* forceUnbind */);
156 }
157
158 @Override
159 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
160 if (doit) { // "doit" means the force stop happened instead of just being queried for.
161 for (String packageName : packages) {
162 evaluateBinding(packageName, true /* forceUnbind */);
163 }
164 }
165 return super.onHandleForceStop(intent, packages, uid, doit);
166 }
167
168 @Override
169 public void onPackageUpdateFinished(String packageName, int uid) {
170 evaluateBinding(packageName, true /* forceUnbind */);
171 }
172
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800173 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
174 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800175 // Early exit when we don't care about the package that has changed.
176 return;
177 }
178
179 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800180 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800181 + ", forceUnbind=" + forceUnbind);
182 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800183
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800184 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
185 if (activeScorer == null) {
186 // Package change has invalidated a scorer, this will also unbind any service
187 // connection.
188 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800189 refreshBinding();
190 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800191 if (forceUnbind) {
192 unbindFromScoringServiceIfNeeded();
193 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800194 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800195 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
196 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800197 }
198 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800199 }
200 }
201 }
202
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800203 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800204 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800205 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800206 @VisibleForTesting
207 public static class DispatchingContentObserver extends ContentObserver {
208 final private Map<Uri, Integer> mUriEventMap;
209 final private Context mContext;
210 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800211
Jeremy Joslincb594f32017-01-03 17:31:23 -0800212 public DispatchingContentObserver(Context context, Handler handler) {
213 super(handler);
214 mContext = context;
215 mHandler = handler;
216 mUriEventMap = new ArrayMap<>();
217 }
218
219 void observe(Uri uri, int what) {
220 mUriEventMap.put(uri, what);
221 final ContentResolver resolver = mContext.getContentResolver();
222 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800223 }
224
225 @Override
226 public void onChange(boolean selfChange) {
227 onChange(selfChange, null);
228 }
229
230 @Override
231 public void onChange(boolean selfChange, Uri uri) {
232 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800233 final Integer what = mUriEventMap.get(uri);
234 if (what != null) {
235 mHandler.obtainMessage(what).sendToTarget();
236 } else {
237 Log.w(TAG, "No matching event to send for URI = " + uri);
238 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800239 }
240 }
241
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700242 public NetworkScoreService(Context context) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700243 this(context, new NetworkScorerAppManager(context),
244 ScoringServiceConnection::new, Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800245 }
246
247 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800248 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700249 Function<NetworkScorerAppData, ScoringServiceConnection> serviceConnProducer,
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800250 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);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700264 mServiceConnProducer = serviceConnProducer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700265 }
266
267 /** Called when the system is ready to run third-party code but before it actually does so. */
268 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700269 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800270 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800271 }
272
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700273 /** Called when the system is ready for us to start third-party code. */
274 void systemRunning() {
275 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700276 }
277
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800278 @VisibleForTesting
279 void onUserUnlocked(int userId) {
280 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
281 refreshBinding();
282 }
283
284 private void refreshBinding() {
285 if (DBG) Log.d(TAG, "refreshBinding()");
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800286 // Make sure the scorer is up-to-date
287 mNetworkScorerAppManager.updateState();
Jeremy Joslinb0fe2172017-03-31 10:38:31 -0700288 mNetworkScorerAppManager.migrateNetworkScorerAppSettingIfNeeded();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700289 registerPackageMonitorIfNeeded();
290 bindToScoringServiceIfNeeded();
291 }
292
Jeremy Joslincb594f32017-01-03 17:31:23 -0800293 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800294 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
295 mContentObserver.observe(packageNameUri,
296 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800297
298 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
299 mContentObserver.observe(timeoutUri,
300 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslin9925c6a2017-03-06 10:39:35 -0800301
302 final Uri settingUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
303 mContentObserver.observe(settingUri,
304 ServiceHandler.MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800305 }
306
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800307 /**
308 * Ensures the package manager is registered to monitor the current active scorer.
309 * If a discrepancy is found any previous monitor will be cleaned up
310 * and a new monitor will be created.
311 *
312 * This method is idempotent.
313 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700314 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800315 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
316 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700317 synchronized (mPackageMonitorLock) {
318 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800319 if (mPackageMonitor != null && (appData == null
320 || !appData.getRecommendationServicePackageName().equals(
321 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700322 if (DBG) {
323 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800324 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800325 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700326 mPackageMonitor.unregister();
327 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800328 }
329
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800330 // Create and register the monitor if a scorer is active.
331 if (appData != null && mPackageMonitor == null) {
332 mPackageMonitor = new NetworkScorerPackageMonitor(
333 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700334 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700335 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
336 false /* externalStorage */);
337 if (DBG) {
338 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800339 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800340 }
341 }
342 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700343 }
344
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700345 private void bindToScoringServiceIfNeeded() {
346 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800347 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700348 bindToScoringServiceIfNeeded(scorerData);
349 }
350
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800351 /**
352 * Ensures the service connection is bound to the current active scorer.
353 * If a discrepancy is found any previous connection will be cleaned up
354 * and a new connection will be created.
355 *
356 * This method is idempotent.
357 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800358 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
359 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
360 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800361 synchronized (mServiceConnectionLock) {
362 // If we're connected to a different component then drop it.
363 if (mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700364 && !mServiceConnection.getAppData().equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800365 unbindFromScoringServiceIfNeeded();
366 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700367
Jeremy Joslin145c3432016-12-09 13:11:51 -0800368 // If we're not connected at all then create a new connection.
369 if (mServiceConnection == null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700370 mServiceConnection = mServiceConnProducer.apply(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800371 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700372
Jeremy Joslin145c3432016-12-09 13:11:51 -0800373 // Make sure the connection is connected (idempotent)
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700374 mServiceConnection.bind(mContext);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800375 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700376 } else { // otherwise make sure it isn't bound.
377 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700378 }
379 }
380
381 private void unbindFromScoringServiceIfNeeded() {
382 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800383 synchronized (mServiceConnectionLock) {
384 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700385 mServiceConnection.unbind(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800386 if (DBG) Log.d(TAG, "Disconnected from: "
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700387 + mServiceConnection.getAppData().getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800388 }
389 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700390 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800391 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700392 }
393
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700394 @Override
395 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800396 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700397 throw new SecurityException("Caller with UID " + getCallingUid() +
398 " is not the active scorer.");
399 }
400
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800401 final long token = Binder.clearCallingIdentity();
402 try {
403 // Separate networks by type.
404 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
405 for (ScoredNetwork network : networks) {
406 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
407 if (networkList == null) {
408 networkList = new ArrayList<>();
409 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800410 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800411 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800412 }
413
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800414 // Pass the scores of each type down to the appropriate network scorer.
415 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
416 final RemoteCallbackList<INetworkScoreCache> callbackList;
417 final boolean isEmpty;
418 synchronized (mScoreCaches) {
419 callbackList = mScoreCaches.get(entry.getKey());
420 isEmpty = callbackList == null
421 || callbackList.getRegisteredCallbackCount() == 0;
422 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800423
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800424 if (isEmpty) {
425 if (Log.isLoggable(TAG, Log.VERBOSE)) {
426 Log.v(TAG, "No scorer registered for type " + entry.getKey()
427 + ", discarding");
428 }
429 continue;
430 }
431
Jeremy Joslinba242732017-01-24 17:16:42 -0800432 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800433 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800434 entry.getKey());
435 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800436 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700437
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800438 return true;
439 } finally {
440 Binder.restoreCallingIdentity(token);
441 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700442 }
443
Jeremy Joslinba242732017-01-24 17:16:42 -0800444 /**
445 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
446 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
447 * accepted {@link INetworkScoreCache} implementation.
448 */
449 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800450 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800451 implements BiConsumer<INetworkScoreCache, Object> {
452 private final Context mContext;
453 private final List<ScoredNetwork> mScoredNetworkList;
454 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800455 // TODO: 1/23/17 - Consider a Map if we implement more filters.
456 // These are created on-demand to defer the construction cost until
457 // an instance is actually needed.
458 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
459 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800460
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800461 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800462 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800463 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
464 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800465 }
466
467 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800468 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800469 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800470 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
471 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800472 mContext = context;
473 mScoredNetworkList = scoredNetworkList;
474 mNetworkType = networkType;
475 mCurrentNetworkFilter = currentNetworkFilter;
476 mScanResultsFilter = scanResultsFilter;
477 }
478
479 @Override
480 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
481 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
482 if (cookie instanceof Integer) {
483 filterType = (Integer) cookie;
484 }
485
486 try {
487 final List<ScoredNetwork> filteredNetworkList =
488 filterScores(mScoredNetworkList, filterType);
489 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800490 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800491 }
492 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800493 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800494 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
495 }
496 }
497 }
498
499 /**
500 * Applies the appropriate filter and returns the filtered results.
501 */
502 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
503 int filterType) {
504 switch (filterType) {
505 case NetworkScoreManager.CACHE_FILTER_NONE:
506 return scoredNetworkList;
507
508 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
509 if (mCurrentNetworkFilter == null) {
510 mCurrentNetworkFilter =
511 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
512 }
513 return mCurrentNetworkFilter.apply(scoredNetworkList);
514
515 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
516 if (mScanResultsFilter == null) {
517 mScanResultsFilter = new ScanResultsScoreCacheFilter(
518 new ScanResultsSupplier(mContext));
519 }
520 return mScanResultsFilter.apply(scoredNetworkList);
521
522 default:
523 Log.w(TAG, "Unknown filter type: " + filterType);
524 return scoredNetworkList;
525 }
526 }
527 }
528
529 /**
530 * Helper class that improves the testability of the cache filter Functions.
531 */
532 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
533 private final Context mContext;
534
535 WifiInfoSupplier(Context context) {
536 mContext = context;
537 }
538
539 @Override
540 public WifiInfo get() {
541 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
542 if (wifiManager != null) {
543 return wifiManager.getConnectionInfo();
544 }
545 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
546 return null;
547 }
548 }
549
550 /**
551 * Helper class that improves the testability of the cache filter Functions.
552 */
553 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
554 private final Context mContext;
555
556 ScanResultsSupplier(Context context) {
557 mContext = context;
558 }
559
560 @Override
561 public List<ScanResult> get() {
562 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
563 if (wifiScanner != null) {
564 return wifiScanner.getSingleScanResults();
565 }
566 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
567 return Collections.emptyList();
568 }
569 }
570
571 /**
572 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
573 * {@link ScoredNetwork} associated with the current network. If no network is connected the
574 * returned list will be empty.
575 * <p>
576 * Note: this filter performs some internal caching for consistency and performance. The
577 * current network is determined at construction time and never changed. Also, the
578 * last filtered list is saved so if the same input is provided multiple times in a row
579 * the computation is only done once.
580 */
581 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800582 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800583 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800584
585 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
586 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
587 }
588
589 @Override
590 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
591 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
592 return Collections.emptyList();
593 }
594
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800595 for (int i = 0; i < scoredNetworks.size(); i++) {
596 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
597 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
598 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800599 }
600 }
601
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800602 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800603 }
604 }
605
606 /**
607 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
608 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
609 * If there are no {@link ScanResult}s the returned list will be empty.
610 * <p>
611 * Note: this filter performs some internal caching for consistency and performance. The
612 * current set of ScanResults is determined at construction time and never changed.
613 * Also, the last filtered list is saved so if the same input is provided multiple
614 * times in a row the computation is only done once.
615 */
616 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800617 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
618 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800619
620 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800621 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800622 final int size = scanResults.size();
623 mScanResultKeys = new ArraySet<>(size);
624 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800625 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800626 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
627 if (key != null) {
628 mScanResultKeys.add(key);
629 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800630 }
631 }
632
633 @Override
634 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
635 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
636 return Collections.emptyList();
637 }
638
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800639 List<ScoredNetwork> filteredScores = new ArrayList<>();
640 for (int i = 0; i < scoredNetworks.size(); i++) {
641 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
642 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
643 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800644 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800645 }
646
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800647 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800648 }
649 }
650
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800651 private boolean callerCanRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800652 // REQUEST_NETWORK_SCORES is a signature only permission.
653 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
654 PackageManager.PERMISSION_GRANTED;
655 }
656
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700657 @Override
658 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800659 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800660 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800661 final long token = Binder.clearCallingIdentity();
662 try {
663 clearInternal();
664 return true;
665 } finally {
666 Binder.restoreCallingIdentity(token);
667 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700668 } else {
669 throw new SecurityException(
670 "Caller is neither the active scorer nor the scorer manager.");
671 }
672 }
673
674 @Override
675 public boolean setActiveScorer(String packageName) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800676 // Only the system can set the active scorer
Jeremy Joslin3bddadd2017-03-21 16:16:46 -0700677 if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800678 throw new SecurityException(
679 "Caller is neither the system process nor a score requester.");
680 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800681
682 return mNetworkScorerAppManager.setActiveScorer(packageName);
Jeff Davidson26fd1432014-07-29 09:39:52 -0700683 }
684
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800685 /**
686 * Determine whether the application with the given UID is the enabled scorer.
687 *
688 * @param callingUid the UID to check
689 * @return true if the provided UID is the active scorer, false otherwise.
690 */
691 @Override
692 public boolean isCallerActiveScorer(int callingUid) {
693 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800694 return mServiceConnection != null
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700695 && mServiceConnection.getAppData().packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800696 }
697 }
698
Jeremy Joslina5172f62017-02-02 14:27:05 -0800699 private boolean isCallerSystemProcess(int callingUid) {
700 return callingUid == Process.SYSTEM_UID;
701 }
702
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800703 /**
704 * Obtain the package name of the current active network scorer.
705 *
706 * @return the full package name of the current active scorer, or null if there is no active
707 * scorer.
708 */
709 @Override
710 public String getActiveScorerPackage() {
711 synchronized (mServiceConnectionLock) {
712 if (mServiceConnection != null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800713 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800714 }
715 }
716 return null;
717 }
718
Jeremy Joslina5172f62017-02-02 14:27:05 -0800719 /**
720 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
721 */
722 @Override
723 public NetworkScorerAppData getActiveScorer() {
724 // Only the system can access this data.
725 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
726 synchronized (mServiceConnectionLock) {
727 if (mServiceConnection != null) {
Jeremy Joslin1e2595d2017-04-05 14:50:32 -0700728 return mServiceConnection.getAppData();
Jeremy Joslina5172f62017-02-02 14:27:05 -0800729 }
730 }
731 } else {
732 throw new SecurityException(
733 "Caller is neither the system process nor a score requester.");
734 }
735
736 return null;
737 }
738
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800739 /**
740 * Returns the list of available scorer apps. The list will be empty if there are
741 * no valid scorers.
742 */
743 @Override
744 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800745 // Only the system can access this data.
Jeremy Joslin3bddadd2017-03-21 16:16:46 -0700746 if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800747 throw new SecurityException(
748 "Caller is neither the system process nor a score requester.");
749 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800750
751 return mNetworkScorerAppManager.getAllValidScorers();
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800752 }
753
Jeff Davidson26fd1432014-07-29 09:39:52 -0700754 @Override
755 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800756 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin3bddadd2017-03-21 16:16:46 -0700757 if (!isCallerActiveScorer(getCallingUid()) && !callerCanRequestScores()) {
Jeff Davidson26fd1432014-07-29 09:39:52 -0700758 throw new SecurityException(
759 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700760 }
Jeremy Josline9052a32017-02-27 15:47:54 -0800761
762 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700763 }
764
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700765 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
766 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800767 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800768 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800769 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800770 try {
771 networkScoreCache.clearScores();
772 } catch (RemoteException e) {
773 if (Log.isLoggable(TAG, Log.VERBOSE)) {
774 Log.v(TAG, "Unable to clear scores", e);
775 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700776 }
777 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800778 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700779 }
780
781 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800782 public void registerNetworkScoreCache(int networkType,
783 INetworkScoreCache scoreCache,
784 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800785 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800786 final long token = Binder.clearCallingIdentity();
787 try {
788 synchronized (mScoreCaches) {
789 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
790 if (callbackList == null) {
791 callbackList = new RemoteCallbackList<>();
792 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800793 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800794 if (!callbackList.register(scoreCache, filterType)) {
795 if (callbackList.getRegisteredCallbackCount() == 0) {
796 mScoreCaches.remove(networkType);
797 }
798 if (Log.isLoggable(TAG, Log.VERBOSE)) {
799 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
800 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800801 }
802 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800803 } finally {
804 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800805 }
806 }
807
808 @Override
809 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800810 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800811 final long token = Binder.clearCallingIdentity();
812 try {
813 synchronized (mScoreCaches) {
814 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
815 if (callbackList == null || !callbackList.unregister(scoreCache)) {
816 if (Log.isLoggable(TAG, Log.VERBOSE)) {
817 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
818 + networkType);
819 }
820 } else if (callbackList.getRegisteredCallbackCount() == 0) {
821 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800822 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800823 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800824 } finally {
825 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700826 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700827 }
828
829 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800830 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800831 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800832 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800833 final long token = Binder.clearCallingIdentity();
834 try {
835 final INetworkRecommendationProvider provider = getRecommendationProvider();
836 if (provider != null) {
837 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800838 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
839 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800840 } catch (RemoteException | TimeoutException e) {
841 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800842 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800843 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800844 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800845
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800846 if (DBG) {
847 Log.d(TAG, "Returning the default network recommendation.");
848 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800849
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800850 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800851 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800852 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800853 }
854 return RecommendationResult.createDoNotConnectRecommendation();
855 } finally {
856 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800857 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800858 }
859
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800860 /**
861 * Request a recommendation for the best network to connect to
862 * taking into account the inputs from the {@link RecommendationRequest}.
863 *
864 * @param request a {@link RecommendationRequest} instance containing the details of the request
865 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
866 * is available.
867 * @throws SecurityException if the caller is not the system
868 */
869 @Override
870 public void requestRecommendationAsync(RecommendationRequest request,
871 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800872 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800873
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800874 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
875 final Pair<RecommendationRequest, OneTimeCallback> pair =
876 Pair.create(request, oneTimeCallback);
877 final Message timeoutMsg = mHandler.obtainMessage(
878 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
879 final INetworkRecommendationProvider provider = getRecommendationProvider();
880 final long token = Binder.clearCallingIdentity();
881 try {
882 if (provider != null) {
883 try {
884 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
885 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
886 @Override
887 public void sendResult(Bundle data) throws RemoteException {
888 // Remove the timeout message
889 mHandler.removeMessages(timeoutMsg.what, pair);
890 oneTimeCallback.sendResult(data);
891 }
892 }, 0 /*sequence*/);
893 return;
894 } catch (RemoteException e) {
895 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800896 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800897 // Remove the timeout message
898 mHandler.removeMessages(timeoutMsg.what, pair);
899 // Will fall through and send back the default recommendation.
900 }
901 }
902 } finally {
903 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800904 }
905
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800906 // Else send back the default recommendation.
907 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800908 }
909
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800910 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800911 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800912 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800913 final long token = Binder.clearCallingIdentity();
914 try {
915 final INetworkRecommendationProvider provider = getRecommendationProvider();
916 if (provider != null) {
917 try {
918 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800919 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800920 // prevent repeated requests for the same scores.
921 return true;
922 } catch (RemoteException e) {
923 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800924 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800925 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800926 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800927 return false;
928 } finally {
929 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800930 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800931 }
932
933 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800934 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600935 if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800936 final long token = Binder.clearCallingIdentity();
937 try {
938 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
939 if (currentScorer == null) {
940 writer.println("Scoring is disabled.");
941 return;
942 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800943 writer.println("Current scorer: " + currentScorer);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -0800944 writer.println("RecommendationRequestTimeoutMs: " + mRecommendationRequestTimeoutMs);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700945
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800946 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
947 @Override
948 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
949 try {
950 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
951 } catch (IOException | RemoteException e) {
952 writer.println("Failed to dump score cache: " + e);
953 }
954 }
955 }, getScoreCacheLists());
956
957 synchronized (mServiceConnectionLock) {
958 if (mServiceConnection != null) {
959 mServiceConnection.dump(fd, writer, args);
960 } else {
961 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800962 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700963 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800964 writer.flush();
965 } finally {
966 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700967 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700968 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700969
970 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800971 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700972 *
973 * <p>May be used to perform an action on all score caches without potentially strange behavior
974 * if a new scorer is registered during that action's execution.
975 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800976 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700977 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800978 return new ArrayList<>(mScoreCaches.values());
979 }
980 }
981
Jeremy Joslinba242732017-01-24 17:16:42 -0800982 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800983 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
984 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
985 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
986 final int count = callbackList.beginBroadcast();
987 try {
988 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800989 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800990 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800991 }
992 } finally {
993 callbackList.finishBroadcast();
994 }
995 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700996 }
997 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700998
Jeremy Joslin145c3432016-12-09 13:11:51 -0800999 private void throwIfCalledOnMainThread() {
1000 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
1001 throw new RuntimeException("Cannot invoke on the main thread");
1002 }
1003 }
1004
1005 @Nullable
1006 private INetworkRecommendationProvider getRecommendationProvider() {
1007 synchronized (mServiceConnectionLock) {
1008 if (mServiceConnection != null) {
1009 return mServiceConnection.getRecommendationProvider();
1010 }
1011 }
1012 return null;
1013 }
1014
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001015 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001016 public void refreshRecommendationRequestTimeoutMs() {
1017 final ContentResolver cr = mContext.getContentResolver();
1018 long timeoutMs = Settings.Global.getLong(cr,
1019 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
1020 if (timeoutMs < 0) {
1021 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
1022 }
1023 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
1024 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -08001025 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001026 }
1027
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001028 // The class and methods need to be public for Mockito to work.
1029 @VisibleForTesting
1030 public static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001031 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001032 private volatile boolean mBound = false;
1033 private volatile boolean mConnected = false;
1034 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001035
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001036 ScoringServiceConnection(NetworkScorerAppData appData) {
1037 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001038 }
1039
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001040 @VisibleForTesting
1041 public void bind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001042 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -08001043 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001044 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001045 mBound = context.bindServiceAsUser(service, this,
1046 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
1047 UserHandle.SYSTEM);
1048 if (!mBound) {
1049 Log.w(TAG, "Bind call failed for " + service);
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001050 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001051 } else {
1052 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1053 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001054 }
1055 }
1056
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001057 @VisibleForTesting
1058 public void unbind(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001059 try {
1060 if (mBound) {
1061 mBound = false;
1062 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001063 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001064 }
1065 } catch (RuntimeException e) {
1066 Log.e(TAG, "Unbind failed.", e);
1067 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001068
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001069 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001070 mRecommendationProvider = null;
1071 }
1072
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001073 @VisibleForTesting
1074 public NetworkScorerAppData getAppData() {
1075 return mAppData;
1076 }
1077
1078 @VisibleForTesting
1079 public INetworkRecommendationProvider getRecommendationProvider() {
Jeremy Joslin145c3432016-12-09 13:11:51 -08001080 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001081 }
1082
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001083 @VisibleForTesting
1084 public String getPackageName() {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001085 return mAppData.getRecommendationServiceComponent().getPackageName();
1086 }
1087
Jeremy Joslin1e2595d2017-04-05 14:50:32 -07001088 @VisibleForTesting
1089 public boolean isAlive() {
1090 return mBound && mConnected;
1091 }
1092
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001093 @Override
1094 public void onServiceConnected(ComponentName name, IBinder service) {
1095 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001096 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001097 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001098 }
1099
1100 @Override
1101 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001102 if (DBG) {
1103 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1104 }
1105 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001106 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001107 }
1108
1109 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001110 writer.println("ScoringServiceConnection: "
1111 + mAppData.getRecommendationServiceComponent()
1112 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001113 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001114 }
1115 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001116
1117 /**
1118 * Executes the async requestRecommendation() call with a timeout.
1119 */
1120 private static final class RequestRecommendationCaller
1121 extends TimedRemoteCaller<RecommendationResult> {
1122 private final IRemoteCallback mCallback;
1123
1124 RequestRecommendationCaller(long callTimeoutMillis) {
1125 super(callTimeoutMillis);
1126 mCallback = new IRemoteCallback.Stub() {
1127 @Override
1128 public void sendResult(Bundle data) throws RemoteException {
1129 final RecommendationResult result =
1130 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1131 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001132 if (VERBOSE) Log.v(TAG, "callback received for sequence " + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001133 onRemoteMethodResult(result, sequence);
1134 }
1135 };
1136 }
1137
1138 /**
1139 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1140 * instance.
1141 *
1142 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1143 * from
1144 * @param request the {@link RecommendationRequest} from the calling client
1145 * @return a {@link RecommendationResult} from the provider
1146 * @throws RemoteException if the call failed
1147 * @throws TimeoutException if the call took longer than the set timeout
1148 */
1149 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1150 RecommendationRequest request) throws RemoteException, TimeoutException {
1151 final int sequence = onBeforeRemoteCall();
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001152 if (VERBOSE) Log.v(TAG, "getRecommendationResult() seq=" + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001153 target.requestRecommendation(request, mCallback, sequence);
1154 return getResultTimed(sequence);
1155 }
1156 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001157
1158 /**
1159 * A wrapper around {@link RemoteCallback} that guarantees
1160 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1161 */
1162 @VisibleForTesting
1163 public static final class OneTimeCallback {
1164 private final RemoteCallback mRemoteCallback;
1165 private final AtomicBoolean mCallbackRun;
1166
1167 public OneTimeCallback(RemoteCallback remoteCallback) {
1168 mRemoteCallback = remoteCallback;
1169 mCallbackRun = new AtomicBoolean(false);
1170 }
1171
1172 public void sendResult(Bundle data) {
1173 if (mCallbackRun.compareAndSet(false, true)) {
1174 mRemoteCallback.sendResult(data);
1175 }
1176 }
1177 }
1178
1179 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1180 OneTimeCallback remoteCallback) {
1181 if (DBG) {
1182 Log.d(TAG, "Returning the default network recommendation.");
1183 }
1184
1185 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001186 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001187 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001188 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001189 } else {
1190 result = RecommendationResult.createDoNotConnectRecommendation();
1191 }
1192
1193 final Bundle data = new Bundle();
1194 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1195 remoteCallback.sendResult(data);
1196 }
1197
1198 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001199 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001200 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001201 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 2;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001202 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001203 public static final int MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED = 4;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001204
1205 public ServiceHandler(Looper looper) {
1206 super(looper);
1207 }
1208
1209 @Override
1210 public void handleMessage(Message msg) {
1211 final int what = msg.what;
1212 switch (what) {
1213 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1214 if (DBG) {
1215 Log.d(TAG, "Network recommendation request timed out.");
1216 }
1217 final Pair<RecommendationRequest, OneTimeCallback> pair =
1218 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1219 final RecommendationRequest request = pair.first;
1220 final OneTimeCallback remoteCallback = pair.second;
1221 sendDefaultRecommendationResponse(request, remoteCallback);
1222 break;
1223
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001224 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
Jeremy Joslin9925c6a2017-03-06 10:39:35 -08001225 case MSG_RECOMMENDATION_ENABLED_SETTING_CHANGED:
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001226 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001227 break;
1228
1229 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1230 refreshRecommendationRequestTimeoutMs();
1231 break;
1232
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001233 default:
1234 Log.w(TAG,"Unknown message: " + what);
1235 }
1236 }
1237 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001238}