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