1/*
2 * Copyright (C) 2013 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.wifi;
18
19import static android.net.NetworkInfo.DetailedState.CONNECTED;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.NetworkInfo;
26import android.net.TrafficStats;
27import android.net.wifi.WifiManager;
28import android.os.Handler;
29import android.os.Looper;
30import android.os.Message;
31import android.os.Messenger;
32import android.os.RemoteException;
33import android.util.Log;
34
35import java.io.FileDescriptor;
36import java.io.PrintWriter;
37import java.util.ArrayList;
38import java.util.List;
39import java.util.concurrent.atomic.AtomicBoolean;
40
41/**
42 * Polls for traffic stats and notifies the clients
43 */
44public class WifiTrafficPoller {
45
46    private static final boolean DBG = false;
47    private static final String TAG = "WifiTrafficPoller";
48    /**
49     * Interval in milliseconds between polling for traffic
50     * statistics
51     */
52    private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
53
54    private static final int ENABLE_TRAFFIC_STATS_POLL  = 1;
55    private static final int TRAFFIC_STATS_POLL         = 2;
56    private static final int ADD_CLIENT                 = 3;
57    private static final int REMOVE_CLIENT              = 4;
58
59    private boolean mEnableTrafficStatsPoll = false;
60    private int mTrafficStatsPollToken = 0;
61    private long mTxPkts;
62    private long mRxPkts;
63    /* Tracks last reported data activity */
64    private int mDataActivity;
65
66    private final List<Messenger> mClients = new ArrayList<Messenger>();
67    // err on the side of updating at boot since screen on broadcast may be missed
68    // the first time
69    private AtomicBoolean mScreenOn = new AtomicBoolean(true);
70    private final TrafficHandler mTrafficHandler;
71    private NetworkInfo mNetworkInfo;
72    private final String mInterface;
73
74    private boolean mVerboseLoggingEnabled = false;
75
76    WifiTrafficPoller(Context context, Looper looper, String iface) {
77        mInterface = iface;
78        mTrafficHandler = new TrafficHandler(looper);
79
80        IntentFilter filter = new IntentFilter();
81        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
82        filter.addAction(Intent.ACTION_SCREEN_OFF);
83        filter.addAction(Intent.ACTION_SCREEN_ON);
84
85        context.registerReceiver(
86                new BroadcastReceiver() {
87                    @Override
88                    public void onReceive(Context context, Intent intent) {
89                        if (intent == null) {
90                            return;
91                        }
92                        if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(
93                                intent.getAction())) {
94                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
95                                    WifiManager.EXTRA_NETWORK_INFO);
96                        } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
97                            mScreenOn.set(false);
98                        } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
99                            mScreenOn.set(true);
100                        }
101                        evaluateTrafficStatsPolling();
102                    }
103                }, filter);
104    }
105
106    void addClient(Messenger client) {
107        Message.obtain(mTrafficHandler, ADD_CLIENT, client).sendToTarget();
108    }
109
110    void removeClient(Messenger client) {
111        Message.obtain(mTrafficHandler, REMOVE_CLIENT, client).sendToTarget();
112    }
113
114    void enableVerboseLogging(int verbose) {
115        if (verbose > 0) {
116            mVerboseLoggingEnabled = true;
117        } else {
118            mVerboseLoggingEnabled = false;
119        }
120    }
121
122    private class TrafficHandler extends Handler {
123        public TrafficHandler(Looper looper) {
124            super(looper);
125        }
126
127        public void handleMessage(Message msg) {
128            switch (msg.what) {
129                case ENABLE_TRAFFIC_STATS_POLL:
130                    mEnableTrafficStatsPoll = (msg.arg1 == 1);
131                    if (mVerboseLoggingEnabled) {
132                        Log.d(TAG, "ENABLE_TRAFFIC_STATS_POLL "
133                                + mEnableTrafficStatsPoll + " Token "
134                                + Integer.toString(mTrafficStatsPollToken));
135                    }
136                    mTrafficStatsPollToken++;
137                    if (mEnableTrafficStatsPoll) {
138                        notifyOnDataActivity();
139                        sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
140                                mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
141                    }
142                    break;
143                case TRAFFIC_STATS_POLL:
144                    if (DBG) {
145                        Log.d(TAG, "TRAFFIC_STATS_POLL "
146                                + mEnableTrafficStatsPoll + " Token "
147                                + Integer.toString(mTrafficStatsPollToken)
148                                + " num clients " + mClients.size());
149                    }
150                    if (msg.arg1 == mTrafficStatsPollToken) {
151                        notifyOnDataActivity();
152                        sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
153                                mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
154                    }
155                    break;
156                case ADD_CLIENT:
157                    mClients.add((Messenger) msg.obj);
158                    if (mVerboseLoggingEnabled) {
159                        Log.d(TAG, "ADD_CLIENT: "
160                                + Integer.toString(mClients.size()));
161                    }
162                    break;
163                case REMOVE_CLIENT:
164                    mClients.remove(msg.obj);
165                    break;
166            }
167
168        }
169    }
170
171    private void evaluateTrafficStatsPolling() {
172        Message msg;
173        if (mNetworkInfo == null) return;
174        if (mNetworkInfo.getDetailedState() == CONNECTED && mScreenOn.get()) {
175            msg = Message.obtain(mTrafficHandler,
176                    ENABLE_TRAFFIC_STATS_POLL, 1, 0);
177        } else {
178            msg = Message.obtain(mTrafficHandler,
179                    ENABLE_TRAFFIC_STATS_POLL, 0, 0);
180        }
181        msg.sendToTarget();
182    }
183
184    private void notifyOnDataActivity() {
185        long sent, received;
186        long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
187        int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
188
189        mTxPkts = TrafficStats.getTxPackets(mInterface);
190        mRxPkts = TrafficStats.getRxPackets(mInterface);
191
192        if (DBG) {
193            Log.d(TAG, " packet count Tx="
194                    + Long.toString(mTxPkts)
195                    + " Rx="
196                    + Long.toString(mRxPkts));
197        }
198
199        if (preTxPkts > 0 || preRxPkts > 0) {
200            sent = mTxPkts - preTxPkts;
201            received = mRxPkts - preRxPkts;
202            if (sent > 0) {
203                dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
204            }
205            if (received > 0) {
206                dataActivity |= WifiManager.DATA_ACTIVITY_IN;
207            }
208
209            if (dataActivity != mDataActivity && mScreenOn.get()) {
210                mDataActivity = dataActivity;
211                if (mVerboseLoggingEnabled) {
212                    Log.e(TAG, "notifying of data activity "
213                            + Integer.toString(mDataActivity));
214                }
215                for (Messenger client : mClients) {
216                    Message msg = Message.obtain();
217                    msg.what = WifiManager.DATA_ACTIVITY_NOTIFICATION;
218                    msg.arg1 = mDataActivity;
219                    try {
220                        client.send(msg);
221                    } catch (RemoteException e) {
222                        // Failed to reach, skip
223                        // Client removal is handled in WifiService
224                    }
225                }
226            }
227        }
228    }
229
230    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
231        pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll);
232        pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken);
233        pw.println("mTxPkts " + mTxPkts);
234        pw.println("mRxPkts " + mRxPkts);
235        pw.println("mDataActivity " + mDataActivity);
236    }
237
238}
239