blob: 1b1b2060f739c2d1eed542d03fb7917908ec8fa8 [file] [log] [blame]
Jeff Davidson6a4b2202014-04-16 17:29:40 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server;
18
Jeremy Joslin145c3432016-12-09 13:11:51 -080019import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
20import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
21
Jeff Davidson6a4b2202014-04-16 17:29:40 -070022import android.Manifest.permission;
Jeremy Joslin145c3432016-12-09 13:11:51 -080023import android.annotation.Nullable;
Jeremy Joslin967b5812016-06-02 07:58:14 -070024import android.content.BroadcastReceiver;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070025import android.content.ComponentName;
Jeff Davidson56f9f732014-08-14 16:47:23 -070026import android.content.ContentResolver;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027import android.content.Context;
Jeff Davidsonb096bdc2014-07-01 12:29:11 -070028import android.content.Intent;
Jeremy Joslin967b5812016-06-02 07:58:14 -070029import android.content.IntentFilter;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070030import android.content.ServiceConnection;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070031import android.content.pm.PackageManager;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080032import android.database.ContentObserver;
Jeremy Joslin145c3432016-12-09 13:11:51 -080033import android.net.INetworkRecommendationProvider;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070034import android.net.INetworkScoreCache;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070035import android.net.INetworkScoreService;
Jeremy Joslinb2087a12016-12-13 16:11:51 -080036import android.net.NetworkKey;
Jeremy Joslin5519d7c2017-01-06 14:36:54 -080037import android.net.NetworkScoreManager;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070038import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070039import android.net.NetworkScorerAppManager.NetworkScorerAppData;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080040import android.net.RecommendationRequest;
41import android.net.RecommendationResult;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070042import android.net.ScoredNetwork;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080043import android.net.Uri;
Jeremy Joslin8f5521a2016-12-20 14:36:20 -080044import android.os.Binder;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080045import android.os.Build;
Jeremy Joslin145c3432016-12-09 13:11:51 -080046import android.os.Bundle;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080047import android.os.Handler;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070048import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080049import android.os.IRemoteCallback;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080050import android.os.Looper;
51import android.os.Message;
Jeremy Joslin998d7ca2016-12-28 15:56:46 -080052import android.os.RemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080053import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070054import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070055import android.os.UserHandle;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080056import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080057import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070058import android.util.Log;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080059import android.util.Pair;
Jeremy Joslin145c3432016-12-09 13:11:51 -080060import android.util.TimedRemoteCaller;
61
Jeff Davidson7842f642014-11-23 13:48:12 -080062import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080063import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070064import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070065import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080066
Jeff Davidson6a4b2202014-04-16 17:29:40 -070067import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070068import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070069import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070070import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080071import java.util.Collection;
72import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070073import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070074import java.util.Map;
Jeremy Joslin145c3432016-12-09 13:11:51 -080075import java.util.concurrent.TimeoutException;
Jeremy Joslince73c6f2016-12-29 14:49:38 -080076import java.util.concurrent.atomic.AtomicBoolean;
Amin Shaikh972e2362016-12-07 14:08:09 -080077import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070078
79/**
80 * Backing service for {@link android.net.NetworkScoreManager}.
81 * @hide
82 */
83public class NetworkScoreService extends INetworkScoreService.Stub {
84 private static final String TAG = "NetworkScoreService";
Jeremy Joslince73c6f2016-12-29 14:49:38 -080085 private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070086
Jeff Davidson6a4b2202014-04-16 17:29:40 -070087 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080088 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin145c3432016-12-09 13:11:51 -080089 private final RequestRecommendationCaller mRequestRecommendationCaller;
Amin Shaikh972e2362016-12-07 14:08:09 -080090 @GuardedBy("mScoreCaches")
91 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070092 /** Lock used to update mPackageMonitor when scorer package changes occur. */
Jeremy Joslince73c6f2016-12-29 14:49:38 -080093 private final Object mPackageMonitorLock = new Object();
94 private final Object mServiceConnectionLock = new Object();
95 private final Handler mHandler;
Jeff Davidson7842f642014-11-23 13:48:12 -080096
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070097 @GuardedBy("mPackageMonitorLock")
98 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -080099 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700100 private ScoringServiceConnection mServiceConnection;
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800101 private long mRecommendationRequestTimeoutMs;
Jeff Davidson7842f642014-11-23 13:48:12 -0800102
Jeremy Joslin967b5812016-06-02 07:58:14 -0700103 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
104 @Override
105 public void onReceive(Context context, Intent intent) {
106 final String action = intent.getAction();
107 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
108 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
109 if (userId == UserHandle.USER_NULL) return;
110
111 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
112 onUserUnlocked(userId);
113 }
114 }
115 };
116
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700117 /**
118 * Clears scores when the active scorer package is no longer valid and
119 * manages the service connection.
120 */
121 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800122 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800123
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800124 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
125 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800126 }
127
128 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700129 public void onPackageAdded(String packageName, int uid) {
130 evaluateBinding(packageName, true /* forceUnbind */);
131 }
132
133 @Override
134 public void onPackageRemoved(String packageName, int uid) {
135 evaluateBinding(packageName, true /* forceUnbind */);
136 }
137
138 @Override
139 public void onPackageModified(String packageName) {
140 evaluateBinding(packageName, false /* forceUnbind */);
141 }
142
143 @Override
144 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
145 if (doit) { // "doit" means the force stop happened instead of just being queried for.
146 for (String packageName : packages) {
147 evaluateBinding(packageName, true /* forceUnbind */);
148 }
149 }
150 return super.onHandleForceStop(intent, packages, uid, doit);
151 }
152
153 @Override
154 public void onPackageUpdateFinished(String packageName, int uid) {
155 evaluateBinding(packageName, true /* forceUnbind */);
156 }
157
158 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800159 if (!mPackagesToWatch.contains(scorerPackageName)) {
160 // Early exit when we don't care about the package that has changed.
161 return;
162 }
163
164 if (DBG) {
165 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
166 + ", forceUnbind=" + forceUnbind);
167 }
168 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
169 if (activeScorer == null) {
170 // Package change has invalidated a scorer, this will also unbind any service
171 // connection.
172 if (DBG) Log.d(TAG, "No active scorers available.");
173 unbindFromScoringServiceIfNeeded();
174 } else if (activeScorer.packageName.equals(scorerPackageName)) {
175 // The active scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700176 if (DBG) {
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800177 Log.d(TAG, "Possible change to the active scorer: "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800178 + activeScorer.packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700179 }
Jeremy Joslin86c2a5e2016-12-21 13:35:02 -0800180 if (forceUnbind) {
181 unbindFromScoringServiceIfNeeded();
182 }
183 bindToScoringServiceIfNeeded(activeScorer);
184 } else {
185 // One of the scoring apps on the device has changed and we may no longer be
186 // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
187 // will sort that out to leave us bound to the most recent active scorer.
188 if (DBG) {
189 Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
190 }
191 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800192 }
193 }
194 }
195
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800196 /**
197 * Reevaluates the service binding when the Settings toggle is changed.
198 */
199 private class SettingsObserver extends ContentObserver {
200
201 public SettingsObserver() {
202 super(null /*handler*/);
203 }
204
205 @Override
206 public void onChange(boolean selfChange) {
207 onChange(selfChange, null);
208 }
209
210 @Override
211 public void onChange(boolean selfChange, Uri uri) {
212 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
213 bindToScoringServiceIfNeeded();
214 }
215 }
216
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700217 public NetworkScoreService(Context context) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800218 this(context, new NetworkScorerAppManager(context), Looper.myLooper());
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800219 }
220
221 @VisibleForTesting
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800222 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
223 Looper looper) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700224 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800225 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800226 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700227 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
228 // TODO: Need to update when we support per-user scorers. http://b/23422763
229 mContext.registerReceiverAsUser(
230 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
231 null /* scheduler */);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800232 // TODO(jjoslin): 12/15/16 - Make timeout configurable.
233 mRequestRecommendationCaller =
234 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800235 mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
236 mHandler = new ServiceHandler(looper);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700237 }
238
239 /** Called when the system is ready to run third-party code but before it actually does so. */
240 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700241 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700242 registerPackageMonitorIfNeeded();
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800243 registerRecommendationSettingObserverIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800244 }
245
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700246 /** Called when the system is ready for us to start third-party code. */
247 void systemRunning() {
248 if (DBG) Log.d(TAG, "systemRunning");
249 bindToScoringServiceIfNeeded();
250 }
251
Jeremy Joslin967b5812016-06-02 07:58:14 -0700252 private void onUserUnlocked(int userId) {
253 registerPackageMonitorIfNeeded();
254 bindToScoringServiceIfNeeded();
255 }
256
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800257 private void registerRecommendationSettingObserverIfNeeded() {
258 final List<String> providerPackages =
259 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
260 if (!providerPackages.isEmpty()) {
261 final ContentResolver resolver = mContext.getContentResolver();
262 final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
263 resolver.registerContentObserver(uri, false, new SettingsObserver());
264 }
265 }
266
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700267 private void registerPackageMonitorIfNeeded() {
268 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800269 final List<String> providerPackages =
270 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700271 synchronized (mPackageMonitorLock) {
272 // Unregister the current monitor if needed.
273 if (mPackageMonitor != null) {
274 if (DBG) {
275 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800276 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800277 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700278 mPackageMonitor.unregister();
279 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800280 }
281
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800282 // Create and register the monitor if there are packages that could be providers.
283 if (!providerPackages.isEmpty()) {
284 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700285 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700286 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
287 false /* externalStorage */);
288 if (DBG) {
289 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800290 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800291 }
292 }
293 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700294 }
295
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700296 private void bindToScoringServiceIfNeeded() {
297 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800298 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700299 bindToScoringServiceIfNeeded(scorerData);
300 }
301
302 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
303 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800304 if (scorerData != null && scorerData.recommendationServiceClassName != null) {
305 ComponentName componentName = new ComponentName(scorerData.packageName,
306 scorerData.recommendationServiceClassName);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800307 synchronized (mServiceConnectionLock) {
308 // If we're connected to a different component then drop it.
309 if (mServiceConnection != null
310 && !mServiceConnection.mComponentName.equals(componentName)) {
311 unbindFromScoringServiceIfNeeded();
312 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700313
Jeremy Joslin145c3432016-12-09 13:11:51 -0800314 // If we're not connected at all then create a new connection.
315 if (mServiceConnection == null) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800316 mServiceConnection = new ScoringServiceConnection(componentName,
317 scorerData.packageUid);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800318 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700319
Jeremy Joslin145c3432016-12-09 13:11:51 -0800320 // Make sure the connection is connected (idempotent)
321 mServiceConnection.connect(mContext);
322 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700323 } else { // otherwise make sure it isn't bound.
324 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700325 }
326 }
327
328 private void unbindFromScoringServiceIfNeeded() {
329 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800330 synchronized (mServiceConnectionLock) {
331 if (mServiceConnection != null) {
332 mServiceConnection.disconnect(mContext);
333 }
334 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700335 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800336 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700337 }
338
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700339 @Override
340 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800341 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700342 throw new SecurityException("Caller with UID " + getCallingUid() +
343 " is not the active scorer.");
344 }
345
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800346 final long token = Binder.clearCallingIdentity();
347 try {
348 // Separate networks by type.
349 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
350 for (ScoredNetwork network : networks) {
351 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
352 if (networkList == null) {
353 networkList = new ArrayList<>();
354 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800355 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800356 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800357 }
358
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800359 // Pass the scores of each type down to the appropriate network scorer.
360 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
361 final RemoteCallbackList<INetworkScoreCache> callbackList;
362 final boolean isEmpty;
363 synchronized (mScoreCaches) {
364 callbackList = mScoreCaches.get(entry.getKey());
365 isEmpty = callbackList == null
366 || callbackList.getRegisteredCallbackCount() == 0;
367 }
368 if (isEmpty) {
369 if (Log.isLoggable(TAG, Log.VERBOSE)) {
370 Log.v(TAG, "No scorer registered for type " + entry.getKey()
371 + ", discarding");
372 }
373 continue;
374 }
375
376 sendCallback(new Consumer<INetworkScoreCache>() {
377 @Override
378 public void accept(INetworkScoreCache networkScoreCache) {
379 try {
380 networkScoreCache.updateScores(entry.getValue());
381 } catch (RemoteException e) {
382 if (Log.isLoggable(TAG, Log.VERBOSE)) {
383 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
384 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800385 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700386 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800387 }, Collections.singleton(callbackList));
388 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700389
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800390 return true;
391 } finally {
392 Binder.restoreCallingIdentity(token);
393 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700394 }
395
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800396 private boolean isCallerSystemUid() {
397 // REQUEST_NETWORK_SCORES is a signature only permission.
398 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
399 PackageManager.PERMISSION_GRANTED;
400 }
401
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700402 @Override
403 public boolean clearScores() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800404 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800405 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800406 final long token = Binder.clearCallingIdentity();
407 try {
408 clearInternal();
409 return true;
410 } finally {
411 Binder.restoreCallingIdentity(token);
412 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700413 } else {
414 throw new SecurityException(
415 "Caller is neither the active scorer nor the scorer manager.");
416 }
417 }
418
419 @Override
420 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800421 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
422 // to directly set the scorer app rather than having to use the consent dialog. The
423 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
424 // do the right thing and not enable this feature without explaining it to the user.
425 // In the future, should this API be opened to 3p apps, we will need to lock this down and
426 // figure out another way to streamline the UX.
427
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800428 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
429
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800430 // Scorers (recommendation providers) are selected and no longer set.
431 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700432 }
433
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800434 /**
435 * Determine whether the application with the given UID is the enabled scorer.
436 *
437 * @param callingUid the UID to check
438 * @return true if the provided UID is the active scorer, false otherwise.
439 */
440 @Override
441 public boolean isCallerActiveScorer(int callingUid) {
442 synchronized (mServiceConnectionLock) {
443 return mServiceConnection != null && mServiceConnection.mScoringAppUid == callingUid;
444 }
445 }
446
Jeremy Joslin6c1ca282017-01-10 13:08:32 -0800447 /**
448 * Obtain the package name of the current active network scorer.
449 *
450 * @return the full package name of the current active scorer, or null if there is no active
451 * scorer.
452 */
453 @Override
454 public String getActiveScorerPackage() {
455 synchronized (mServiceConnectionLock) {
456 if (mServiceConnection != null) {
457 return mServiceConnection.mComponentName.getPackageName();
458 }
459 }
460 return null;
461 }
462
Jeff Davidson26fd1432014-07-29 09:39:52 -0700463 @Override
464 public void disableScoring() {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800465 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800466 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800467 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700468 } else {
469 throw new SecurityException(
470 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700471 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700472 }
473
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700474 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
475 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800476 sendCallback(new Consumer<INetworkScoreCache>() {
477 @Override
478 public void accept(INetworkScoreCache networkScoreCache) {
479 try {
480 networkScoreCache.clearScores();
481 } catch (RemoteException e) {
482 if (Log.isLoggable(TAG, Log.VERBOSE)) {
483 Log.v(TAG, "Unable to clear scores", e);
484 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700485 }
486 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800487 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700488 }
489
490 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800491 public void registerNetworkScoreCache(int networkType,
492 INetworkScoreCache scoreCache,
493 int filterType) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800494 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800495 final long token = Binder.clearCallingIdentity();
496 try {
497 synchronized (mScoreCaches) {
498 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
499 if (callbackList == null) {
500 callbackList = new RemoteCallbackList<>();
501 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800502 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800503 if (!callbackList.register(scoreCache, filterType)) {
504 if (callbackList.getRegisteredCallbackCount() == 0) {
505 mScoreCaches.remove(networkType);
506 }
507 if (Log.isLoggable(TAG, Log.VERBOSE)) {
508 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
509 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800510 }
511 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800512 } finally {
513 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800514 }
515 }
516
517 @Override
518 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800519 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800520 final long token = Binder.clearCallingIdentity();
521 try {
522 synchronized (mScoreCaches) {
523 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
524 if (callbackList == null || !callbackList.unregister(scoreCache)) {
525 if (Log.isLoggable(TAG, Log.VERBOSE)) {
526 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
527 + networkType);
528 }
529 } else if (callbackList.getRegisteredCallbackCount() == 0) {
530 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800531 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800532 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800533 } finally {
534 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700535 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700536 }
537
538 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800539 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800540 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800541 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800542 final long token = Binder.clearCallingIdentity();
543 try {
544 final INetworkRecommendationProvider provider = getRecommendationProvider();
545 if (provider != null) {
546 try {
547 return mRequestRecommendationCaller.getRecommendationResult(provider, request);
548 } catch (RemoteException | TimeoutException e) {
549 Log.w(TAG, "Failed to request a recommendation.", e);
550 // TODO(jjoslin): 12/15/16 - Keep track of failures.
551 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800552 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800553
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800554 if (DBG) {
555 Log.d(TAG, "Returning the default network recommendation.");
556 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800557
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800558 if (request != null && request.getCurrentSelectedConfig() != null) {
559 return RecommendationResult.createConnectRecommendation(
560 request.getCurrentSelectedConfig());
561 }
562 return RecommendationResult.createDoNotConnectRecommendation();
563 } finally {
564 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800565 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800566 }
567
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800568 /**
569 * Request a recommendation for the best network to connect to
570 * taking into account the inputs from the {@link RecommendationRequest}.
571 *
572 * @param request a {@link RecommendationRequest} instance containing the details of the request
573 * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
574 * is available.
575 * @throws SecurityException if the caller is not the system
576 */
577 @Override
578 public void requestRecommendationAsync(RecommendationRequest request,
579 RemoteCallback remoteCallback) {
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800580 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800581
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800582 final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
583 final Pair<RecommendationRequest, OneTimeCallback> pair =
584 Pair.create(request, oneTimeCallback);
585 final Message timeoutMsg = mHandler.obtainMessage(
586 ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
587 final INetworkRecommendationProvider provider = getRecommendationProvider();
588 final long token = Binder.clearCallingIdentity();
589 try {
590 if (provider != null) {
591 try {
592 mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
593 provider.requestRecommendation(request, new IRemoteCallback.Stub() {
594 @Override
595 public void sendResult(Bundle data) throws RemoteException {
596 // Remove the timeout message
597 mHandler.removeMessages(timeoutMsg.what, pair);
598 oneTimeCallback.sendResult(data);
599 }
600 }, 0 /*sequence*/);
601 return;
602 } catch (RemoteException e) {
603 Log.w(TAG, "Failed to request a recommendation.", e);
604 // TODO(jjoslin): 12/15/16 - Keep track of failures.
605 // Remove the timeout message
606 mHandler.removeMessages(timeoutMsg.what, pair);
607 // Will fall through and send back the default recommendation.
608 }
609 }
610 } finally {
611 Binder.restoreCallingIdentity(token);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800612 }
613
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800614 // Else send back the default recommendation.
615 sendDefaultRecommendationResponse(request, oneTimeCallback);
Jeremy Joslin998d7ca2016-12-28 15:56:46 -0800616 }
617
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800618 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800619 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin5519d7c2017-01-06 14:36:54 -0800620 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800621 final long token = Binder.clearCallingIdentity();
622 try {
623 final INetworkRecommendationProvider provider = getRecommendationProvider();
624 if (provider != null) {
625 try {
626 provider.requestScores(networks);
627 // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
628 // prevent repeated requests for the same scores.
629 return true;
630 } catch (RemoteException e) {
631 Log.w(TAG, "Failed to request scores.", e);
632 // TODO(jjoslin): 12/15/16 - Keep track of failures.
633 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800634 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800635 return false;
636 } finally {
637 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800638 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800639 }
640
641 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800642 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700643 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800644 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700645 if (currentScorer == null) {
646 writer.println("Scoring is disabled.");
647 return;
648 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800649 writer.println("Current scorer: " + currentScorer.packageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700650
Amin Shaikh972e2362016-12-07 14:08:09 -0800651 sendCallback(new Consumer<INetworkScoreCache>() {
652 @Override
653 public void accept(INetworkScoreCache networkScoreCache) {
654 try {
655 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
656 } catch (IOException | RemoteException e) {
657 writer.println("Failed to dump score cache: " + e);
658 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700659 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800660 }, getScoreCacheLists());
661
Jeremy Joslin145c3432016-12-09 13:11:51 -0800662 synchronized (mServiceConnectionLock) {
663 if (mServiceConnection != null) {
664 mServiceConnection.dump(fd, writer, args);
665 } else {
666 writer.println("ScoringServiceConnection: null");
667 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700668 }
669 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700670 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700671
672 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800673 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700674 *
675 * <p>May be used to perform an action on all score caches without potentially strange behavior
676 * if a new scorer is registered during that action's execution.
677 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800678 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700679 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800680 return new ArrayList<>(mScoreCaches.values());
681 }
682 }
683
684 private void sendCallback(Consumer<INetworkScoreCache> consumer,
685 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
686 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
687 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
688 final int count = callbackList.beginBroadcast();
689 try {
690 for (int i = 0; i < count; i++) {
691 consumer.accept(callbackList.getBroadcastItem(i));
692 }
693 } finally {
694 callbackList.finishBroadcast();
695 }
696 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700697 }
698 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700699
Jeremy Joslin145c3432016-12-09 13:11:51 -0800700 private void throwIfCalledOnMainThread() {
701 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
702 throw new RuntimeException("Cannot invoke on the main thread");
703 }
704 }
705
706 @Nullable
707 private INetworkRecommendationProvider getRecommendationProvider() {
708 synchronized (mServiceConnectionLock) {
709 if (mServiceConnection != null) {
710 return mServiceConnection.getRecommendationProvider();
711 }
712 }
713 return null;
714 }
715
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800716 @VisibleForTesting
717 public void setRecommendationRequestTimeoutMs(long recommendationRequestTimeoutMs) {
718 mRecommendationRequestTimeoutMs = recommendationRequestTimeoutMs;
719 }
720
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700721 private static class ScoringServiceConnection implements ServiceConnection {
722 private final ComponentName mComponentName;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800723 private final int mScoringAppUid;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800724 private volatile boolean mBound = false;
725 private volatile boolean mConnected = false;
726 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700727
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800728 ScoringServiceConnection(ComponentName componentName, int scoringAppUid) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700729 mComponentName = componentName;
Jeremy Joslin134c9d32017-01-09 16:22:20 -0800730 mScoringAppUid = scoringAppUid;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700731 }
732
733 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700734 if (!mBound) {
Joe LaPenna25e7ec22016-12-27 14:50:14 -0800735 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700736 service.setComponent(mComponentName);
737 mBound = context.bindServiceAsUser(service, this,
738 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
739 UserHandle.SYSTEM);
740 if (!mBound) {
741 Log.w(TAG, "Bind call failed for " + service);
742 } else {
743 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
744 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700745 }
746 }
747
748 void disconnect(Context context) {
749 try {
750 if (mBound) {
751 mBound = false;
752 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700753 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700754 }
755 } catch (RuntimeException e) {
756 Log.e(TAG, "Unbind failed.", e);
757 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800758
759 mRecommendationProvider = null;
760 }
761
762 INetworkRecommendationProvider getRecommendationProvider() {
763 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700764 }
765
766 @Override
767 public void onServiceConnected(ComponentName name, IBinder service) {
768 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700769 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800770 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700771 }
772
773 @Override
774 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700775 if (DBG) {
776 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
777 }
778 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800779 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700780 }
781
782 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700783 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
784 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700785 }
786 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800787
788 /**
789 * Executes the async requestRecommendation() call with a timeout.
790 */
791 private static final class RequestRecommendationCaller
792 extends TimedRemoteCaller<RecommendationResult> {
793 private final IRemoteCallback mCallback;
794
795 RequestRecommendationCaller(long callTimeoutMillis) {
796 super(callTimeoutMillis);
797 mCallback = new IRemoteCallback.Stub() {
798 @Override
799 public void sendResult(Bundle data) throws RemoteException {
800 final RecommendationResult result =
801 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
802 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
803 onRemoteMethodResult(result, sequence);
804 }
805 };
806 }
807
808 /**
809 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
810 * instance.
811 *
812 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
813 * from
814 * @param request the {@link RecommendationRequest} from the calling client
815 * @return a {@link RecommendationResult} from the provider
816 * @throws RemoteException if the call failed
817 * @throws TimeoutException if the call took longer than the set timeout
818 */
819 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
820 RecommendationRequest request) throws RemoteException, TimeoutException {
821 final int sequence = onBeforeRemoteCall();
822 target.requestRecommendation(request, mCallback, sequence);
823 return getResultTimed(sequence);
824 }
825 }
Jeremy Joslince73c6f2016-12-29 14:49:38 -0800826
827 /**
828 * A wrapper around {@link RemoteCallback} that guarantees
829 * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
830 */
831 @VisibleForTesting
832 public static final class OneTimeCallback {
833 private final RemoteCallback mRemoteCallback;
834 private final AtomicBoolean mCallbackRun;
835
836 public OneTimeCallback(RemoteCallback remoteCallback) {
837 mRemoteCallback = remoteCallback;
838 mCallbackRun = new AtomicBoolean(false);
839 }
840
841 public void sendResult(Bundle data) {
842 if (mCallbackRun.compareAndSet(false, true)) {
843 mRemoteCallback.sendResult(data);
844 }
845 }
846 }
847
848 private static void sendDefaultRecommendationResponse(RecommendationRequest request,
849 OneTimeCallback remoteCallback) {
850 if (DBG) {
851 Log.d(TAG, "Returning the default network recommendation.");
852 }
853
854 final RecommendationResult result;
855 if (request != null && request.getCurrentSelectedConfig() != null) {
856 result = RecommendationResult.createConnectRecommendation(
857 request.getCurrentSelectedConfig());
858 } else {
859 result = RecommendationResult.createDoNotConnectRecommendation();
860 }
861
862 final Bundle data = new Bundle();
863 data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
864 remoteCallback.sendResult(data);
865 }
866
867 @VisibleForTesting
868 public static final class ServiceHandler extends Handler {
869 public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
870
871 public ServiceHandler(Looper looper) {
872 super(looper);
873 }
874
875 @Override
876 public void handleMessage(Message msg) {
877 final int what = msg.what;
878 switch (what) {
879 case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
880 if (DBG) {
881 Log.d(TAG, "Network recommendation request timed out.");
882 }
883 final Pair<RecommendationRequest, OneTimeCallback> pair =
884 (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
885 final RecommendationRequest request = pair.first;
886 final OneTimeCallback remoteCallback = pair.second;
887 sendDefaultRecommendationResponse(request, remoteCallback);
888 break;
889
890 default:
891 Log.w(TAG,"Unknown message: " + what);
892 }
893 }
894 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700895}