blob: d54ebaafb5bb6cd7a003bbd74747af9500e7a649 [file] [log] [blame]
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server;
18
Jeremy Joslin145c3432016-12-09 13:11:51 -080019import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
20import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
21
Jeff Davidson6a4b2202014-04-16 17:29:40 -070022import android.Manifest.permission;
Jeremy Joslin145c3432016-12-09 13:11:51 -080023import android.annotation.Nullable;
Jeremy Joslin967b5812016-06-02 07:58:14 -070024import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070025import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070026import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070028import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070029import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070030import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.content.pm.PackageManager;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080032import android.database.ContentObserver;
Jeremy Joslin145c3432016-12-09 13:11:51 -080033import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070034import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070035import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080036import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080037import android.net.NetworkScoreManager;
Jeremy Joslinf621bc92017-02-16 11:11:57 -080038import android.net.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080039import android.net.RecommendationRequest;
40import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070041import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080042import android.net.Uri;
Jeremy Joslinba242732017-01-24 17:16:42 -080043import android.net.wifi.ScanResult;
44import android.net.wifi.WifiInfo;
45import android.net.wifi.WifiManager;
46import android.net.wifi.WifiScanner;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080047import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080048import android.os.Build;
Jeremy Joslin145c3432016-12-09 13:11:51 -080049import android.os.Bundle;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080050import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070051import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080052import android.os.IRemoteCallback;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080053import android.os.Looper;
54import android.os.Message;
Jeremy Joslina5172f62017-02-02 14:27:05 -080055import android.os.Process;
Jeremy Joslin998d7ca2016-12-28 15:56:46 -080056import android.os.RemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080057import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070058import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070059import android.os.UserHandle;
Jeremy Joslincb594f32017-01-03 17:31:23 -080060import android.provider.Settings;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080061import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080062import android.util.ArrayMap;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080063import android.util.ArraySet;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080065import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066import android.util.TimedRemoteCaller;
67
Jeff Davidson7842f642014-11-23 13:48:12 -080068import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080069import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070070import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070071import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080072
Jeff Davidson6a4b2202014-04-16 17:29:40 -070073import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070074import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070075import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070076import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080077import java.util.Collection;
78import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070079import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070080import java.util.Map;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080081import java.util.Set;
Jeremy Joslin145c3432016-12-09 13:11:51 -080082import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080083import java.util.concurrent.atomic.AtomicBoolean;
Jeremy Joslin3452b692017-01-17 15:48:13 -080084import java.util.concurrent.atomic.AtomicReference;
Jeremy Joslinba242732017-01-24 17:16:42 -080085import java.util.function.BiConsumer;
Jeremy Joslinba242732017-01-24 17:16:42 -080086import java.util.function.Supplier;
Jeremy Josline71fe2b2017-01-25 11:40:08 -080087import java.util.function.UnaryOperator;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070088
89/**
90 * Backing service for {@link android.net.NetworkScoreManager}.
91 * @hide
92 */
93public class NetworkScoreService extends INetworkScoreService.Stub {
94 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080095 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeremy Josline71fe2b2017-01-25 11:40:08 -080096 private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070097
Jeff Davidson6a4b2202014-04-16 17:29:40 -070098 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080099 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin3452b692017-01-17 15:48:13 -0800100 private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
Amin Shaikh972e2362016-12-07 14:08:09 -0800101 @GuardedBy("mScoreCaches")
102 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700103 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800104 private final Object mPackageMonitorLock = new Object();
105 private final Object mServiceConnectionLock = new Object();
106 private final Handler mHandler;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800107 private final DispatchingContentObserver mContentObserver;
Jeff Davidson7842f642014-11-23 13:48:12 -0800108
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700109 @GuardedBy("mPackageMonitorLock")
110 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800111 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700112 private ScoringServiceConnection mServiceConnection;
Jeremy Joslincb594f32017-01-03 17:31:23 -0800113 private volatile long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800114
Jeremy Joslin967b5812016-06-02 07:58:14 -0700115 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
116 @Override
117 public void onReceive(Context context, Intent intent) {
118 final String action = intent.getAction();
119 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
120 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
121 if (userId == UserHandle.USER_NULL) return;
122
123 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
124 onUserUnlocked(userId);
125 }
126 }
127 };
128
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700129 /**
130 * Clears scores when the active scorer package is no longer valid and
131 * manages the service connection.
132 */
133 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800134 final String mPackageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800135
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800136 private NetworkScorerPackageMonitor(String packageToWatch) {
137 mPackageToWatch = packageToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800138 }
139
140 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700141 public void onPackageAdded(String packageName, int uid) {
142 evaluateBinding(packageName, true /* forceUnbind */);
143 }
144
145 @Override
146 public void onPackageRemoved(String packageName, int uid) {
147 evaluateBinding(packageName, true /* forceUnbind */);
148 }
149
150 @Override
151 public void onPackageModified(String packageName) {
152 evaluateBinding(packageName, false /* forceUnbind */);
153 }
154
155 @Override
156 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
157 if (doit) { // "doit" means the force stop happened instead of just being queried for.
158 for (String packageName : packages) {
159 evaluateBinding(packageName, true /* forceUnbind */);
160 }
161 }
162 return super.onHandleForceStop(intent, packages, uid, doit);
163 }
164
165 @Override
166 public void onPackageUpdateFinished(String packageName, int uid) {
167 evaluateBinding(packageName, true /* forceUnbind */);
168 }
169
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800170 private void evaluateBinding(String changedPackageName, boolean forceUnbind) {
171 if (!mPackageToWatch.equals(changedPackageName)) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800172 // Early exit when we don't care about the package that has changed.
173 return;
174 }
175
176 if (DBG) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800177 Log.d(TAG, "Evaluating binding for: " + changedPackageName
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800178 + ", forceUnbind=" + forceUnbind);
179 }
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800180
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800181 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
182 if (activeScorer == null) {
183 // Package change has invalidated a scorer, this will also unbind any service
184 // connection.
185 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800186 refreshBinding();
187 } else { // The scoring service changed in some way.
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800188 if (forceUnbind) {
189 unbindFromScoringServiceIfNeeded();
190 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800191 if (DBG) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800192 Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent()
193 + " if needed.");
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800194 }
195 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800196 }
197 }
198 }
199
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800200 /**
Jeremy Joslincb594f32017-01-03 17:31:23 -0800201 * Dispatches observed content changes to a handler for further processing.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800202 */
Jeremy Joslincb594f32017-01-03 17:31:23 -0800203 @VisibleForTesting
204 public static class DispatchingContentObserver extends ContentObserver {
205 final private Map<Uri, Integer> mUriEventMap;
206 final private Context mContext;
207 final private Handler mHandler;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800208
Jeremy Joslincb594f32017-01-03 17:31:23 -0800209 public DispatchingContentObserver(Context context, Handler handler) {
210 super(handler);
211 mContext = context;
212 mHandler = handler;
213 mUriEventMap = new ArrayMap<>();
214 }
215
216 void observe(Uri uri, int what) {
217 mUriEventMap.put(uri, what);
218 final ContentResolver resolver = mContext.getContentResolver();
219 resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800220 }
221
222 @Override
223 public void onChange(boolean selfChange) {
224 onChange(selfChange, null);
225 }
226
227 @Override
228 public void onChange(boolean selfChange, Uri uri) {
229 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
Jeremy Joslincb594f32017-01-03 17:31:23 -0800230 final Integer what = mUriEventMap.get(uri);
231 if (what != null) {
232 mHandler.obtainMessage(what).sendToTarget();
233 } else {
234 Log.w(TAG, "No matching event to send for URI = " + uri);
235 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800236 }
237 }
238
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700239 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800240 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800241 }
242
243 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800244 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
245 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700246 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800247 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800248 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700249 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
250 // TODO: Need to update when we support per-user scorers. http://b/23422763
251 mContext.registerReceiverAsUser(
252 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
253 null /* scheduler */);
Jeremy Joslin3452b692017-01-17 15:48:13 -0800254 mReqRecommendationCallerRef = new AtomicReference<>(
255 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800256 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
257 mHandler = new ServiceHandler(looper);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800258 mContentObserver = new DispatchingContentObserver(context, mHandler);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700259 }
260
261 /** Called when the system is ready to run third-party code but before it actually does so. */
262 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700263 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslincb594f32017-01-03 17:31:23 -0800264 registerRecommendationSettingsObserver();
Jeff Davidson7842f642014-11-23 13:48:12 -0800265 }
266
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700267 /** Called when the system is ready for us to start third-party code. */
268 void systemRunning() {
269 if (DBG) Log.d(TAG, "systemRunning");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700270 }
271
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800272 @VisibleForTesting
273 void onUserUnlocked(int userId) {
274 if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")");
275 refreshBinding();
276 }
277
278 private void refreshBinding() {
279 if (DBG) Log.d(TAG, "refreshBinding()");
280 // Apply the default package name if the Setting isn't set.
281 mNetworkScorerAppManager.revertToDefaultIfNoActive();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700282 registerPackageMonitorIfNeeded();
283 bindToScoringServiceIfNeeded();
284 }
285
Jeremy Joslincb594f32017-01-03 17:31:23 -0800286 private void registerRecommendationSettingsObserver() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800287 final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE);
288 mContentObserver.observe(packageNameUri,
289 ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED);
Jeremy Joslincb594f32017-01-03 17:31:23 -0800290
291 final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
292 mContentObserver.observe(timeoutUri,
293 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800294 }
295
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800296 /**
297 * Ensures the package manager is registered to monitor the current active scorer.
298 * If a discrepancy is found any previous monitor will be cleaned up
299 * and a new monitor will be created.
300 *
301 * This method is idempotent.
302 */
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700303 private void registerPackageMonitorIfNeeded() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800304 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()");
305 final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700306 synchronized (mPackageMonitorLock) {
307 // Unregister the current monitor if needed.
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800308 if (mPackageMonitor != null && (appData == null
309 || !appData.getRecommendationServicePackageName().equals(
310 mPackageMonitor.mPackageToWatch))) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700311 if (DBG) {
312 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800313 + mPackageMonitor.mPackageToWatch);
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 Joslinee3fb5c2017-02-13 13:44:11 -0800319 // Create and register the monitor if a scorer is active.
320 if (appData != null && mPackageMonitor == null) {
321 mPackageMonitor = new NetworkScorerPackageMonitor(
322 appData.getRecommendationServicePackageName());
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700323 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700324 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
325 false /* externalStorage */);
326 if (DBG) {
327 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800328 + mPackageMonitor.mPackageToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800329 }
330 }
331 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700332 }
333
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700334 private void bindToScoringServiceIfNeeded() {
335 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800336 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700337 bindToScoringServiceIfNeeded(scorerData);
338 }
339
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800340 /**
341 * Ensures the service connection is bound to the current active scorer.
342 * If a discrepancy is found any previous connection will be cleaned up
343 * and a new connection will be created.
344 *
345 * This method is idempotent.
346 */
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800347 private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) {
348 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")");
349 if (appData != null) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800350 synchronized (mServiceConnectionLock) {
351 // If we're connected to a different component then drop it.
352 if (mServiceConnection != null
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800353 && !mServiceConnection.mAppData.equals(appData)) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800354 unbindFromScoringServiceIfNeeded();
355 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700356
Jeremy Joslin145c3432016-12-09 13:11:51 -0800357 // If we're not connected at all then create a new connection.
358 if (mServiceConnection == null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800359 mServiceConnection = new ScoringServiceConnection(appData);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800360 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700361
Jeremy Joslin145c3432016-12-09 13:11:51 -0800362 // Make sure the connection is connected (idempotent)
363 mServiceConnection.connect(mContext);
364 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700365 } else { // otherwise make sure it isn't bound.
366 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700367 }
368 }
369
370 private void unbindFromScoringServiceIfNeeded() {
371 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800372 synchronized (mServiceConnectionLock) {
373 if (mServiceConnection != null) {
374 mServiceConnection.disconnect(mContext);
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800375 if (DBG) Log.d(TAG, "Disconnected from: "
376 + mServiceConnection.mAppData.getRecommendationServiceComponent());
Jeremy Joslin145c3432016-12-09 13:11:51 -0800377 }
378 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700379 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800380 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700381 }
382
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700383 @Override
384 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800385 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700386 throw new SecurityException("Caller with UID " + getCallingUid() +
387 " is not the active scorer.");
388 }
389
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800390 final long token = Binder.clearCallingIdentity();
391 try {
392 // Separate networks by type.
393 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
394 for (ScoredNetwork network : networks) {
395 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
396 if (networkList == null) {
397 networkList = new ArrayList<>();
398 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800399 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800400 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800401 }
402
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800403 // Pass the scores of each type down to the appropriate network scorer.
404 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
405 final RemoteCallbackList<INetworkScoreCache> callbackList;
406 final boolean isEmpty;
407 synchronized (mScoreCaches) {
408 callbackList = mScoreCaches.get(entry.getKey());
409 isEmpty = callbackList == null
410 || callbackList.getRegisteredCallbackCount() == 0;
411 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800412
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800413 if (isEmpty) {
414 if (Log.isLoggable(TAG, Log.VERBOSE)) {
415 Log.v(TAG, "No scorer registered for type " + entry.getKey()
416 + ", discarding");
417 }
418 continue;
419 }
420
Jeremy Joslinba242732017-01-24 17:16:42 -0800421 final BiConsumer<INetworkScoreCache, Object> consumer =
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800422 FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
Jeremy Joslinba242732017-01-24 17:16:42 -0800423 entry.getKey());
424 sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800425 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700426
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800427 return true;
428 } finally {
429 Binder.restoreCallingIdentity(token);
430 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700431 }
432
Jeremy Joslinba242732017-01-24 17:16:42 -0800433 /**
434 * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
435 * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
436 * accepted {@link INetworkScoreCache} implementation.
437 */
438 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800439 static class FilteringCacheUpdatingConsumer
Jeremy Joslinba242732017-01-24 17:16:42 -0800440 implements BiConsumer<INetworkScoreCache, Object> {
441 private final Context mContext;
442 private final List<ScoredNetwork> mScoredNetworkList;
443 private final int mNetworkType;
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800444 // TODO: 1/23/17 - Consider a Map if we implement more filters.
445 // These are created on-demand to defer the construction cost until
446 // an instance is actually needed.
447 private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
448 private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
Jeremy Joslinba242732017-01-24 17:16:42 -0800449
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800450 static FilteringCacheUpdatingConsumer create(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800451 List<ScoredNetwork> scoredNetworkList, int networkType) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800452 return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
453 null, null);
Jeremy Joslinba242732017-01-24 17:16:42 -0800454 }
455
456 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800457 FilteringCacheUpdatingConsumer(Context context,
Jeremy Joslinba242732017-01-24 17:16:42 -0800458 List<ScoredNetwork> scoredNetworkList, int networkType,
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800459 UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
460 UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800461 mContext = context;
462 mScoredNetworkList = scoredNetworkList;
463 mNetworkType = networkType;
464 mCurrentNetworkFilter = currentNetworkFilter;
465 mScanResultsFilter = scanResultsFilter;
466 }
467
468 @Override
469 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
470 int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
471 if (cookie instanceof Integer) {
472 filterType = (Integer) cookie;
473 }
474
475 try {
476 final List<ScoredNetwork> filteredNetworkList =
477 filterScores(mScoredNetworkList, filterType);
478 if (!filteredNetworkList.isEmpty()) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800479 networkScoreCache.updateScores(filteredNetworkList);
Jeremy Joslinba242732017-01-24 17:16:42 -0800480 }
481 } catch (RemoteException e) {
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800482 if (VERBOSE) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800483 Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
484 }
485 }
486 }
487
488 /**
489 * Applies the appropriate filter and returns the filtered results.
490 */
491 private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
492 int filterType) {
493 switch (filterType) {
494 case NetworkScoreManager.CACHE_FILTER_NONE:
495 return scoredNetworkList;
496
497 case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
498 if (mCurrentNetworkFilter == null) {
499 mCurrentNetworkFilter =
500 new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
501 }
502 return mCurrentNetworkFilter.apply(scoredNetworkList);
503
504 case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
505 if (mScanResultsFilter == null) {
506 mScanResultsFilter = new ScanResultsScoreCacheFilter(
507 new ScanResultsSupplier(mContext));
508 }
509 return mScanResultsFilter.apply(scoredNetworkList);
510
511 default:
512 Log.w(TAG, "Unknown filter type: " + filterType);
513 return scoredNetworkList;
514 }
515 }
516 }
517
518 /**
519 * Helper class that improves the testability of the cache filter Functions.
520 */
521 private static class WifiInfoSupplier implements Supplier<WifiInfo> {
522 private final Context mContext;
523
524 WifiInfoSupplier(Context context) {
525 mContext = context;
526 }
527
528 @Override
529 public WifiInfo get() {
530 WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
531 if (wifiManager != null) {
532 return wifiManager.getConnectionInfo();
533 }
534 Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
535 return null;
536 }
537 }
538
539 /**
540 * Helper class that improves the testability of the cache filter Functions.
541 */
542 private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
543 private final Context mContext;
544
545 ScanResultsSupplier(Context context) {
546 mContext = context;
547 }
548
549 @Override
550 public List<ScanResult> get() {
551 WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
552 if (wifiScanner != null) {
553 return wifiScanner.getSingleScanResults();
554 }
555 Log.w(TAG, "WifiScanner is null, failed to return scan results.");
556 return Collections.emptyList();
557 }
558 }
559
560 /**
561 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
562 * {@link ScoredNetwork} associated with the current network. If no network is connected the
563 * returned list will be empty.
564 * <p>
565 * Note: this filter performs some internal caching for consistency and performance. The
566 * current network is determined at construction time and never changed. Also, the
567 * last filtered list is saved so if the same input is provided multiple times in a row
568 * the computation is only done once.
569 */
570 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800571 static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
Jeremy Joslinba242732017-01-24 17:16:42 -0800572 private final NetworkKey mCurrentNetwork;
Jeremy Joslinba242732017-01-24 17:16:42 -0800573
574 CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
575 mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
576 }
577
578 @Override
579 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
580 if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
581 return Collections.emptyList();
582 }
583
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800584 for (int i = 0; i < scoredNetworks.size(); i++) {
585 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
586 if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
587 return Collections.singletonList(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800588 }
589 }
590
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800591 return Collections.emptyList();
Jeremy Joslinba242732017-01-24 17:16:42 -0800592 }
593 }
594
595 /**
596 * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
597 * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
598 * If there are no {@link ScanResult}s the returned list will be empty.
599 * <p>
600 * Note: this filter performs some internal caching for consistency and performance. The
601 * current set of ScanResults is determined at construction time and never changed.
602 * Also, the last filtered list is saved so if the same input is provided multiple
603 * times in a row the computation is only done once.
604 */
605 @VisibleForTesting
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800606 static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
607 private final Set<NetworkKey> mScanResultKeys;
Jeremy Joslinba242732017-01-24 17:16:42 -0800608
609 ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800610 List<ScanResult> scanResults = resultsSupplier.get();
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800611 final int size = scanResults.size();
612 mScanResultKeys = new ArraySet<>(size);
613 for (int i = 0; i < size; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800614 ScanResult scanResult = scanResults.get(i);
Stephen Chenfde900d2017-02-14 16:40:21 -0800615 NetworkKey key = NetworkKey.createFromScanResult(scanResult);
616 if (key != null) {
617 mScanResultKeys.add(key);
618 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800619 }
620 }
621
622 @Override
623 public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
624 if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
625 return Collections.emptyList();
626 }
627
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800628 List<ScoredNetwork> filteredScores = new ArrayList<>();
629 for (int i = 0; i < scoredNetworks.size(); i++) {
630 final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
631 if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
632 filteredScores.add(scoredNetwork);
Jeremy Joslinba242732017-01-24 17:16:42 -0800633 }
Jeremy Joslinba242732017-01-24 17:16:42 -0800634 }
635
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800636 return filteredScores;
Jeremy Joslinba242732017-01-24 17:16:42 -0800637 }
638 }
639
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800640 private boolean callerCanRequestScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800641 // REQUEST_NETWORK_SCORES is a signature only permission.
642 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
643 PackageManager.PERMISSION_GRANTED;
644 }
645
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700646 @Override
647 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800648 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800649 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800650 final long token = Binder.clearCallingIdentity();
651 try {
652 clearInternal();
653 return true;
654 } finally {
655 Binder.restoreCallingIdentity(token);
656 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700657 } else {
658 throw new SecurityException(
659 "Caller is neither the active scorer nor the scorer manager.");
660 }
661 }
662
663 @Override
664 public boolean setActiveScorer(String packageName) {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800665 // Only the system can set the active scorer
666 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
667 return mNetworkScorerAppManager.setActiveScorer(packageName);
668 } else {
669 throw new SecurityException(
670 "Caller is neither the system process nor a score requester.");
671 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700672 }
673
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800674 /**
675 * Determine whether the application with the given UID is the enabled scorer.
676 *
677 * @param callingUid the UID to check
678 * @return true if the provided UID is the active scorer, false otherwise.
679 */
680 @Override
681 public boolean isCallerActiveScorer(int callingUid) {
682 synchronized (mServiceConnectionLock) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800683 return mServiceConnection != null
684 && mServiceConnection.mAppData.packageUid == callingUid;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800685 }
686 }
687
Jeremy Joslina5172f62017-02-02 14:27:05 -0800688 private boolean isCallerSystemProcess(int callingUid) {
689 return callingUid == Process.SYSTEM_UID;
690 }
691
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800692 /**
693 * Obtain the package name of the current active network scorer.
694 *
695 * @return the full package name of the current active scorer, or null if there is no active
696 * scorer.
697 */
698 @Override
699 public String getActiveScorerPackage() {
700 synchronized (mServiceConnectionLock) {
701 if (mServiceConnection != null) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800702 return mServiceConnection.getPackageName();
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800703 }
704 }
705 return null;
706 }
707
Jeremy Joslina5172f62017-02-02 14:27:05 -0800708 /**
709 * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
710 */
711 @Override
712 public NetworkScorerAppData getActiveScorer() {
713 // Only the system can access this data.
714 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
715 synchronized (mServiceConnectionLock) {
716 if (mServiceConnection != null) {
717 return mServiceConnection.mAppData;
718 }
719 }
720 } else {
721 throw new SecurityException(
722 "Caller is neither the system process nor a score requester.");
723 }
724
725 return null;
726 }
727
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800728 /**
729 * Returns the list of available scorer apps. The list will be empty if there are
730 * no valid scorers.
731 */
732 @Override
733 public List<NetworkScorerAppData> getAllValidScorers() {
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -0800734 // Only the system can access this data.
735 if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) {
736 return mNetworkScorerAppManager.getAllValidScorers();
737 } else {
738 throw new SecurityException(
739 "Caller is neither the system process nor a score requester.");
740 }
Jeremy Joslinf95c8652017-02-09 15:32:04 -0800741 }
742
Jeff Davidson26fd1432014-07-29 09:39:52 -0700743 @Override
744 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800745 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin35f34ea2017-02-03 09:13:51 -0800746 if (isCallerActiveScorer(getCallingUid()) || callerCanRequestScores()) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800747 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700748 } else {
749 throw new SecurityException(
750 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700751 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700752 }
753
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700754 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
755 private void clearInternal() {
Jeremy Joslinba242732017-01-24 17:16:42 -0800756 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800757 @Override
Jeremy Joslinba242732017-01-24 17:16:42 -0800758 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800759 try {
760 networkScoreCache.clearScores();
761 } catch (RemoteException e) {
762 if (Log.isLoggable(TAG, Log.VERBOSE)) {
763 Log.v(TAG, "Unable to clear scores", e);
764 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700765 }
766 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800767 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700768 }
769
770 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800771 public void registerNetworkScoreCache(int networkType,
772 INetworkScoreCache scoreCache,
773 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800774 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800775 final long token = Binder.clearCallingIdentity();
776 try {
777 synchronized (mScoreCaches) {
778 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
779 if (callbackList == null) {
780 callbackList = new RemoteCallbackList<>();
781 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800782 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800783 if (!callbackList.register(scoreCache, filterType)) {
784 if (callbackList.getRegisteredCallbackCount() == 0) {
785 mScoreCaches.remove(networkType);
786 }
787 if (Log.isLoggable(TAG, Log.VERBOSE)) {
788 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
789 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800790 }
791 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800792 } finally {
793 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800794 }
795 }
796
797 @Override
798 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800799 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800800 final long token = Binder.clearCallingIdentity();
801 try {
802 synchronized (mScoreCaches) {
803 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
804 if (callbackList == null || !callbackList.unregister(scoreCache)) {
805 if (Log.isLoggable(TAG, Log.VERBOSE)) {
806 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
807 + networkType);
808 }
809 } else if (callbackList.getRegisteredCallbackCount() == 0) {
810 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800811 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800812 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800813 } finally {
814 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700815 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700816 }
817
818 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800819 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800820 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800821 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800822 final long token = Binder.clearCallingIdentity();
823 try {
824 final INetworkRecommendationProvider provider = getRecommendationProvider();
825 if (provider != null) {
826 try {
Jeremy Joslin3452b692017-01-17 15:48:13 -0800827 final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
828 return caller.getRecommendationResult(provider, request);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800829 } catch (RemoteException | TimeoutException e) {
830 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800831 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800832 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800833 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800834
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800835 if (DBG) {
836 Log.d(TAG, "Returning the default network recommendation.");
837 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800838
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800839 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800840 return RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -0800841 request.getDefaultWifiConfig());
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800842 }
843 return RecommendationResult.createDoNotConnectRecommendation();
844 } finally {
845 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800846 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800847 }
848
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800849 /**
850 * Request a recommendation for the best network to connect to
851 * taking into account the inputs from the {@link RecommendationRequest}.
852 *
853 * @param request a {@link RecommendationRequest} instance containing the details of the request
854 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
855 * is available.
856 * @throws SecurityException if the caller is not the system
857 */
858 @Override
859 public void requestRecommendationAsync(RecommendationRequest request,
860 RemoteCallback remoteCallback) {
Jeremy Joslin6397ab52017-01-18 15:12:01 -0800861 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800862
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800863 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
864 final Pair<RecommendationRequest, OneTimeCallback> pair =
865 Pair.create(request, oneTimeCallback);
866 final Message timeoutMsg = mHandler.obtainMessage(
867 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
868 final INetworkRecommendationProvider provider = getRecommendationProvider();
869 final long token = Binder.clearCallingIdentity();
870 try {
871 if (provider != null) {
872 try {
873 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
874 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
875 @Override
876 public void sendResult(Bundle data) throws RemoteException {
877 // Remove the timeout message
878 mHandler.removeMessages(timeoutMsg.what, pair);
879 oneTimeCallback.sendResult(data);
880 }
881 }, 0 /*sequence*/);
882 return;
883 } catch (RemoteException e) {
884 Log.w(TAG, "Failed to request a recommendation.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800885 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800886 // Remove the timeout message
887 mHandler.removeMessages(timeoutMsg.what, pair);
888 // Will fall through and send back the default recommendation.
889 }
890 }
891 } finally {
892 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800893 }
894
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800895 // Else send back the default recommendation.
896 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800897 }
898
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800899 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800900 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800901 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800902 final long token = Binder.clearCallingIdentity();
903 try {
904 final INetworkRecommendationProvider provider = getRecommendationProvider();
905 if (provider != null) {
906 try {
907 provider.requestScores(networks);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800908 // TODO: 12/15/16 - Consider pushing null scores into the cache to
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800909 // prevent repeated requests for the same scores.
910 return true;
911 } catch (RemoteException e) {
912 Log.w(TAG, "Failed to request scores.", e);
Jeremy Josline71fe2b2017-01-25 11:40:08 -0800913 // TODO: 12/15/16 - Keep track of failures.
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800914 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800915 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800916 return false;
917 } finally {
918 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800919 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800920 }
921
922 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800923 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700924 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800925 final long token = Binder.clearCallingIdentity();
926 try {
927 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
928 if (currentScorer == null) {
929 writer.println("Scoring is disabled.");
930 return;
931 }
Jeremy Joslin37e877b2017-02-02 11:06:14 -0800932 writer.println("Current scorer: " + currentScorer);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -0800933 writer.println("RecommendationRequestTimeoutMs: " + mRecommendationRequestTimeoutMs);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700934
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800935 sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
936 @Override
937 public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
938 try {
939 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
940 } catch (IOException | RemoteException e) {
941 writer.println("Failed to dump score cache: " + e);
942 }
943 }
944 }, getScoreCacheLists());
945
946 synchronized (mServiceConnectionLock) {
947 if (mServiceConnection != null) {
948 mServiceConnection.dump(fd, writer, args);
949 } else {
950 writer.println("ScoringServiceConnection: null");
Amin Shaikh972e2362016-12-07 14:08:09 -0800951 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700952 }
Jeremy Joslin534b6cf2017-01-25 18:20:21 -0800953 writer.flush();
954 } finally {
955 Binder.restoreCallingIdentity(token);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700956 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700957 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700958
959 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800960 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700961 *
962 * <p>May be used to perform an action on all score caches without potentially strange behavior
963 * if a new scorer is registered during that action's execution.
964 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800965 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700966 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800967 return new ArrayList<>(mScoreCaches.values());
968 }
969 }
970
Jeremy Joslinba242732017-01-24 17:16:42 -0800971 private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Amin Shaikh972e2362016-12-07 14:08:09 -0800972 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
973 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
974 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
975 final int count = callbackList.beginBroadcast();
976 try {
977 for (int i = 0; i < count; i++) {
Jeremy Joslinba242732017-01-24 17:16:42 -0800978 consumer.accept(callbackList.getBroadcastItem(i),
Jeremy Joslin7890e192017-02-06 11:14:34 -0800979 callbackList.getBroadcastCookie(i));
Amin Shaikh972e2362016-12-07 14:08:09 -0800980 }
981 } finally {
982 callbackList.finishBroadcast();
983 }
984 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700985 }
986 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700987
Jeremy Joslin145c3432016-12-09 13:11:51 -0800988 private void throwIfCalledOnMainThread() {
989 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
990 throw new RuntimeException("Cannot invoke on the main thread");
991 }
992 }
993
994 @Nullable
995 private INetworkRecommendationProvider getRecommendationProvider() {
996 synchronized (mServiceConnectionLock) {
997 if (mServiceConnection != null) {
998 return mServiceConnection.getRecommendationProvider();
999 }
1000 }
1001 return null;
1002 }
1003
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001004 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001005 public void refreshRecommendationRequestTimeoutMs() {
1006 final ContentResolver cr = mContext.getContentResolver();
1007 long timeoutMs = Settings.Global.getLong(cr,
1008 Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
1009 if (timeoutMs < 0) {
1010 timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
1011 }
1012 if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
1013 mRecommendationRequestTimeoutMs = timeoutMs;
Jeremy Joslin3452b692017-01-17 15:48:13 -08001014 mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001015 }
1016
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001017 private static class ScoringServiceConnection implements ServiceConnection {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001018 private final NetworkScorerAppData mAppData;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001019 private volatile boolean mBound = false;
1020 private volatile boolean mConnected = false;
1021 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001022
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001023 ScoringServiceConnection(NetworkScorerAppData appData) {
1024 mAppData = appData;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001025 }
1026
1027 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001028 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -08001029 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001030 service.setComponent(mAppData.getRecommendationServiceComponent());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001031 mBound = context.bindServiceAsUser(service, this,
1032 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
1033 UserHandle.SYSTEM);
1034 if (!mBound) {
1035 Log.w(TAG, "Bind call failed for " + service);
1036 } else {
1037 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
1038 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001039 }
1040 }
1041
1042 void disconnect(Context context) {
1043 try {
1044 if (mBound) {
1045 mBound = false;
1046 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001047 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001048 }
1049 } catch (RuntimeException e) {
1050 Log.e(TAG, "Unbind failed.", e);
1051 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001052
1053 mRecommendationProvider = null;
1054 }
1055
1056 INetworkRecommendationProvider getRecommendationProvider() {
1057 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001058 }
1059
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001060 String getPackageName() {
1061 return mAppData.getRecommendationServiceComponent().getPackageName();
1062 }
1063
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001064 @Override
1065 public void onServiceConnected(ComponentName name, IBinder service) {
1066 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001067 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001068 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001069 }
1070
1071 @Override
1072 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001073 if (DBG) {
1074 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
1075 }
1076 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -08001077 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001078 }
1079
1080 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin37e877b2017-02-02 11:06:14 -08001081 writer.println("ScoringServiceConnection: "
1082 + mAppData.getRecommendationServiceComponent()
1083 + ", bound: " + mBound
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -07001084 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -07001085 }
1086 }
Jeremy Joslin145c3432016-12-09 13:11:51 -08001087
1088 /**
1089 * Executes the async requestRecommendation() call with a timeout.
1090 */
1091 private static final class RequestRecommendationCaller
1092 extends TimedRemoteCaller<RecommendationResult> {
1093 private final IRemoteCallback mCallback;
1094
1095 RequestRecommendationCaller(long callTimeoutMillis) {
1096 super(callTimeoutMillis);
1097 mCallback = new IRemoteCallback.Stub() {
1098 @Override
1099 public void sendResult(Bundle data) throws RemoteException {
1100 final RecommendationResult result =
1101 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
1102 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001103 if (VERBOSE) Log.v(TAG, "callback received for sequence " + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001104 onRemoteMethodResult(result, sequence);
1105 }
1106 };
1107 }
1108
1109 /**
1110 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
1111 * instance.
1112 *
1113 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
1114 * from
1115 * @param request the {@link RecommendationRequest} from the calling client
1116 * @return a {@link RecommendationResult} from the provider
1117 * @throws RemoteException if the call failed
1118 * @throws TimeoutException if the call took longer than the set timeout
1119 */
1120 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
1121 RecommendationRequest request) throws RemoteException, TimeoutException {
1122 final int sequence = onBeforeRemoteCall();
Jeremy Joslin4ad7ca02017-02-13 11:35:03 -08001123 if (VERBOSE) Log.v(TAG, "getRecommendationResult() seq=" + sequence);
Jeremy Joslin145c3432016-12-09 13:11:51 -08001124 target.requestRecommendation(request, mCallback, sequence);
1125 return getResultTimed(sequence);
1126 }
1127 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001128
1129 /**
1130 * A wrapper around {@link RemoteCallback} that guarantees
1131 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
1132 */
1133 @VisibleForTesting
1134 public static final class OneTimeCallback {
1135 private final RemoteCallback mRemoteCallback;
1136 private final AtomicBoolean mCallbackRun;
1137
1138 public OneTimeCallback(RemoteCallback remoteCallback) {
1139 mRemoteCallback = remoteCallback;
1140 mCallbackRun = new AtomicBoolean(false);
1141 }
1142
1143 public void sendResult(Bundle data) {
1144 if (mCallbackRun.compareAndSet(false, true)) {
1145 mRemoteCallback.sendResult(data);
1146 }
1147 }
1148 }
1149
1150 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
1151 OneTimeCallback remoteCallback) {
1152 if (DBG) {
1153 Log.d(TAG, "Returning the default network recommendation.");
1154 }
1155
1156 final RecommendationResult result;
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001157 if (request != null && request.getDefaultWifiConfig() != null) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001158 result = RecommendationResult.createConnectRecommendation(
Jeremy Joslin26a45e52017-01-18 11:55:17 -08001159 request.getDefaultWifiConfig());
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001160 } else {
1161 result = RecommendationResult.createDoNotConnectRecommendation();
1162 }
1163
1164 final Bundle data = new Bundle();
1165 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
1166 remoteCallback.sendResult(data);
1167 }
1168
1169 @VisibleForTesting
Jeremy Joslincb594f32017-01-03 17:31:23 -08001170 public final class ServiceHandler extends Handler {
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001171 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001172 public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 2;
Jeremy Joslincb594f32017-01-03 17:31:23 -08001173 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001174
1175 public ServiceHandler(Looper looper) {
1176 super(looper);
1177 }
1178
1179 @Override
1180 public void handleMessage(Message msg) {
1181 final int what = msg.what;
1182 switch (what) {
1183 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
1184 if (DBG) {
1185 Log.d(TAG, "Network recommendation request timed out.");
1186 }
1187 final Pair<RecommendationRequest, OneTimeCallback> pair =
1188 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
1189 final RecommendationRequest request = pair.first;
1190 final OneTimeCallback remoteCallback = pair.second;
1191 sendDefaultRecommendationResponse(request, remoteCallback);
1192 break;
1193
Jeremy Joslinee3fb5c2017-02-13 13:44:11 -08001194 case MSG_RECOMMENDATIONS_PACKAGE_CHANGED:
1195 refreshBinding();
Jeremy Joslincb594f32017-01-03 17:31:23 -08001196 break;
1197
1198 case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
1199 refreshRecommendationRequestTimeoutMs();
1200 break;
1201
Jeremy Joslince73c6f2016-12-29 14:49:38 -08001202 default:
1203 Log.w(TAG,"Unknown message: " + what);
1204 }
1205 }
1206 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001207}