NetworkScoreService.java revision e56f2bb5ecae624e1b6573515f855a26f756aed5
1/*
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
19import android.Manifest.permission;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.net.INetworkScoreCache;
25import android.net.INetworkScoreService;
26import android.net.NetworkScoreManager;
27import android.net.NetworkScorerAppManager;
28import android.net.NetworkScorerAppManager.NetworkScorerAppData;
29import android.net.ScoredNetwork;
30import android.os.Binder;
31import android.os.RemoteException;
32import android.os.UserHandle;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36
37import com.android.internal.R;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47
48/**
49 * Backing service for {@link android.net.NetworkScoreManager}.
50 * @hide
51 */
52public class NetworkScoreService extends INetworkScoreService.Stub {
53    private static final String TAG = "NetworkScoreService";
54
55    private final Context mContext;
56
57    private final Map<Integer, INetworkScoreCache> mScoreCaches;
58
59    public NetworkScoreService(Context context) {
60        mContext = context;
61        mScoreCaches = new HashMap<>();
62    }
63
64    /** Called when the system is ready to run third-party code but before it actually does so. */
65    void systemReady() {
66        ContentResolver cr = mContext.getContentResolver();
67        if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
68            // On first run, we try to initialize the scorer to the one configured at build time.
69            // This will be a no-op if the scorer isn't actually valid.
70            String defaultPackage = mContext.getResources().getString(
71                    R.string.config_defaultNetworkScorerPackageName);
72            if (!TextUtils.isEmpty(defaultPackage)) {
73                NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
74            }
75            Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
76        }
77    }
78
79    @Override
80    public boolean updateScores(ScoredNetwork[] networks) {
81        if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
82            throw new SecurityException("Caller with UID " + getCallingUid() +
83                    " is not the active scorer.");
84        }
85
86        // Separate networks by type.
87        Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
88        for (ScoredNetwork network : networks) {
89            List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
90            if (networkList == null) {
91                networkList = new ArrayList<>();
92                networksByType.put(network.networkKey.type, networkList);
93            }
94            networkList.add(network);
95        }
96
97        // Pass the scores of each type down to the appropriate network scorer.
98        for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
99            INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
100            if (scoreCache != null) {
101                try {
102                    scoreCache.updateScores(entry.getValue());
103                } catch (RemoteException e) {
104                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
105                        Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
106                    }
107                }
108            } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
109                Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
110            }
111        }
112
113        return true;
114    }
115
116    @Override
117    public boolean clearScores() {
118        // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
119        // should be allowed to flush all scores.
120        if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
121                mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
122                        PackageManager.PERMISSION_GRANTED) {
123            clearInternal();
124            return true;
125        } else {
126            throw new SecurityException(
127                    "Caller is neither the active scorer nor the scorer manager.");
128        }
129    }
130
131    @Override
132    public boolean setActiveScorer(String packageName) {
133        // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps
134        // to directly set the scorer app rather than having to use the consent dialog. The
135        // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to
136        // do the right thing and not enable this feature without explaining it to the user.
137        // In the future, should this API be opened to 3p apps, we will need to lock this down and
138        // figure out another way to streamline the UX.
139
140        // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
141        mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
142
143        return setScorerInternal(packageName);
144    }
145
146    @Override
147    public void disableScoring() {
148        // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED)
149        // should be allowed to disable scoring.
150        if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) ||
151                mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
152                        PackageManager.PERMISSION_GRANTED) {
153            // The return value is discarded here because at this point, the call should always
154            // succeed. The only reason for failure is if the new package is not a valid scorer, but
155            // we're disabling scoring altogether here.
156            setScorerInternal(null /* packageName */);
157        } else {
158            throw new SecurityException(
159                    "Caller is neither the active scorer nor the scorer manager.");
160        }
161    }
162
163    /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
164    private boolean setScorerInternal(String packageName) {
165        long token = Binder.clearCallingIdentity();
166        try {
167            // Preemptively clear scores even though the set operation could fail. We do this for
168            // safety as scores should never be compared across apps; in practice, Settings should
169            // only be allowing valid apps to be set as scorers, so failure here should be rare.
170            clearInternal();
171            boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
172            if (result) {
173                Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
174                intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
175                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
176            }
177            return result;
178        } finally {
179            Binder.restoreCallingIdentity(token);
180        }
181    }
182
183    /** Clear scores. Callers are responsible for checking permissions as appropriate. */
184    private void clearInternal() {
185        Set<INetworkScoreCache> cachesToClear = getScoreCaches();
186
187        for (INetworkScoreCache scoreCache : cachesToClear) {
188            try {
189                scoreCache.clearScores();
190            } catch (RemoteException e) {
191                if (Log.isLoggable(TAG, Log.VERBOSE)) {
192                    Log.v(TAG, "Unable to clear scores", e);
193                }
194            }
195        }
196    }
197
198    @Override
199    public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
200        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
201        synchronized (mScoreCaches) {
202            if (mScoreCaches.containsKey(networkType)) {
203                throw new IllegalArgumentException(
204                        "Score cache already registered for type " + networkType);
205            }
206            mScoreCaches.put(networkType, scoreCache);
207        }
208    }
209
210    @Override
211    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
212        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
213        NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
214        if (currentScorer == null) {
215            writer.println("Scoring is disabled.");
216            return;
217        }
218        writer.println("Current scorer: " + currentScorer.mPackageName);
219        writer.flush();
220
221        for (INetworkScoreCache scoreCache : getScoreCaches()) {
222            try {
223                scoreCache.asBinder().dump(fd, args);
224            } catch (RemoteException e) {
225                writer.println("Unable to dump score cache");
226                if (Log.isLoggable(TAG, Log.VERBOSE)) {
227                    Log.v(TAG, "Unable to dump score cache", e);
228                }
229            }
230        }
231    }
232
233    /**
234     * Returns a set of all score caches that are currently active.
235     *
236     * <p>May be used to perform an action on all score caches without potentially strange behavior
237     * if a new scorer is registered during that action's execution.
238     */
239    private Set<INetworkScoreCache> getScoreCaches() {
240        synchronized (mScoreCaches) {
241            return new HashSet<>(mScoreCaches.values());
242        }
243    }
244}
245