1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.net;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
import static android.net.NetworkPolicyManager.dumpPolicy;
import static android.net.NetworkPolicyManager.dumpRules;
import android.app.IActivityManager;
import android.app.IProcessObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.os.IPowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Service that maintains low-level network policy rules and collects usage
* statistics to drive those rules.
* <p>
* Derives active rules by combining a given policy with other system status,
* and delivers to listeners, such as {@link ConnectivityManager}, for
* enforcement.
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
private static final boolean LOGD = true;
private final Context mContext;
private final IActivityManager mActivityManager;
private final IPowerManager mPowerManager;
private final INetworkStatsService mNetworkStats;
private final Object mRulesLock = new Object();
private boolean mScreenOn;
/** Current network policy for each UID. */
private SparseIntArray mUidPolicy = new SparseIntArray();
/** Current derived network rules for each UID. */
private SparseIntArray mUidRules = new SparseIntArray();
/** Foreground at both UID and PID granularity. */
private SparseBooleanArray mUidForeground = new SparseBooleanArray();
private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
SparseBooleanArray>();
private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
INetworkPolicyListener>();
// TODO: save/restore policy information from disk
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
// TODO: keep record of billing cycle details, and limit rules
// TODO: keep map of interfaces-to-billing-relationship
// TODO: dispatch callbacks through handler when locked
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
IPowerManager powerManager, INetworkStatsService networkStats) {
mContext = checkNotNull(context, "missing context");
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
mNetworkStats = checkNotNull(networkStats, "missing networkStats");
}
public void systemReady() {
// TODO: read current policy from disk
updateScreenOn();
try {
mActivityManager.registerProcessObserver(mProcessObserver);
} catch (RemoteException e) {
// ouch, no foregroundActivities updates means some processes may
// never get network access.
Slog.e(TAG, "unable to register IProcessObserver", e);
}
// TODO: traverse existing processes to know foreground state, or have
// activitymanager dispatch current state when new observer attached.
final IntentFilter screenFilter = new IntentFilter();
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, screenFilter);
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
// remember all pid states and summarize foreground at uid level.
// record foreground for this specific pid
SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
if (pidForeground == null) {
pidForeground = new SparseBooleanArray(2);
mUidPidForeground.put(uid, pidForeground);
}
pidForeground.put(pid, foregroundActivities);
computeUidForegroundL(uid);
}
}
@Override
public void onProcessDied(int pid, int uid) {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// clear records and recompute, when they exist
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
if (pidForeground != null) {
pidForeground.delete(pid);
computeUidForegroundL(uid);
}
}
}
};
private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mRulesLock) {
// screen-related broadcasts are protected by system, no need
// for permissions check.
updateScreenOn();
}
}
};
@Override
public void setUidPolicy(int uid, int policy) {
// TODO: create permission for modifying data policy
mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
final int oldPolicy;
synchronized (mRulesLock) {
oldPolicy = getUidPolicy(uid);
mUidPolicy.put(uid, policy);
updateRulesForUidL(uid);
}
// TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
}
@Override
public int getUidPolicy(int uid) {
synchronized (mRulesLock) {
return mUidPolicy.get(uid, POLICY_NONE);
}
}
@Override
public void registerListener(INetworkPolicyListener listener) {
mListeners.register(listener);
synchronized (mRulesLock) {
// dispatch any existing rules to new listeners
final int size = mUidRules.size();
for (int i = 0; i < size; i++) {
final int uid = mUidRules.keyAt(i);
final int uidRules = mUidRules.valueAt(i);
if (uidRules != RULE_ALLOW_ALL) {
try {
listener.onRulesChanged(uid, uidRules);
} catch (RemoteException e) {
}
}
}
}
}
@Override
public void unregisterListener(INetworkPolicyListener listener) {
mListeners.unregister(listener);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
synchronized (mRulesLock) {
fout.println("Policy status for known UIDs:");
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidPolicy, knownUids);
collectKeys(mUidForeground, knownUids);
collectKeys(mUidRules, knownUids);
final int size = knownUids.size();
for (int i = 0; i < size; i++) {
final int uid = knownUids.keyAt(i);
fout.print(" UID=");
fout.print(uid);
fout.print(" policy=");
final int policyIndex = mUidPolicy.indexOfKey(uid);
if (policyIndex < 0) {
fout.print("UNKNOWN");
} else {
dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
}
fout.print(" foreground=");
final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
if (foregroundIndex < 0) {
fout.print("UNKNOWN");
} else {
dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
}
fout.print(" rules=");
final int rulesIndex = mUidRules.indexOfKey(uid);
if (rulesIndex < 0) {
fout.print("UNKNOWN");
} else {
dumpRules(fout, mUidRules.valueAt(rulesIndex));
}
fout.println();
}
}
}
@Override
public boolean isUidForeground(int uid) {
synchronized (mRulesLock) {
// only really in foreground when screen is also on
return mUidForeground.get(uid, false) && mScreenOn;
}
}
/**
* Foreground for PID changed; recompute foreground at UID level. If
* changed, will trigger {@link #updateRulesForUidL(int)}.
*/
private void computeUidForegroundL(int uid) {
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
// current pid is dropping foreground; examine other pids
boolean uidForeground = false;
final int size = pidForeground.size();
for (int i = 0; i < size; i++) {
if (pidForeground.valueAt(i)) {
uidForeground = true;
break;
}
}
final boolean oldUidForeground = mUidForeground.get(uid, false);
if (oldUidForeground != uidForeground) {
// foreground changed, push updated rules
mUidForeground.put(uid, uidForeground);
updateRulesForUidL(uid);
}
}
private void updateScreenOn() {
synchronized (mRulesLock) {
try {
mScreenOn = mPowerManager.isScreenOn();
} catch (RemoteException e) {
}
updateRulesForScreenL();
}
}
/**
* Update rules that might be changed by {@link #mScreenOn} value.
*/
private void updateRulesForScreenL() {
// only update rules for anyone with foreground activities
final int size = mUidForeground.size();
for (int i = 0; i < size; i++) {
if (mUidForeground.valueAt(i)) {
final int uid = mUidForeground.keyAt(i);
updateRulesForUidL(uid);
}
}
}
private void updateRulesForUidL(int uid) {
final int uidPolicy = getUidPolicy(uid);
final boolean uidForeground = isUidForeground(uid);
// derive active rules based on policy and active state
int uidRules = RULE_ALLOW_ALL;
if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
// uid in background, and policy says to block paid data
uidRules = RULE_REJECT_PAID;
}
// TODO: only dispatch when rules actually change
// record rule locally to dispatch to new listeners
mUidRules.put(uid, uidRules);
// dispatch changed rule to existing listeners
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
if (listener != null) {
try {
listener.onRulesChanged(uid, uidRules);
} catch (RemoteException e) {
}
}
}
mListeners.finishBroadcast();
}
private static <T> T checkNotNull(T value, String message) {
if (value == null) {
throw new NullPointerException(message);
}
return value;
}
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
target.put(source.keyAt(i), true);
}
}
private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
target.put(source.keyAt(i), true);
}
}
private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
fout.print("[");
final int size = value.size();
for (int i = 0; i < size; i++) {
fout.print(value.keyAt(i) + "=" + value.valueAt(i));
if (i < size - 1) fout.print(",");
}
fout.print("]");
}
}
|