Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd42acd9 authored by Antonio Cansado's avatar Antonio Cansado
Browse files

Implementation of data usage callbacks.

NetworkStatsService will register data usage requests
and keep data usage stats scoped to the request.

There are different types of data usage requests
- scoped to a set of NetworkTemplate; these are restrictred to
device owners and carrier apps and allow the caller to monitor
all activity on the specified interfaces.
- scoped to all uids visible to the user, if the user has
android.Manifest.permission#PACKAGE_USAGE_STATS permission.
The set of uids may change over time, so we keep track of that.
- scoped to a set of uids given by the caller, granted that
the caller has access to those uids.
- scoped to the caller's own data usage. This doesn't require
PACKAGE_USAGE_STATS.

Bug: 25812785
Change-Id: Ie11f35fc1f29d0dbe82f7fc924b169bb55c76708
parent c86f234c
Loading
Loading
Loading
Loading
+94 −5
Original line number Diff line number Diff line
@@ -25,9 +25,15 @@ import android.net.ConnectivityManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
import android.net.INetworkStatsService;
import android.os.Binder;
import android.os.Build;
import android.os.Message;
import android.os.Messenger;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

/**
@@ -75,16 +81,26 @@ import android.util.Log;
 * not included.
 */
public class NetworkStatsManager {
    private final static String TAG = "NetworkStatsManager";
    private static final String TAG = "NetworkStatsManager";
    private static final boolean DBG = false;

    /** @hide */
    public static final int CALLBACK_LIMIT_REACHED = 0;
    /** @hide */
    public static final int CALLBACK_RELEASED = 1;

    private final Context mContext;
    private final INetworkStatsService mService;

    /**
     * {@hide}
     */
    public NetworkStatsManager(Context context) {
        mContext = context;
        mService = INetworkStatsService.Stub.asInterface(
                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
    }

    /**
     * Query network usage statistics summaries. Result is summarised data usage for the whole
     * device. Result is a single Bucket aggregated over time, state, uid, tag and roaming. This
@@ -322,7 +338,40 @@ public class NetworkStatsManager {
        checkNotNull(policy, "DataUsagePolicy cannot be null");
        checkNotNull(callback, "DataUsageCallback cannot be null");

        // TODO: Implement stub.
        final Looper looper;
        if (handler == null) {
            looper = Looper.myLooper();
        } else {
            looper = handler.getLooper();
        }

        if (DBG) Log.d(TAG, "registerDataUsageCallback called with " + policy);

        NetworkTemplate[] templates;
        if (policy.subscriberIds == null || policy.subscriberIds.length == 0) {
            templates = new NetworkTemplate[1];
            templates[0] = createTemplate(policy.networkType, null /* subscriberId */);
        } else {
            templates = new NetworkTemplate[policy.subscriberIds.length];
            for (int i = 0; i < policy.subscriberIds.length; i++) {
                templates[i] = createTemplate(policy.networkType, policy.subscriberIds[i]);
            }
        }
        DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
                templates, policy.uids, policy.thresholdInBytes);
        try {
            CallbackHandler callbackHandler = new CallbackHandler(looper, callback);
            callback.request = mService.registerDataUsageCallback(
                    mContext.getOpPackageName(), request, new Messenger(callbackHandler),
                    new Binder());
            if (DBG) Log.d(TAG, "registerDataUsageCallback returned " + callback.request);

            if (callback.request == null) {
                Log.e(TAG, "Request from callback is null; should not happen");
            }
        } catch (RemoteException e) {
            if (DBG) Log.d(TAG, "Remote exception when registering callback");
        }
    }

    /**
@@ -331,9 +380,15 @@ public class NetworkStatsManager {
     * @param callback The {@link DataUsageCallback} used when registering.
     */
    public void unregisterDataUsageCallback(DataUsageCallback callback) {
        checkNotNull(callback, "DataUsageCallback cannot be null");

        // TODO: Implement stub.
        if (callback == null || callback.request == null
                || callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
            throw new IllegalArgumentException("Invalid DataUsageCallback");
        }
        try {
            mService.unregisterDataUsageRequest(callback.request);
        } catch (RemoteException e) {
            if (DBG) Log.d(TAG, "Remote exception when unregistering callback");
        }
    }

    /**
@@ -366,4 +421,38 @@ public class NetworkStatsManager {
        }
        return template;
    }

    private static class CallbackHandler extends Handler {
        private DataUsageCallback mCallback;
        CallbackHandler(Looper looper, DataUsageCallback callback) {
            super(looper);
            mCallback = callback;
        }

        @Override
        public void handleMessage(Message message) {
            DataUsageRequest request =
                    (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);

            switch (message.what) {
                case CALLBACK_LIMIT_REACHED: {
                    if (mCallback != null) {
                        mCallback.onLimitReached();
                    } else {
                        Log.e(TAG, "limit reached with released callback for " + request);
                    }
                    break;
                }
                case CALLBACK_RELEASED: {
                    if (DBG) Log.d(TAG, "callback released for " + request);
                    mCallback = null;
                    break;
                }
            }
        }

        private static Object getObject(Message msg, String key) {
            return msg.getData().getParcelable(key);
        }
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -31,6 +31,11 @@ import java.util.Objects;
 */
