blob: e23844c3e633b488d4175489a885729ab6019990 [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 Joslin36d4c482016-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 Joslin36d4c482016-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 Joslinb8418ac82016-12-06 07:42:38 -080032import android.database.ContentObserver;
Jeremy Joslin36d4c482016-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 Josline7f273d2016-12-13 16:11:51 -080036import android.net.NetworkKey;
Jeremy Joslin186f3332017-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 Joslinb8418ac82016-12-06 07:42:38 -080043import android.net.Uri;
Jeremy Joslin29ed4a92016-12-20 14:36:20 -080044import android.os.Binder;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080045import android.os.Bundle;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070046import android.os.IBinder;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080047import android.os.IRemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080048import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070049import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070050import android.os.UserHandle;
Jeremy Joslinb8418ac82016-12-06 07:42:38 -080051import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080052import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070053import android.util.Log;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080054import android.util.TimedRemoteCaller;
55
Jeff Davidson7842f642014-11-23 13:48:12 -080056import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080057import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070058import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070059import com.android.internal.os.TransferPipe;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080060
Jeff Davidson6a4b2202014-04-16 17:29:40 -070061import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070062import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070063import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070064import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080065import java.util.Collection;
66import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070067import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070068import java.util.Map;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080069import java.util.concurrent.TimeoutException;
Amin Shaikh972e2362016-12-07 14:08:09 -080070import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070071
72/**
73 * Backing service for {@link android.net.NetworkScoreManager}.
74 * @hide
75 */
76public class NetworkScoreService extends INetworkScoreService.Stub {
77 private static final String TAG = "NetworkScoreService";
Jeremy Joslin36d4c482016-12-09 13:11:51 -080078 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070079
Jeff Davidson6a4b2202014-04-16 17:29:40 -070080 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080081 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080082 private final RequestRecommendationCaller mRequestRecommendationCaller;
Amin Shaikh972e2362016-12-07 14:08:09 -080083 @GuardedBy("mScoreCaches")
84 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070085 /** Lock used to update mPackageMonitor when scorer package changes occur. */
86 private final Object mPackageMonitorLock = new Object[0];
Jeremy Joslin36d4c482016-12-09 13:11:51 -080087 private final Object mServiceConnectionLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080088
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070089 @GuardedBy("mPackageMonitorLock")
90 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin36d4c482016-12-09 13:11:51 -080091 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -070092 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080093
Jeremy Joslin967b5812016-06-02 07:58:14 -070094 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
95 @Override
96 public void onReceive(Context context, Intent intent) {
97 final String action = intent.getAction();
98 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
99 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
100 if (userId == UserHandle.USER_NULL) return;
101
102 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
103 onUserUnlocked(userId);
104 }
105 }
106 };
107
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700108 /**
109 * Clears scores when the active scorer package is no longer valid and
110 * manages the service connection.
111 */
112 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800113 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800114
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800115 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
116 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800117 }
118
119 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700120 public void onPackageAdded(String packageName, int uid) {
121 evaluateBinding(packageName, true /* forceUnbind */);
122 }
123
124 @Override
125 public void onPackageRemoved(String packageName, int uid) {
126 evaluateBinding(packageName, true /* forceUnbind */);
127 }
128
129 @Override
130 public void onPackageModified(String packageName) {
131 evaluateBinding(packageName, false /* forceUnbind */);
132 }
133
134 @Override
135 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
136 if (doit) { // "doit" means the force stop happened instead of just being queried for.
137 for (String packageName : packages) {
138 evaluateBinding(packageName, true /* forceUnbind */);
139 }
140 }
141 return super.onHandleForceStop(intent, packages, uid, doit);
142 }
143
144 @Override
145 public void onPackageUpdateFinished(String packageName, int uid) {
146 evaluateBinding(packageName, true /* forceUnbind */);
147 }
148
149 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Josline3d260d2016-12-21 13:35:02 -0800150 if (!mPackagesToWatch.contains(scorerPackageName)) {
151 // Early exit when we don't care about the package that has changed.
152 return;
153 }
154
155 if (DBG) {
156 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
157 + ", forceUnbind=" + forceUnbind);
158 }
159 final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
160 if (activeScorer == null) {
161 // Package change has invalidated a scorer, this will also unbind any service
162 // connection.
163 if (DBG) Log.d(TAG, "No active scorers available.");
164 unbindFromScoringServiceIfNeeded();
165 } else if (activeScorer.packageName.equals(scorerPackageName)) {
166 // The active scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700167 if (DBG) {
Jeremy Josline3d260d2016-12-21 13:35:02 -0800168 Log.d(TAG, "Possible change to the active scorer: "
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800169 + activeScorer.packageName);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700170 }
Jeremy Josline3d260d2016-12-21 13:35:02 -0800171 if (forceUnbind) {
172 unbindFromScoringServiceIfNeeded();
173 }
174 bindToScoringServiceIfNeeded(activeScorer);
175 } else {
176 // One of the scoring apps on the device has changed and we may no longer be
177 // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
178 // will sort that out to leave us bound to the most recent active scorer.
179 if (DBG) {
180 Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
181 }
182 bindToScoringServiceIfNeeded(activeScorer);
Jeff Davidson7842f642014-11-23 13:48:12 -0800183 }
184 }
185 }
186
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800187 /**
188 * Reevaluates the service binding when the Settings toggle is changed.
189 */
190 private class SettingsObserver extends ContentObserver {
191
192 public SettingsObserver() {
193 super(null /*handler*/);
194 }
195
196 @Override
197 public void onChange(boolean selfChange) {
198 onChange(selfChange, null);
199 }
200
201 @Override
202 public void onChange(boolean selfChange, Uri uri) {
203 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
204 bindToScoringServiceIfNeeded();
205 }
206 }
207
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700208 public NetworkScoreService(Context context) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800209 this(context, new NetworkScorerAppManager(context));
210 }
211
212 @VisibleForTesting
213 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700214 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800215 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800216 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700217 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
218 // TODO: Need to update when we support per-user scorers. http://b/23422763
219 mContext.registerReceiverAsUser(
220 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
221 null /* scheduler */);
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800222 // TODO(jjoslin): 12/15/16 - Make timeout configurable.
223 mRequestRecommendationCaller =
224 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700225 }
226
227 /** Called when the system is ready to run third-party code but before it actually does so. */
228 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700229 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700230 registerPackageMonitorIfNeeded();
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800231 registerRecommendationSettingObserverIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800232 }
233
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700234 /** Called when the system is ready for us to start third-party code. */
235 void systemRunning() {
236 if (DBG) Log.d(TAG, "systemRunning");
237 bindToScoringServiceIfNeeded();
238 }
239
Jeremy Joslin967b5812016-06-02 07:58:14 -0700240 private void onUserUnlocked(int userId) {
241 registerPackageMonitorIfNeeded();
242 bindToScoringServiceIfNeeded();
243 }
244
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800245 private void registerRecommendationSettingObserverIfNeeded() {
246 final List<String> providerPackages =
247 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
248 if (!providerPackages.isEmpty()) {
249 final ContentResolver resolver = mContext.getContentResolver();
250 final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
251 resolver.registerContentObserver(uri, false, new SettingsObserver());
252 }
253 }
254
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700255 private void registerPackageMonitorIfNeeded() {
256 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800257 final List<String> providerPackages =
258 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700259 synchronized (mPackageMonitorLock) {
260 // Unregister the current monitor if needed.
261 if (mPackageMonitor != null) {
262 if (DBG) {
263 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800264 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800265 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700266 mPackageMonitor.unregister();
267 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800268 }
269
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800270 // Create and register the monitor if there are packages that could be providers.
271 if (!providerPackages.isEmpty()) {
272 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700273 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700274 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
275 false /* externalStorage */);
276 if (DBG) {
277 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800278 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800279 }
280 }
281 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700282 }
283
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700284 private void bindToScoringServiceIfNeeded() {
285 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800286 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700287 bindToScoringServiceIfNeeded(scorerData);
288 }
289
290 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
291 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800292 if (scorerData != null && scorerData.recommendationServiceClassName != null) {
293 ComponentName componentName = new ComponentName(scorerData.packageName,
294 scorerData.recommendationServiceClassName);
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800295 synchronized (mServiceConnectionLock) {
296 // If we're connected to a different component then drop it.
297 if (mServiceConnection != null
298 && !mServiceConnection.mComponentName.equals(componentName)) {
299 unbindFromScoringServiceIfNeeded();
300 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700301
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800302 // If we're not connected at all then create a new connection.
303 if (mServiceConnection == null) {
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800304 mServiceConnection = new ScoringServiceConnection(componentName,
305 scorerData.packageUid);
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800306 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700307
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800308 // Make sure the connection is connected (idempotent)
309 mServiceConnection.connect(mContext);
310 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700311 } else { // otherwise make sure it isn't bound.
312 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700313 }
314 }
315
316 private void unbindFromScoringServiceIfNeeded() {
317 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800318 synchronized (mServiceConnectionLock) {
319 if (mServiceConnection != null) {
320 mServiceConnection.disconnect(mContext);
321 }
322 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700323 }
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800324 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700325 }
326
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700327 @Override
328 public boolean updateScores(ScoredNetwork[] networks) {
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800329 if (!isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700330 throw new SecurityException("Caller with UID " + getCallingUid() +
331 " is not the active scorer.");
332 }
333
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800334 final long token = Binder.clearCallingIdentity();
335 try {
336 // Separate networks by type.
337 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
338 for (ScoredNetwork network : networks) {
339 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
340 if (networkList == null) {
341 networkList = new ArrayList<>();
342 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800343 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800344 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800345 }
346
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800347 // Pass the scores of each type down to the appropriate network scorer.
348 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
349 final RemoteCallbackList<INetworkScoreCache> callbackList;
350 final boolean isEmpty;
351 synchronized (mScoreCaches) {
352 callbackList = mScoreCaches.get(entry.getKey());
353 isEmpty = callbackList == null
354 || callbackList.getRegisteredCallbackCount() == 0;
355 }
356 if (isEmpty) {
357 if (Log.isLoggable(TAG, Log.VERBOSE)) {
358 Log.v(TAG, "No scorer registered for type " + entry.getKey()
359 + ", discarding");
360 }
361 continue;
362 }
363
364 sendCallback(new Consumer<INetworkScoreCache>() {
365 @Override
366 public void accept(INetworkScoreCache networkScoreCache) {
367 try {
368 networkScoreCache.updateScores(entry.getValue());
369 } catch (RemoteException e) {
370 if (Log.isLoggable(TAG, Log.VERBOSE)) {
371 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
372 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800373 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700374 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800375 }, Collections.singleton(callbackList));
376 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700377
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800378 return true;
379 } finally {
380 Binder.restoreCallingIdentity(token);
381 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700382 }
383
Jeremy Joslin186f3332017-01-06 14:36:54 -0800384 private boolean isCallerSystemUid() {
385 // REQUEST_NETWORK_SCORES is a signature only permission.
386 return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
387 PackageManager.PERMISSION_GRANTED;
388 }
389
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700390 @Override
391 public boolean clearScores() {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800392 // Only the active scorer or the system should be allowed to flush all scores.
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800393 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800394 final long token = Binder.clearCallingIdentity();
395 try {
396 clearInternal();
397 return true;
398 } finally {
399 Binder.restoreCallingIdentity(token);
400 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700401 } else {
402 throw new SecurityException(
403 "Caller is neither the active scorer nor the scorer manager.");
404 }
405 }
406
407 @Override
408 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800409 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
410 // to directly set the scorer app rather than having to use the consent dialog. The
411 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
412 // do the right thing and not enable this feature without explaining it to the user.
413 // In the future, should this API be opened to 3p apps, we will need to lock this down and
414 // figure out another way to streamline the UX.
415
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800416 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
417
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800418 // Scorers (recommendation providers) are selected and no longer set.
419 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700420 }
421
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800422 /**
423 * Determine whether the application with the given UID is the enabled scorer.
424 *
425 * @param callingUid the UID to check
426 * @return true if the provided UID is the active scorer, false otherwise.
427 */
428 @Override
429 public boolean isCallerActiveScorer(int callingUid) {
430 synchronized (mServiceConnectionLock) {
431 return mServiceConnection != null && mServiceConnection.mScoringAppUid == callingUid;
432 }
433 }
434
Jeremy Joslind7670f62017-01-10 13:08:32 -0800435 /**
436 * Obtain the package name of the current active network scorer.
437 *
438 * @return the full package name of the current active scorer, or null if there is no active
439 * scorer.
440 */
441 @Override
442 public String getActiveScorerPackage() {
443 synchronized (mServiceConnectionLock) {
444 if (mServiceConnection != null) {
445 return mServiceConnection.mComponentName.getPackageName();
446 }
447 }
448 return null;
449 }
450
Jeff Davidson26fd1432014-07-29 09:39:52 -0700451 @Override
452 public void disableScoring() {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800453 // Only the active scorer or the system should be allowed to disable scoring.
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800454 if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) {
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800455 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700456 } else {
457 throw new SecurityException(
458 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700459 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700460 }
461
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700462 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
463 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800464 sendCallback(new Consumer<INetworkScoreCache>() {
465 @Override
466 public void accept(INetworkScoreCache networkScoreCache) {
467 try {
468 networkScoreCache.clearScores();
469 } catch (RemoteException e) {
470 if (Log.isLoggable(TAG, Log.VERBOSE)) {
471 Log.v(TAG, "Unable to clear scores", e);
472 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700473 }
474 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800475 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700476 }
477
478 @Override
Jeremy Joslin823db052016-11-30 15:05:40 -0800479 public void registerNetworkScoreCache(int networkType,
480 INetworkScoreCache scoreCache,
481 int filterType) {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800482 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800483 final long token = Binder.clearCallingIdentity();
484 try {
485 synchronized (mScoreCaches) {
486 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
487 if (callbackList == null) {
488 callbackList = new RemoteCallbackList<>();
489 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800490 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800491 if (!callbackList.register(scoreCache, filterType)) {
492 if (callbackList.getRegisteredCallbackCount() == 0) {
493 mScoreCaches.remove(networkType);
494 }
495 if (Log.isLoggable(TAG, Log.VERBOSE)) {
496 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
497 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800498 }
499 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800500 } finally {
501 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800502 }
503 }
504
505 @Override
506 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800507 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800508 final long token = Binder.clearCallingIdentity();
509 try {
510 synchronized (mScoreCaches) {
511 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
512 if (callbackList == null || !callbackList.unregister(scoreCache)) {
513 if (Log.isLoggable(TAG, Log.VERBOSE)) {
514 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
515 + networkType);
516 }
517 } else if (callbackList.getRegisteredCallbackCount() == 0) {
518 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800519 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800520 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800521 } finally {
522 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700523 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700524 }
525
526 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800527 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800528 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800529 throwIfCalledOnMainThread();
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800530 final long token = Binder.clearCallingIdentity();
531 try {
532 final INetworkRecommendationProvider provider = getRecommendationProvider();
533 if (provider != null) {
534 try {
535 return mRequestRecommendationCaller.getRecommendationResult(provider, request);
536 } catch (RemoteException | TimeoutException e) {
537 Log.w(TAG, "Failed to request a recommendation.", e);
538 // TODO(jjoslin): 12/15/16 - Keep track of failures.
539 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800540 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800541
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800542 if (DBG) {
543 Log.d(TAG, "Returning the default network recommendation.");
544 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800545
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800546 if (request != null && request.getCurrentSelectedConfig() != null) {
547 return RecommendationResult.createConnectRecommendation(
548 request.getCurrentSelectedConfig());
549 }
550 return RecommendationResult.createDoNotConnectRecommendation();
551 } finally {
552 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800553 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800554 }
555
556 @Override
Jeremy Josline7f273d2016-12-13 16:11:51 -0800557 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin186f3332017-01-06 14:36:54 -0800558 mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800559 final long token = Binder.clearCallingIdentity();
560 try {
561 final INetworkRecommendationProvider provider = getRecommendationProvider();
562 if (provider != null) {
563 try {
564 provider.requestScores(networks);
565 // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
566 // prevent repeated requests for the same scores.
567 return true;
568 } catch (RemoteException e) {
569 Log.w(TAG, "Failed to request scores.", e);
570 // TODO(jjoslin): 12/15/16 - Keep track of failures.
571 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800572 }
Jeremy Joslin29ed4a92016-12-20 14:36:20 -0800573 return false;
574 } finally {
575 Binder.restoreCallingIdentity(token);
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800576 }
Jeremy Josline7f273d2016-12-13 16:11:51 -0800577 }
578
579 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800580 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700581 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800582 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700583 if (currentScorer == null) {
584 writer.println("Scoring is disabled.");
585 return;
586 }
Jeremy Joslinb8418ac82016-12-06 07:42:38 -0800587 writer.println("Current scorer: " + currentScorer.packageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700588
Amin Shaikh972e2362016-12-07 14:08:09 -0800589 sendCallback(new Consumer<INetworkScoreCache>() {
590 @Override
591 public void accept(INetworkScoreCache networkScoreCache) {
592 try {
593 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
594 } catch (IOException | RemoteException e) {
595 writer.println("Failed to dump score cache: " + e);
596 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700597 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800598 }, getScoreCacheLists());
599
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800600 synchronized (mServiceConnectionLock) {
601 if (mServiceConnection != null) {
602 mServiceConnection.dump(fd, writer, args);
603 } else {
604 writer.println("ScoringServiceConnection: null");
605 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700606 }
607 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700608 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700609
610 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800611 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700612 *
613 * <p>May be used to perform an action on all score caches without potentially strange behavior
614 * if a new scorer is registered during that action's execution.
615 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800616 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700617 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800618 return new ArrayList<>(mScoreCaches.values());
619 }
620 }
621
622 private void sendCallback(Consumer<INetworkScoreCache> consumer,
623 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
624 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
625 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
626 final int count = callbackList.beginBroadcast();
627 try {
628 for (int i = 0; i < count; i++) {
629 consumer.accept(callbackList.getBroadcastItem(i));
630 }
631 } finally {
632 callbackList.finishBroadcast();
633 }
634 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700635 }
636 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700637
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800638 private void throwIfCalledOnMainThread() {
639 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
640 throw new RuntimeException("Cannot invoke on the main thread");
641 }
642 }
643
644 @Nullable
645 private INetworkRecommendationProvider getRecommendationProvider() {
646 synchronized (mServiceConnectionLock) {
647 if (mServiceConnection != null) {
648 return mServiceConnection.getRecommendationProvider();
649 }
650 }
651 return null;
652 }
653
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700654 private static class ScoringServiceConnection implements ServiceConnection {
655 private final ComponentName mComponentName;
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800656 private final int mScoringAppUid;
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800657 private volatile boolean mBound = false;
658 private volatile boolean mConnected = false;
659 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700660
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800661 ScoringServiceConnection(ComponentName componentName, int scoringAppUid) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700662 mComponentName = componentName;
Jeremy Joslin9b442fa2017-01-09 16:22:20 -0800663 mScoringAppUid = scoringAppUid;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700664 }
665
666 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700667 if (!mBound) {
Joe LaPenna9bc5eff2016-12-27 14:50:14 -0800668 Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700669 service.setComponent(mComponentName);
670 mBound = context.bindServiceAsUser(service, this,
671 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
672 UserHandle.SYSTEM);
673 if (!mBound) {
674 Log.w(TAG, "Bind call failed for " + service);
675 } else {
676 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
677 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700678 }
679 }
680
681 void disconnect(Context context) {
682 try {
683 if (mBound) {
684 mBound = false;
685 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700686 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700687 }
688 } catch (RuntimeException e) {
689 Log.e(TAG, "Unbind failed.", e);
690 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800691
692 mRecommendationProvider = null;
693 }
694
695 INetworkRecommendationProvider getRecommendationProvider() {
696 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700697 }
698
699 @Override
700 public void onServiceConnected(ComponentName name, IBinder service) {
701 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700702 mConnected = true;
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800703 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700704 }
705
706 @Override
707 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700708 if (DBG) {
709 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
710 }
711 mConnected = false;
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800712 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700713 }
714
715 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700716 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
717 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700718 }
719 }
Jeremy Joslin36d4c482016-12-09 13:11:51 -0800720
721 /**
722 * Executes the async requestRecommendation() call with a timeout.
723 */
724 private static final class RequestRecommendationCaller
725 extends TimedRemoteCaller<RecommendationResult> {
726 private final IRemoteCallback mCallback;
727
728 RequestRecommendationCaller(long callTimeoutMillis) {
729 super(callTimeoutMillis);
730 mCallback = new IRemoteCallback.Stub() {
731 @Override
732 public void sendResult(Bundle data) throws RemoteException {
733 final RecommendationResult result =
734 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
735 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
736 onRemoteMethodResult(result, sequence);
737 }
738 };
739 }
740
741 /**
742 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
743 * instance.
744 *
745 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
746 * from
747 * @param request the {@link RecommendationRequest} from the calling client
748 * @return a {@link RecommendationResult} from the provider
749 * @throws RemoteException if the call failed
750 * @throws TimeoutException if the call took longer than the set timeout
751 */
752 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
753 RecommendationRequest request) throws RemoteException, TimeoutException {
754 final int sequence = onBeforeRemoteCall();
755 target.requestRecommendation(request, mCallback, sequence);
756 return getResultTimed(sequence);
757 }
758 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700759}