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