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