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