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