blob: c64aa8e13aa93cfbcd193b1085920c439235a9fb [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;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070037import android.net.NetworkScorerAppManager;
Jeff Davidsonc7415532014-06-23 18:15:34 -070038import android.net.NetworkScorerAppManager.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 Joslin8f5521a2016-12-20 14:36:20 -080043import android.os.Binder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080044import android.os.Bundle;
Jeremy Joslindd251ef2016-03-14 11:17:41 -070045import android.os.IBinder;
Jeremy Joslin145c3432016-12-09 13:11:51 -080046import android.os.IRemoteCallback;
Amin Shaikh972e2362016-12-07 14:08:09 -080047import android.os.RemoteCallbackList;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070048import android.os.RemoteException;
Jeff Davidsonac7285d2014-08-08 15:12:47 -070049import android.os.UserHandle;
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -080050import android.provider.Settings.Global;
Amin Shaikh972e2362016-12-07 14:08:09 -080051import android.util.ArrayMap;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070052import android.util.Log;
Jeremy Joslin145c3432016-12-09 13:11:51 -080053import android.util.TimedRemoteCaller;
54
Jeff Davidson7842f642014-11-23 13:48:12 -080055import com.android.internal.annotations.GuardedBy;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080056import com.android.internal.annotations.VisibleForTesting;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070057import com.android.internal.content.PackageMonitor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070058import com.android.internal.os.TransferPipe;
Jeremy Joslin145c3432016-12-09 13:11:51 -080059
Jeff Davidson6a4b2202014-04-16 17:29:40 -070060import java.io.FileDescriptor;
Jeff Sharkeyba6f8c82016-11-09 12:25:44 -070061import java.io.IOException;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070062import java.io.PrintWriter;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070063import java.util.ArrayList;
Amin Shaikh972e2362016-12-07 14:08:09 -080064import java.util.Collection;
65import java.util.Collections;
Jeff Davidson14f1ec02014-04-29 11:58:26 -070066import java.util.List;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070067import java.util.Map;
Jeremy Joslin145c3432016-12-09 13:11:51 -080068import java.util.concurrent.TimeoutException;
Amin Shaikh972e2362016-12-07 14:08:09 -080069import java.util.function.Consumer;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070070
71/**
72 * Backing service for {@link android.net.NetworkScoreManager}.
73 * @hide
74 */
75public class NetworkScoreService extends INetworkScoreService.Stub {
76 private static final String TAG = "NetworkScoreService";
Jeremy Joslin145c3432016-12-09 13:11:51 -080077 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Jeff Davidson6a4b2202014-04-16 17:29:40 -070078
Jeff Davidson6a4b2202014-04-16 17:29:40 -070079 private final Context mContext;
Amin Shaikhaa09aa02016-11-21 17:27:53 -080080 private final NetworkScorerAppManager mNetworkScorerAppManager;
Jeremy Joslin145c3432016-12-09 13:11:51 -080081 private final RequestRecommendationCaller mRequestRecommendationCaller;
Amin Shaikh972e2362016-12-07 14:08:09 -080082 @GuardedBy("mScoreCaches")
83 private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070084 /** Lock used to update mPackageMonitor when scorer package changes occur. */
85 private final Object mPackageMonitorLock = new Object[0];
Jeremy Joslin145c3432016-12-09 13:11:51 -080086 private final Object mServiceConnectionLock = new Object[0];
Jeff Davidson7842f642014-11-23 13:48:12 -080087
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -070088 @GuardedBy("mPackageMonitorLock")
89 private NetworkScorerPackageMonitor mPackageMonitor;
Jeremy Joslin145c3432016-12-09 13:11:51 -080090 @GuardedBy("mServiceConnectionLock")
Jeremy Joslindd251ef2016-03-14 11:17:41 -070091 private ScoringServiceConnection mServiceConnection;
Jeff Davidson7842f642014-11-23 13:48:12 -080092
Jeremy Joslin967b5812016-06-02 07:58:14 -070093 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
94 @Override
95 public void onReceive(Context context, Intent intent) {
96 final String action = intent.getAction();
97 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
98 if (DBG) Log.d(TAG, "Received " + action + " for userId " + userId);
99 if (userId == UserHandle.USER_NULL) return;
100
101 if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
102 onUserUnlocked(userId);
103 }
104 }
105 };
106
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700107 /**
108 * Clears scores when the active scorer package is no longer valid and
109 * manages the service connection.
110 */
111 private class NetworkScorerPackageMonitor extends PackageMonitor {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800112 final List<String> mPackagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800113
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800114 private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
115 mPackagesToWatch = packagesToWatch;
Jeff Davidson7842f642014-11-23 13:48:12 -0800116 }
117
118 @Override
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700119 public void onPackageAdded(String packageName, int uid) {
120 evaluateBinding(packageName, true /* forceUnbind */);
121 }
122
123 @Override
124 public void onPackageRemoved(String packageName, int uid) {
125 evaluateBinding(packageName, true /* forceUnbind */);
126 }
127
128 @Override
129 public void onPackageModified(String packageName) {
130 evaluateBinding(packageName, false /* forceUnbind */);
131 }
132
133 @Override
134 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
135 if (doit) { // "doit" means the force stop happened instead of just being queried for.
136 for (String packageName : packages) {
137 evaluateBinding(packageName, true /* forceUnbind */);
138 }
139 }
140 return super.onHandleForceStop(intent, packages, uid, doit);
141 }
142
143 @Override
144 public void onPackageUpdateFinished(String packageName, int uid) {
145 evaluateBinding(packageName, true /* forceUnbind */);
146 }
147
148 private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800149 if (mPackagesToWatch.contains(scorerPackageName)) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700150 if (DBG) {
151 Log.d(TAG, "Evaluating binding for: " + scorerPackageName
152 + ", forceUnbind=" + forceUnbind);
153 }
154 final NetworkScorerAppData activeScorer =
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800155 mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700156 if (activeScorer == null) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700157 // Package change has invalidated a scorer, this will also unbind any service
158 // connection.
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800159 if (DBG) Log.d(TAG, "No active scorers available.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700160 unbindFromScoringServiceIfNeeded();
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800161 } else if (activeScorer.packageName.equals(scorerPackageName)) {
162 if (DBG) {
163 Log.d(TAG, "Possible change to the active scorer: "
164 + activeScorer.packageName);
165 }
166 // The scoring service changed in some way.
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700167 if (forceUnbind) {
168 unbindFromScoringServiceIfNeeded();
169 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700170 bindToScoringServiceIfNeeded(activeScorer);
171 }
Jeff Davidson7842f642014-11-23 13:48:12 -0800172 }
173 }
174 }
175
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800176 /**
177 * Reevaluates the service binding when the Settings toggle is changed.
178 */
179 private class SettingsObserver extends ContentObserver {
180
181 public SettingsObserver() {
182 super(null /*handler*/);
183 }
184
185 @Override
186 public void onChange(boolean selfChange) {
187 onChange(selfChange, null);
188 }
189
190 @Override
191 public void onChange(boolean selfChange, Uri uri) {
192 if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
193 bindToScoringServiceIfNeeded();
194 }
195 }
196
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700197 public NetworkScoreService(Context context) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800198 this(context, new NetworkScorerAppManager(context));
199 }
200
201 @VisibleForTesting
202 NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700203 mContext = context;
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800204 mNetworkScorerAppManager = networkScoreAppManager;
Amin Shaikh972e2362016-12-07 14:08:09 -0800205 mScoreCaches = new ArrayMap<>();
Jeremy Joslin967b5812016-06-02 07:58:14 -0700206 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
207 // TODO: Need to update when we support per-user scorers. http://b/23422763
208 mContext.registerReceiverAsUser(
209 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
210 null /* scheduler */);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800211 // TODO(jjoslin): 12/15/16 - Make timeout configurable.
212 mRequestRecommendationCaller =
213 new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700214 }
215
216 /** Called when the system is ready to run third-party code but before it actually does so. */
217 void systemReady() {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700218 if (DBG) Log.d(TAG, "systemReady");
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700219 registerPackageMonitorIfNeeded();
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800220 registerRecommendationSettingObserverIfNeeded();
Jeff Davidson7842f642014-11-23 13:48:12 -0800221 }
222
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700223 /** Called when the system is ready for us to start third-party code. */
224 void systemRunning() {
225 if (DBG) Log.d(TAG, "systemRunning");
226 bindToScoringServiceIfNeeded();
227 }
228
Jeremy Joslin967b5812016-06-02 07:58:14 -0700229 private void onUserUnlocked(int userId) {
230 registerPackageMonitorIfNeeded();
231 bindToScoringServiceIfNeeded();
232 }
233
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800234 private void registerRecommendationSettingObserverIfNeeded() {
235 final List<String> providerPackages =
236 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
237 if (!providerPackages.isEmpty()) {
238 final ContentResolver resolver = mContext.getContentResolver();
239 final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
240 resolver.registerContentObserver(uri, false, new SettingsObserver());
241 }
242 }
243
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700244 private void registerPackageMonitorIfNeeded() {
245 if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800246 final List<String> providerPackages =
247 mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700248 synchronized (mPackageMonitorLock) {
249 // Unregister the current monitor if needed.
250 if (mPackageMonitor != null) {
251 if (DBG) {
252 Log.d(TAG, "Unregistering package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800253 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800254 }
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700255 mPackageMonitor.unregister();
256 mPackageMonitor = null;
Jeff Davidson7842f642014-11-23 13:48:12 -0800257 }
258
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800259 // Create and register the monitor if there are packages that could be providers.
260 if (!providerPackages.isEmpty()) {
261 mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700262 // TODO: Need to update when we support per-user scorers. http://b/23422763
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700263 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
264 false /* externalStorage */);
265 if (DBG) {
266 Log.d(TAG, "Registered package monitor for "
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800267 + mPackageMonitor.mPackagesToWatch);
Jeff Davidson7842f642014-11-23 13:48:12 -0800268 }
269 }
270 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700271 }
272
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700273 private void bindToScoringServiceIfNeeded() {
274 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800275 NetworkScorerAppData scorerData = mNetworkScorerAppManager.getActiveScorer();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700276 bindToScoringServiceIfNeeded(scorerData);
277 }
278
279 private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
280 if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800281 if (scorerData != null && scorerData.recommendationServiceClassName != null) {
282 ComponentName componentName = new ComponentName(scorerData.packageName,
283 scorerData.recommendationServiceClassName);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800284 synchronized (mServiceConnectionLock) {
285 // If we're connected to a different component then drop it.
286 if (mServiceConnection != null
287 && !mServiceConnection.mComponentName.equals(componentName)) {
288 unbindFromScoringServiceIfNeeded();
289 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700290
Jeremy Joslin145c3432016-12-09 13:11:51 -0800291 // If we're not connected at all then create a new connection.
292 if (mServiceConnection == null) {
293 mServiceConnection = new ScoringServiceConnection(componentName);
294 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700295
Jeremy Joslin145c3432016-12-09 13:11:51 -0800296 // Make sure the connection is connected (idempotent)
297 mServiceConnection.connect(mContext);
298 }
Jeremy Joslin967b5812016-06-02 07:58:14 -0700299 } else { // otherwise make sure it isn't bound.
300 unbindFromScoringServiceIfNeeded();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700301 }
302 }
303
304 private void unbindFromScoringServiceIfNeeded() {
305 if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
Jeremy Joslin145c3432016-12-09 13:11:51 -0800306 synchronized (mServiceConnectionLock) {
307 if (mServiceConnection != null) {
308 mServiceConnection.disconnect(mContext);
309 }
310 mServiceConnection = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700311 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800312 clearInternal();
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700313 }
314
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700315 @Override
316 public boolean updateScores(ScoredNetwork[] networks) {
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800317 if (!mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid())) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700318 throw new SecurityException("Caller with UID " + getCallingUid() +
319 " is not the active scorer.");
320 }
321
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800322 final long token = Binder.clearCallingIdentity();
323 try {
324 // Separate networks by type.
325 Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
326 for (ScoredNetwork network : networks) {
327 List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
328 if (networkList == null) {
329 networkList = new ArrayList<>();
330 networksByType.put(network.networkKey.type, networkList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800331 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800332 networkList.add(network);
Amin Shaikh972e2362016-12-07 14:08:09 -0800333 }
334
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800335 // Pass the scores of each type down to the appropriate network scorer.
336 for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
337 final RemoteCallbackList<INetworkScoreCache> callbackList;
338 final boolean isEmpty;
339 synchronized (mScoreCaches) {
340 callbackList = mScoreCaches.get(entry.getKey());
341 isEmpty = callbackList == null
342 || callbackList.getRegisteredCallbackCount() == 0;
343 }
344 if (isEmpty) {
345 if (Log.isLoggable(TAG, Log.VERBOSE)) {
346 Log.v(TAG, "No scorer registered for type " + entry.getKey()
347 + ", discarding");
348 }
349 continue;
350 }
351
352 sendCallback(new Consumer<INetworkScoreCache>() {
353 @Override
354 public void accept(INetworkScoreCache networkScoreCache) {
355 try {
356 networkScoreCache.updateScores(entry.getValue());
357 } catch (RemoteException e) {
358 if (Log.isLoggable(TAG, Log.VERBOSE)) {
359 Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
360 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800361 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700362 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800363 }, Collections.singleton(callbackList));
364 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700365
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800366 return true;
367 } finally {
368 Binder.restoreCallingIdentity(token);
369 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700370 }
371
372 @Override
373 public boolean clearScores() {
Jeff Davidson16197792014-11-03 17:39:54 -0800374 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
375 // should be allowed to flush all scores.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800376 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800377 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700378 PackageManager.PERMISSION_GRANTED) {
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800379 final long token = Binder.clearCallingIdentity();
380 try {
381 clearInternal();
382 return true;
383 } finally {
384 Binder.restoreCallingIdentity(token);
385 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700386 } else {
387 throw new SecurityException(
388 "Caller is neither the active scorer nor the scorer manager.");
389 }
390 }
391
392 @Override
393 public boolean setActiveScorer(String packageName) {
Jeff Davidsone56f2bb2014-11-05 11:14:19 -0800394 // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
395 // to directly set the scorer app rather than having to use the consent dialog. The
396 // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
397 // do the right thing and not enable this feature without explaining it to the user.
398 // In the future, should this API be opened to 3p apps, we will need to lock this down and
399 // figure out another way to streamline the UX.
400
401 // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
402 mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
403
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800404 // Scorers (recommendation providers) are selected and no longer set.
405 return false;
Jeff Davidson26fd1432014-07-29 09:39:52 -0700406 }
407
408 @Override
409 public void disableScoring() {
Jeff Davidson16197792014-11-03 17:39:54 -0800410 // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
411 // should be allowed to disable scoring.
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800412 if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
Jeff Davidson16197792014-11-03 17:39:54 -0800413 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
Jeff Davidson26fd1432014-07-29 09:39:52 -0700414 PackageManager.PERMISSION_GRANTED) {
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800415 // no-op for now but we could write to the setting if needed.
Jeff Davidson26fd1432014-07-29 09:39:52 -0700416 } else {
417 throw new SecurityException(
418 "Caller is neither the active scorer nor the scorer manager.");
Jeff Davidsonb096bdc2014-07-01 12:29:11 -0700419 }
Jeff Davidson26fd1432014-07-29 09:39:52 -0700420 }
421
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700422 /** Clear scores. Callers are responsible for checking permissions as appropriate. */
423 private void clearInternal() {
Amin Shaikh972e2362016-12-07 14:08:09 -0800424 sendCallback(new Consumer<INetworkScoreCache>() {
425 @Override
426 public void accept(INetworkScoreCache networkScoreCache) {
427 try {
428 networkScoreCache.clearScores();
429 } catch (RemoteException e) {
430 if (Log.isLoggable(TAG, Log.VERBOSE)) {
431 Log.v(TAG, "Unable to clear scores", e);
432 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700433 }
434 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800435 }, getScoreCacheLists());
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700436 }
437
438 @Override
Jeremy Joslinc5ac5872016-11-30 15:05:40 -0800439 public void registerNetworkScoreCache(int networkType,
440 INetworkScoreCache scoreCache,
441 int filterType) {
Jeff Davidson16197792014-11-03 17:39:54 -0800442 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800443 final long token = Binder.clearCallingIdentity();
444 try {
445 synchronized (mScoreCaches) {
446 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
447 if (callbackList == null) {
448 callbackList = new RemoteCallbackList<>();
449 mScoreCaches.put(networkType, callbackList);
Amin Shaikh972e2362016-12-07 14:08:09 -0800450 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800451 if (!callbackList.register(scoreCache, filterType)) {
452 if (callbackList.getRegisteredCallbackCount() == 0) {
453 mScoreCaches.remove(networkType);
454 }
455 if (Log.isLoggable(TAG, Log.VERBOSE)) {
456 Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
457 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800458 }
459 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800460 } finally {
461 Binder.restoreCallingIdentity(token);
Amin Shaikh972e2362016-12-07 14:08:09 -0800462 }
463 }
464
465 @Override
466 public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
467 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800468 final long token = Binder.clearCallingIdentity();
469 try {
470 synchronized (mScoreCaches) {
471 RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
472 if (callbackList == null || !callbackList.unregister(scoreCache)) {
473 if (Log.isLoggable(TAG, Log.VERBOSE)) {
474 Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
475 + networkType);
476 }
477 } else if (callbackList.getRegisteredCallbackCount() == 0) {
478 mScoreCaches.remove(networkType);
Amin Shaikh972e2362016-12-07 14:08:09 -0800479 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800480 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800481 } finally {
482 Binder.restoreCallingIdentity(token);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700483 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700484 }
485
486 @Override
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800487 public RecommendationResult requestRecommendation(RecommendationRequest request) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800488 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
489 throwIfCalledOnMainThread();
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800490 final long token = Binder.clearCallingIdentity();
491 try {
492 final INetworkRecommendationProvider provider = getRecommendationProvider();
493 if (provider != null) {
494 try {
495 return mRequestRecommendationCaller.getRecommendationResult(provider, request);
496 } catch (RemoteException | TimeoutException e) {
497 Log.w(TAG, "Failed to request a recommendation.", e);
498 // TODO(jjoslin): 12/15/16 - Keep track of failures.
499 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800500 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800501
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800502 if (DBG) {
503 Log.d(TAG, "Returning the default network recommendation.");
504 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800505
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800506 if (request != null && request.getCurrentSelectedConfig() != null) {
507 return RecommendationResult.createConnectRecommendation(
508 request.getCurrentSelectedConfig());
509 }
510 return RecommendationResult.createDoNotConnectRecommendation();
511 } finally {
512 Binder.restoreCallingIdentity(token);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800513 }
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800514 }
515
516 @Override
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800517 public boolean requestScores(NetworkKey[] networks) {
Jeremy Joslin145c3432016-12-09 13:11:51 -0800518 mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800519 final long token = Binder.clearCallingIdentity();
520 try {
521 final INetworkRecommendationProvider provider = getRecommendationProvider();
522 if (provider != null) {
523 try {
524 provider.requestScores(networks);
525 // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
526 // prevent repeated requests for the same scores.
527 return true;
528 } catch (RemoteException e) {
529 Log.w(TAG, "Failed to request scores.", e);
530 // TODO(jjoslin): 12/15/16 - Keep track of failures.
531 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800532 }
Jeremy Joslin8f5521a2016-12-20 14:36:20 -0800533 return false;
534 } finally {
535 Binder.restoreCallingIdentity(token);
Jeremy Joslin145c3432016-12-09 13:11:51 -0800536 }
Jeremy Joslinb2087a12016-12-13 16:11:51 -0800537 }
538
539 @Override
Amin Shaikh972e2362016-12-07 14:08:09 -0800540 protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700541 mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
Amin Shaikhaa09aa02016-11-21 17:27:53 -0800542 NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700543 if (currentScorer == null) {
544 writer.println("Scoring is disabled.");
545 return;
546 }
Jeremy Joslinfa4f08e2016-12-06 07:42:38 -0800547 writer.println("Current scorer: " + currentScorer.packageName);
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700548
Amin Shaikh972e2362016-12-07 14:08:09 -0800549 sendCallback(new Consumer<INetworkScoreCache>() {
550 @Override
551 public void accept(INetworkScoreCache networkScoreCache) {
552 try {
553 TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
554 } catch (IOException | RemoteException e) {
555 writer.println("Failed to dump score cache: " + e);
556 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700557 }
Amin Shaikh972e2362016-12-07 14:08:09 -0800558 }, getScoreCacheLists());
559
Jeremy Joslin145c3432016-12-09 13:11:51 -0800560 synchronized (mServiceConnectionLock) {
561 if (mServiceConnection != null) {
562 mServiceConnection.dump(fd, writer, args);
563 } else {
564 writer.println("ScoringServiceConnection: null");
565 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700566 }
567 writer.flush();
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700568 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700569
570 /**
Amin Shaikh972e2362016-12-07 14:08:09 -0800571 * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700572 *
573 * <p>May be used to perform an action on all score caches without potentially strange behavior
574 * if a new scorer is registered during that action's execution.
575 */
Amin Shaikh972e2362016-12-07 14:08:09 -0800576 private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700577 synchronized (mScoreCaches) {
Amin Shaikh972e2362016-12-07 14:08:09 -0800578 return new ArrayList<>(mScoreCaches.values());
579 }
580 }
581
582 private void sendCallback(Consumer<INetworkScoreCache> consumer,
583 Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
584 for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
585 synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
586 final int count = callbackList.beginBroadcast();
587 try {
588 for (int i = 0; i < count; i++) {
589 consumer.accept(callbackList.getBroadcastItem(i));
590 }
591 } finally {
592 callbackList.finishBroadcast();
593 }
594 }
Jeff Davidson14f1ec02014-04-29 11:58:26 -0700595 }
596 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700597
Jeremy Joslin145c3432016-12-09 13:11:51 -0800598 private void throwIfCalledOnMainThread() {
599 if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
600 throw new RuntimeException("Cannot invoke on the main thread");
601 }
602 }
603
604 @Nullable
605 private INetworkRecommendationProvider getRecommendationProvider() {
606 synchronized (mServiceConnectionLock) {
607 if (mServiceConnection != null) {
608 return mServiceConnection.getRecommendationProvider();
609 }
610 }
611 return null;
612 }
613
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700614 private static class ScoringServiceConnection implements ServiceConnection {
615 private final ComponentName mComponentName;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800616 private volatile boolean mBound = false;
617 private volatile boolean mConnected = false;
618 private volatile INetworkRecommendationProvider mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700619
620 ScoringServiceConnection(ComponentName componentName) {
621 mComponentName = componentName;
622 }
623
624 void connect(Context context) {
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700625 if (!mBound) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700626 Intent service = new Intent();
627 service.setComponent(mComponentName);
628 mBound = context.bindServiceAsUser(service, this,
629 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
630 UserHandle.SYSTEM);
631 if (!mBound) {
632 Log.w(TAG, "Bind call failed for " + service);
633 } else {
634 if (DBG) Log.d(TAG, "ScoringServiceConnection bound.");
635 }
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700636 }
637 }
638
639 void disconnect(Context context) {
640 try {
641 if (mBound) {
642 mBound = false;
643 context.unbindService(this);
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700644 if (DBG) Log.d(TAG, "ScoringServiceConnection unbound.");
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700645 }
646 } catch (RuntimeException e) {
647 Log.e(TAG, "Unbind failed.", e);
648 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800649
650 mRecommendationProvider = null;
651 }
652
653 INetworkRecommendationProvider getRecommendationProvider() {
654 return mRecommendationProvider;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700655 }
656
657 @Override
658 public void onServiceConnected(ComponentName name, IBinder service) {
659 if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700660 mConnected = true;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800661 mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700662 }
663
664 @Override
665 public void onServiceDisconnected(ComponentName name) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700666 if (DBG) {
667 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
668 }
669 mConnected = false;
Jeremy Joslin145c3432016-12-09 13:11:51 -0800670 mRecommendationProvider = null;
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700671 }
672
673 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
Jeremy Joslin1ec8cd952016-05-26 15:28:48 -0700674 writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound
675 + ", connected: " + mConnected);
Jeremy Joslindd251ef2016-03-14 11:17:41 -0700676 }
677 }
Jeremy Joslin145c3432016-12-09 13:11:51 -0800678
679 /**
680 * Executes the async requestRecommendation() call with a timeout.
681 */
682 private static final class RequestRecommendationCaller
683 extends TimedRemoteCaller<RecommendationResult> {
684 private final IRemoteCallback mCallback;
685
686 RequestRecommendationCaller(long callTimeoutMillis) {
687 super(callTimeoutMillis);
688 mCallback = new IRemoteCallback.Stub() {
689 @Override
690 public void sendResult(Bundle data) throws RemoteException {
691 final RecommendationResult result =
692 data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
693 final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
694 onRemoteMethodResult(result, sequence);
695 }
696 };
697 }
698
699 /**
700 * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
701 * instance.
702 *
703 * @param target the {@link INetworkRecommendationProvider} to request a recommendation
704 * from
705 * @param request the {@link RecommendationRequest} from the calling client
706 * @return a {@link RecommendationResult} from the provider
707 * @throws RemoteException if the call failed
708 * @throws TimeoutException if the call took longer than the set timeout
709 */
710 RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
711 RecommendationRequest request) throws RemoteException, TimeoutException {
712 final int sequence = onBeforeRemoteCall();
713 target.requestRecommendation(request, mCallback, sequence);
714 return getResultTimed(sequence);
715 }
716 }
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700717}