public class DataUsageRequest implements Parcelable {

    /**
     * @hide
     */
    public static final String PARCELABLE_KEY = "DataUsageRequest";

    /**
     * @hide
     */
+10 −0
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package android.net;

import android.net.DataUsageRequest;
import android.net.INetworkStatsSession;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.IBinder;
import android.os.Messenger;

/** {@hide} */
interface INetworkStatsService {
@@ -57,4 +60,11 @@ interface INetworkStatsService {
    /** Advise persistance threshold; may be overridden internally. */
    void advisePersistThreshold(long thresholdBytes);

    /** Registers a callback on data usage. */
    DataUsageRequest registerDataUsageCallback(String callingPackage,
            in DataUsageRequest request, in Messenger messenger, in IBinder binder);

    /** Unregisters a callback on data usage. */
    void unregisterDataUsageRequest(in DataUsageRequest request);

}
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.net;

import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;

@@ -48,6 +49,7 @@ public final class NetworkStatsAccess {
    @IntDef({
            Level.DEFAULT,
            Level.USER,
            Level.DEVICESUMMARY,
            Level.DEVICE,
    })
    @Retention(RetentionPolicy.SOURCE)
@@ -147,6 +149,12 @@ public final class NetworkStatsAccess {
                // Device-level access - can access usage for any uid.
                return true;
            case NetworkStatsAccess.Level.DEVICESUMMARY:
                // Can access usage for any app running in the same user, along
                // with some special uids (system, removed, or tethering) and
                // anonymized uids
                return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
                        || uid == UID_TETHERING || uid == UID_ALL
                        || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
            case NetworkStatsAccess.Level.USER:
                // User-level access - can access usage for any app running in the same user, along
                // with some special uids (system, removed, or tethering).
+25 −3
Original line number Diff line number Diff line
@@ -135,7 +135,11 @@ public class NetworkStatsCollection implements FileRotator.Reader {
    }

    public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
        final int callerUid = Binder.getCallingUid();
        return getRelevantUids(accessLevel, Binder.getCallingUid());
    }

    public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
                final int callerUid) {
        IntArray uids = new IntArray();
        for (int i = 0; i < mStats.size(); i++) {
            final Key key = mStats.keyAt(i);
@@ -169,7 +173,17 @@ public class NetworkStatsCollection implements FileRotator.Reader {
    public NetworkStatsHistory getHistory(
            NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
            @NetworkStatsAccess.Level int accessLevel) {
        final int callerUid = Binder.getCallingUid();
        return getHistory(template, uid, set, tag, fields, start, end, accessLevel,
                Binder.getCallingUid());
    }

    /**
     * Combine all {@link NetworkStatsHistory} in this collection which match
     * the requested parameters.
     */
    public NetworkStatsHistory getHistory(
            NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
            @NetworkStatsAccess.Level int accessLevel, int callerUid) {
        if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
            throw new SecurityException("Network stats history of uid " + uid
                    + " is forbidden for caller " + callerUid);
@@ -198,6 +212,15 @@ public class NetworkStatsCollection implements FileRotator.Reader {
     */
    public NetworkStats getSummary(NetworkTemplate template, long start, long end,
            @NetworkStatsAccess.Level int accessLevel) {
        return getSummary(template, start, end, accessLevel, Binder.getCallingUid());
    }

    /**
     * Summarize all {@link NetworkStatsHistory} in this collection which match
     * the requested parameters.
     */
    public NetworkStats getSummary(NetworkTemplate template, long start, long end,
            @NetworkStatsAccess.Level int accessLevel, int callerUid) {
        final long now = System.currentTimeMillis();

        final NetworkStats stats = new NetworkStats(end - start, 24);
@@ -207,7 +230,6 @@ public class NetworkStatsCollection implements FileRotator.Reader {
        final NetworkStats.Entry entry = new NetworkStats.Entry();
        NetworkStatsHistory.Entry historyEntry = null;

        final int callerUid = Binder.getCallingUid();
        for (int i = 0; i < mStats.size(); i++) {
            final Key key = mStats.keyAt(i);
            if (templateMatches(template, key.ident)
Loading