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