com.mobilyzer.measurements.RRCTask.java Source code

Java tutorial

Introduction

Here is the source code for com.mobilyzer.measurements.RRCTask.java

Source

/*
 * Copyright 2013 RobustNet Lab, University of Michigan. All Rights Reserved.
 * 
 * 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.mobilyzer.measurements;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;

import com.mobilyzer.Checkin;
import com.mobilyzer.Config;
import com.mobilyzer.DeviceInfo;
import com.mobilyzer.MeasurementDesc;
import com.mobilyzer.MeasurementResult;
import com.mobilyzer.MeasurementTask;
import com.mobilyzer.MeasurementResult.TaskProgress;
import com.mobilyzer.exceptions.MeasurementError;
import com.mobilyzer.util.Logger;
import com.mobilyzer.util.MeasurementJsonConvertor;
import com.mobilyzer.util.PhoneUtils;

import android.content.SharedPreferences;
import android.net.TrafficStats;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.StringBuilderPrinter;

/**
 * This class measures the round trip times of packets as you vary the packet timings between them,
 * with the purpose of inferring RRC state transitions.
 * 
 * See "Characterizing Radio Resource Allocation for 3G Networks" by Feng et. al, IMC 2010 for a
 * full explanation of the methodology and goals.
 * 
 * TODO (sanae): Pause the RRC tasks when a checkin is performed, rather than pausing the checkin
 * while the RRC task is performed.
 * 
 * @author sanae@umich.edu (Sanae Rosen)
 * 
 */
public class RRCTask extends MeasurementTask {
    // Type name for internal use
    public static final String TYPE = "rrc";
    // Human readable name for the task
    public static final String DESCRIPTOR = "rrc";
    public static String TAG = "MobiPerf_RRC_INFERENCE";

    private volatile boolean stopFlag;
    private long duration;

    /**
     * Stores parameters for the RRC inference task
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     * 
     */
    public static class RRCDesc extends MeasurementDesc {
        private static String HOST = "www.google.com";

        // Default echo server name and port to measure the RTT to infer RRC state
        private static int PORT = 50000;
        private static String ECHO_HOST = "ep2.eecs.umich.edu";
        // Perform RTT measurements every GRANULARITY ms
        public int GRANULARITY = 500;
        // MIN / MAX is the echo packet size
        int MIN = 0;
        int MAX = 1024;
        // Default total number of measurements
        int size = 31;
        // Echo server / port, and target to perform the upper-layer tasks
        public String echoHost = ECHO_HOST;
        public String target = HOST;
        int port = PORT;

        long testId; // unique value for this set of tests

        // Default threshold to repeat each RTT measurement because of background traffic
        int GIVEUP_THRESHHOLD = 15;

        // server controled variable
        boolean DNS = true;
        boolean TCP = true;
        boolean HTTP = true;
        boolean RRC = true;
        boolean SIZES = true;

        // Whether RRC result is visible to users
        public boolean RESULT_VISIBILITY = false;

        /*
         * For the upper-layer tests, a series of tests are made for different inter-packet intervals,
         * in order. "Times" indicates the inter-packet intervals, the other fields store the results.
         * All must be the same size.
         */
        Integer[] times; // The times where the above tests were made, in units of GRANULARITY.
        int sizeGranularity = 200; // the spacing between sizes to test
        int[] httpTest; // The results of the HTTP test performed at each time
        int[] dnsTest; // likewise, for the DNS test
        int[] tcpTest; // likewise, for the TCP test

        // Whether or not to run the upper layer tests, i.e. the HTTP, TCP and DNS tests.
        // Disabling this flag will disable all upper layer tests.
        private boolean runUpperLayerTests = false;

        /*
         * Default times between packets for which the upper layer tests are performed. Later, these
         * times will be replaced by times from the model. These times are GRANULARITY milliseconds: if
         * GRANULARITY is 500, then a default time of 6 means measurements are taken 500 ms apart.
         */
        Integer[] defaultTimesULTasks = new Integer[] { 0, 2, 4, 8, 12, 16, 22 };

        public RRCDesc(String key, Date startTime, Date endTime, double intervalSec, long count, long priority,
                int contextIntervalSec, Map<String, String> params) {
            super(RRCTask.TYPE, key, startTime, endTime, intervalSec, count, priority, contextIntervalSec, params);
            initializeParams(params);
        }

        protected RRCDesc(Parcel in) {
            super(in);
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            echoHost = in.readString();
            target = in.readString();
            MIN = in.readInt();
            MAX = in.readInt();
            port = in.readInt();
            size = in.readInt();
            sizeGranularity = in.readInt();
            DNS = in.readByte() != 0;
            HTTP = in.readByte() != 0;
            TCP = in.readByte() != 0;
            RRC = in.readByte() != 0;
            SIZES = in.readByte() != 0;
            RESULT_VISIBILITY = in.readByte() != 0;
            GIVEUP_THRESHHOLD = in.readInt();
            times = (Integer[]) in.readArray(loader);
        }

        public static final Parcelable.Creator<RRCDesc> CREATOR = new Parcelable.Creator<RRCDesc>() {

            @Override
            public RRCDesc createFromParcel(Parcel in) {
                return new RRCDesc(in);
            }

            @Override
            public RRCDesc[] newArray(int size) {
                return new RRCDesc[size];
            }

        };

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeString(echoHost);
            dest.writeString(target);
            dest.writeInt(MIN);
            dest.writeInt(MAX);
            dest.writeInt(port);
            dest.writeInt(sizeGranularity);
            dest.writeByte((byte) (DNS ? 1 : 0));
            dest.writeByte((byte) (HTTP ? 1 : 0));
            dest.writeByte((byte) (TCP ? 1 : 0));
            dest.writeByte((byte) (RRC ? 1 : 0));
            dest.writeByte((byte) (SIZES ? 1 : 0));
            dest.writeByte((byte) (RESULT_VISIBILITY ? 1 : 0));
            dest.writeInt(GIVEUP_THRESHHOLD);
            dest.writeArray(times);
        }

        public MeasurementResult getResults(MeasurementResult result) {
            if (HTTP)
                result.addResult("http", httpTest);
            if (TCP)
                result.addResult("tcp", tcpTest);
            if (DNS)
                result.addResult("dns", dnsTest);
            result.addResult("times", times);
            return result;
        }

        public void displayResults(StringBuilderPrinter printer) {
            String DEL = "\t", toprint = DEL + DEL;
            for (int i = 1; i <= times.length; i++) {
                toprint += DEL + " | state" + i;
            }
            toprint += " |";
            int oneLineLen = toprint.length();
            toprint += "\n";
            // seperator
            for (int i = 0; i < oneLineLen; i++) {
                toprint += "-";
            }
            toprint += "\n";
            if (HTTP) {
                toprint += "HTTP (ms)" + DEL;
                for (int i = 0; i < httpTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(httpTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            if (DNS) {
                toprint += "DNS (ms)" + DEL;
                for (int i = 0; i < dnsTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(dnsTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            if (TCP) {
                toprint += "TCP (ms)" + DEL;
                for (int i = 0; i < tcpTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(tcpTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            toprint += "Timers (s)";
            for (int i = 0; i < times.length; i++) {
                double curTime = (double) times[i] * (double) GRANULARITY / 1000.0;
                toprint += DEL + " | " + String.format("%.2f", curTime);
            }
            toprint += " |\n";
            printer.println(toprint);
        }

        @Override
        public String getType() {
            return RRCTask.TYPE;
        }

        /**
         * Given the parameters fetched from the server, sets up the parameters as needed for the upper
         * layer tests, i.e. the application layer tests.
         */
        @Override
        protected void initializeParams(Map<String, String> params) {

            // In this case, we fall back to the default values defined above.
            if (params == null) {
                return;
            }

            // The parameters for the echo server
            this.echoHost = params.get("echo_host");
            this.target = params.get("target");
            if (this.echoHost == null) {
                this.echoHost = ECHO_HOST;
            }
            if (this.target == null) {
                this.target = HOST;
            }
            Logger.d("param: echo_host " + this.echoHost);
            Logger.d("param: target " + this.target);

            try {
                String val = null;
                // Size of the small packet
                if ((val = params.get("min")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.MIN = Integer.parseInt(val);
                }
                Logger.d("param: Min " + this.MIN);
                // Size of the large packet
                if ((val = params.get("max")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.MAX = Integer.parseInt(val);
                }
                Logger.d("param: MAX " + this.MAX);
                // Echo server port
                if ((val = params.get("port")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.port = Integer.parseInt(val);
                }
                Logger.d("param: port " + this.port);
                // Number of tests to run from the RRC test
                if ((val = params.get("size")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.size = Integer.parseInt(val);
                }
                Logger.d("param: size " + this.size);
                // When testing size dependence, increase the
                if ((val = params.get("size_granularity")) != null && val.length() > 0
                        && Integer.parseInt(val) > 0) {
                    this.sizeGranularity = Integer.parseInt(val);
                }
                Logger.d("param: size_granularity " + this.sizeGranularity);
                // Whether or not to run the DNS test
                if ((val = params.get("dns")) != null && val.length() > 0) {
                    this.DNS = Boolean.parseBoolean(val);
                }
                Logger.d("param: DNS " + this.DNS);
                // Whether or not to run the HTTP test
                if ((val = params.get("http")) != null && val.length() > 0) {
                    this.HTTP = Boolean.parseBoolean(val);
                }
                Logger.d("param: HTTP " + this.HTTP);
                // Whether or not to run the TCP test
                if ((val = params.get("tcp")) != null && val.length() > 0) {
                    this.TCP = Boolean.parseBoolean(val);
                }
                Logger.d(params.get("rrc"));
                Logger.d("param: TCP " + this.TCP);
                // Whether or not to run the RRC inference task
                if ((val = params.get("rrc")) != null && val.length() > 0) {
                    this.RRC = Boolean.parseBoolean(val);
                }
                Logger.d("param: RRC " + this.RRC);
                if ((val = params.get("measure_sizes")) != null && val.length() > 0) {
                    this.SIZES = Boolean.parseBoolean(val);
                }
                Logger.d("param: SIZES " + this.SIZES);
                // Whether the RRC result is visible to users
                if ((val = params.get("result_visibility")) != null && val.length() > 0) {
                    this.RESULT_VISIBILITY = Boolean.parseBoolean(val);
                }
                Logger.d("param: visibility " + this.RESULT_VISIBILITY);
                // How many times to retry a test when interrupted by background traffic
                if ((val = params.get("giveup_threshhold")) != null && val.length() > 0
                        && Integer.parseInt(val) > 0) {
                    this.GIVEUP_THRESHHOLD = Integer.parseInt(val);
                }
                Logger.d("param: GIVEUP_THRESHHOLD " + this.GIVEUP_THRESHHOLD);

                // Default assumed timers for the upper layer tests (HTTP, DNS, TCP),
                // in units of GRANULARITY. These are set via a comma-separated list
                // of numbers.
                if ((val = params.get("default_extra_test_timers")) != null && val.length() > 0) {
                    String[] timesString = val.split("\\s*,\\s*");
                    List<String> stringList = new ArrayList<String>(Arrays.asList(timesString));
                    List<Integer> intList = new ArrayList<Integer>();
                    Iterator<String> iterator = stringList.iterator();
                    while (iterator.hasNext()) {
                        intList.add(Integer.parseInt(iterator.next()));
                    }
                    times = (Integer[]) intList.toArray(new Integer[intList.size()]);

                }
                if (times == null) {
                    times = defaultTimesULTasks;
                }
            } catch (NumberFormatException e) {
                throw new InvalidParameterException(" RRCTask cannot be created due to invalid params");
            }

            if (size == 0) {
                // 31 tests, by default. From 0s to 15s inclusive, in half-second intervals.
                size = 31;
            }
        }

        /**
         * For the arrays holding the results for the upper layer tests, we need to initialize them to
         * be the same size as the number of tests we run. -1 means uninitialized.
         * 
         * @param size Number of results to store
         */
        public void initializeExtraTaskResults(int size) {
            httpTest = new int[size];
            dnsTest = new int[size];
            tcpTest = new int[size];
            for (int i = 0; i < size; i++) {
                httpTest[i] = -1;
                dnsTest[i] = -1;
                tcpTest[i] = -1;
            }
            runUpperLayerTests = true;
        }

        /**
         * Given an interpacket interval and a round trip time from an HTTP test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for HTTP test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setHttp(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            httpTest[index] = rtt;
        }

        /**
         * Given an interpacket interval and a round trip time from a TCP test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for the TCP test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setTcp(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            tcpTest[index] = rtt;
        }

        /**
         * Given an interpacket interval and a round trip time from a DNS test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for the DNS test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setDns(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            dnsTest[index] = rtt;
        }

    }

    /**
     * Stores data from the tests on the effect of packet size on RRC state related delays
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     */
    public static class RrcSizeTestData {
        int time; // Interval between packets
        int size; // Size of packets sent
        long result; // Round trip time, in milliseconds
        long testId; // A unique id associated with a set of measurements

        /**
         * 
         * @param time The inter-packet interval
         * @param size The packet size, in bytes
         * @param result The round trip time for that packet size and inter-packet interval
         * @param testId The unique id for this set of tests
         */
        public RrcSizeTestData(int time, int size, long result, long testId) {
            this.time = time;
            this.size = size;
            this.result = result;
            this.testId = testId;
        }

        public JSONObject toJSON(String networktype, String phoneId) throws JSONException {
            JSONObject entry = new JSONObject();
            entry.put("network_type", networktype);
            entry.put("phone_id", phoneId);
            entry.put("test_id", testId);
            entry.put("time_delay", time);
            entry.put("size", size);
            entry.put("result", result);

            return entry;
        }

    }

    /**
     * Store the results of our RRC test.
     * 
     * Data is stored as a list of results indexed by the test number. Tests are performed in order
     * with increasing inter-packet intervals and results and data about the tests are stored here.
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     * 
     */
    public static class RRCTestData {

        // Round-trip times, in ms
        int[] rttsSmall;
        int[] rttsLarge;
        // Packets lost for each test
        int[] packetsLostSmall;
        int[] packetsLostLarge;
        // Signal strengths at time of each test
        int[] signalStrengthSmall;
        int[] signalStrengthLarge;
        // Error Counts from each test
        int[] errorCountSmall;
        int[] errorCountLarge;

        ArrayList<RrcSizeTestData> packetSizes;

        // Unique incrementing value that identifies this set of tests.
        long testId;

        /**
         * @param size Number of tests to run (each corresponding to an interpacket interval, increasing
         *        in increments of 500 ms)
         * @param testId Unique ID for this set of tests
         */
        public RRCTestData(int size, long testId) {
            size = size + 1;

            rttsSmall = new int[size];
            rttsLarge = new int[size];
            packetsLostSmall = new int[size];
            packetsLostLarge = new int[size];
            signalStrengthSmall = new int[size];
            signalStrengthLarge = new int[size];
            errorCountLarge = new int[size];
            errorCountSmall = new int[size];

            this.testId = testId;

            packetSizes = new ArrayList<RrcSizeTestData>();

            // Set default values
            for (int i = 0; i < rttsSmall.length; i++) {
                // 7000 is the cutoff for timeouts.
                // This makes the model-building script treat no data and timeouts the same.
                rttsSmall[i] = 7000;
                rttsLarge[i] = 7000;
                packetsLostSmall[i] = -1;
                packetsLostLarge[i] = -1;
                signalStrengthSmall[i] = -1;
                signalStrengthLarge[i] = -1;
                errorCountSmall[i] = -1;
                errorCountLarge[i] = -1;
            }
        }

        public long testId() {
            return testId;
        }

        public String[] toJSON(String networktype, String phoneId) {
            String[] returnval = new String[rttsSmall.length];
            try {
                for (int i = 0; i < rttsSmall.length; i++) {
                    JSONObject subtest = new JSONObject();
                    subtest.put("rtt_low", rttsSmall[i]);
                    subtest.put("rtt_high", rttsLarge[i]);
                    subtest.put("lost_low", packetsLostSmall[i]);
                    subtest.put("lost_high", packetsLostLarge[i]);
                    subtest.put("signal_low", signalStrengthSmall[i]);
                    subtest.put("signal_high", signalStrengthLarge[i]);
                    subtest.put("error_low", errorCountSmall[i]);
                    subtest.put("error_high", errorCountLarge[i]);
                    subtest.put("network_type", networktype);
                    subtest.put("time_delay", i);
                    subtest.put("test_id", testId);
                    subtest.put("phone_id", phoneId);
                    returnval[i] = subtest.toString();
                    Logger.w("Test ID for rrc inference test was " + this.testId);
                }
            } catch (JSONException e) {
                Logger.e("Error converting RRC data to JSON");
            }
            return returnval;
        }

        /**
         * Erase all data corresponding to a set of results with a particular interpacket interval
         * 
         * @param index Index into the array of results.
         */
        public void deleteItem(int index) {
            rttsSmall[index] = -1;
            rttsLarge[index] = -1;
            packetsLostSmall[index] = -1;
            packetsLostLarge[index] = -1;
            signalStrengthSmall[index] = -1;
            signalStrengthLarge[index] = -1;
            errorCountSmall[index] = -1;
            errorCountLarge[index] = -1;
        }

        /**
         * Save the data resulting from a given test.
         * 
         * @param index Index into the array of results.
         * @param rttMax Round trip time for large packets (generally 1KB)
         * @param rttMin Round time time for small packets (generally empty)
         * @param numPacketsLostMax Number of packets lost for the set of large packets, out of 10
         * @param numPacketsLostMin Likewise, for the small packets
         * @param errorHigh Error count for the set of large packets
         * @param errorLow Likewise, for the small packets
         * @param signalHigh The signal strength when sending the large packets
         * @param signalLow Likewise, for the small packets
         */
        public void updateAll(int index, int rttMax, int rttMin, int numPacketsLostMax, int numPacketsLostMin,
                int errorHigh, int errorLow, int signalHigh, int signalLow) {
            this.rttsLarge[index] = (int) rttMax;
            this.rttsSmall[index] = (int) rttMin;
            this.packetsLostLarge[index] = numPacketsLostMax;
            this.packetsLostSmall[index] = numPacketsLostMin;
            this.errorCountLarge[index] = errorHigh;
            this.errorCountSmall[index] = errorLow;
            this.signalStrengthLarge[index] = signalHigh;
            this.signalStrengthSmall[index] = signalLow;
        }

        public void setRrcSizeTestData(int index, int size, long result, long testId) throws MeasurementError {
            packetSizes.add(new RrcSizeTestData(index, size, result, testId));
        }

        public String[] sizeDataToJSON(String networkType, String phoneId) {
            String[] returnval = new String[packetSizes.size()];
            try {
                for (int i = 0; i < packetSizes.size(); i++) {
                    returnval[i] = packetSizes.get(i).toJSON(networkType, phoneId).toString();
                }
            } catch (JSONException e) {
                Logger.e("Error converting RRC data to JSON");
            }
            return returnval;
        }
    }

    /**
     * Class for tracking if there has been interfering traffic.
     * 
     * We need to know how many packets are expected. We can then use the global packet counters to
     * see if more packets are sent than expected.
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     * 
     */
    public static class PacketMonitor {
        private long[] packetsFirst;
        private long[] packetsLast;
        boolean bySize = false;

        /**
         * Initialize immediately before use. Values are time-sensitive.
         */
        PacketMonitor() {
            readCurrentPacketValues();
        }

        void setBySize() {
            bySize = true;
        }

        void readCurrentPacketValues() {
            packetsFirst = getPacketsSent();
        }

        /**
         * Call this to determine if packets have been sent since initializing.
         * 
         * @param expectedRcv Number of packets expected to be received by the device since
         *        initialization
         * @param expectedSent Number of packets expected to be sent by the device since initialization
         * @return Whether or not there is interfering traffic
         */
        boolean isTrafficInterfering(int expectedRcv, int expectedSent) {
            packetsLast = getPacketsSent();

            long rcvPackets = (packetsLast[0] - packetsFirst[0]);
            long sentPackets = (packetsLast[1] - packetsFirst[1]);
            if (rcvPackets <= expectedRcv && sentPackets <= expectedSent) {
                Logger.d("No competing traffic, continue");
                return false;
            }
            Logger.d("Competing traffic, retry");

            return true;
        }

        /**
         * Determine how many packets, so far, have been sent (the contents of /proc/net/dev/). This is
         * a global value. We use this to determine if any other app anywhere on the phone may have sent
         * interfering traffic that might have changed the RRC state without our knowledge.
         * 
         * @return Two values: number of bytes or packets received at index 0, followed by the number
         *         sent at index 1.
         */
        public long[] getPacketsSent() {
            long[] retval = { -1, -1 };
            if (bySize) {
                retval[0] = TrafficStats.getMobileRxBytes();
                retval[1] = TrafficStats.getMobileTxBytes();

            } else {
                retval[0] = TrafficStats.getMobileRxPackets();
                retval[1] = TrafficStats.getMobileTxPackets();
            }

            return retval;
        }
    }

    @SuppressWarnings("rawtypes")
    public static Class getDescClass() throws InvalidClassException {
        return RRCDesc.class;
    }

    public RRCTask(MeasurementDesc desc) {
        super(new RRCDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority,
                desc.contextIntervalSec, desc.parameters));
        this.stopFlag = false;
        this.duration = Config.DEFAULT_RRC_TASK_DURATION;
    }

    protected RRCTask(Parcel in) {
        super(in);
        stopFlag = in.readByte() != 0;
        duration = in.readLong();
    }

    public static final Parcelable.Creator<RRCTask> CREATOR = new Parcelable.Creator<RRCTask>() {
        public RRCTask createFromParcel(Parcel in) {
            return new RRCTask(in);
        }

        public RRCTask[] newArray(int size) {
            return new RRCTask[size];
        }
    };

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeByte((byte) (stopFlag ? 1 : 0));
        dest.writeLong(duration);
    }

    @Override
    public MeasurementTask clone() {
        MeasurementDesc desc = this.measurementDesc;
        RRCDesc newDesc = new RRCDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count,
                desc.priority, desc.contextIntervalSec, desc.parameters);
        return new RRCTask(newDesc);
    }

    @Override
    public MeasurementResult[] call() throws MeasurementError {
        RRCDesc desc = runInferenceTests();
        MeasurementResult[] mrArray = new MeasurementResult[1];
        mrArray[0] = constructResultStandard(desc);
        ;
        return mrArray;
    }

    @Override
    public String getDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public String getType() {
        return RRCTask.TYPE;
    }

    @Override
    public boolean stop() {
        stopFlag = true;
        return true;
    }

    /**
     * Helper function to construct MeasurementResults to submit to the server
     * 
     * @param desc The data collected for the RRC test to be converted into something understandable
     *        by the server.
     * @return A data structure that can be sent to the server. Contains only the results of the TCP,
     *         DNS and HTTP tests: the others are sent via a separate mechanism as they are stored
     *         separately.
     */
    private MeasurementResult constructResultStandard(RRCDesc desc) {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
        TaskProgress taskProgress = TaskProgress.COMPLETED;
        MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId,
                phoneUtils.getDeviceProperty(this.getKey()), RRCTask.TYPE, System.currentTimeMillis() * 1000,
                taskProgress, this.measurementDesc);

        if (desc.runUpperLayerTests) {
            result = desc.getResults(result);
        }

        Logger.i(MeasurementJsonConvertor.toJsonString(result));
        return result;
    }

    /**
     * The core RRC inference functionality is in this function. The steps involved can be summarized
     * as follows:
     * <ol>
     * <li>Fetch the last model generated by the server, if it exists.</li>
     * <li>Check we are on a cellular network, otherwise abort.</li>
     * <li>Inform all other tasks that they should delay network traffic until later.</li>
     * <li>For every upper layer test, if upper layer tests and that test specifically are enabled,
     * run that test.</li>
     * </ol>
     * 
     * @return A data structure containing all the results and metadata surrounding the RRC inference
     *         tests performed.
     * @throws MeasurementError
     */
    private RRCDesc runInferenceTests() throws MeasurementError {

        // Fetch the existing model from the server, if it exists
        RRCDesc desc = (RRCDesc) measurementDesc;
        desc.testId = getTestId(PhoneUtils.getGlobalContext());
        Logger.w("Test ID set to " + desc.testId);
        PhoneUtils utils = PhoneUtils.getPhoneUtils();
        desc.initializeExtraTaskResults(desc.times.length);

        // Check to make sure we are on a valid (i.e. cellular) network
        if (utils.getNetwork() == "UNKNOWN" || utils.getNetwork() == "WIRELESS") {
            Logger.d("Returning: network is" + utils.getNetwork() + " rssi " + utils.getCurrentRssi());
            return desc;
        }

        try {
            /*
             * Suspend all other tasks performed by the app as they can interfere. Although we have a
             * built-in check where we abort if traffic in the background interferes, in the past people
             * have scheduled other tests to be every 5 minutes, which can cause the RRC task to never
             * successfully complete without having to abort.
             */

            RRCTestData data = new RRCTestData(desc.size, desc.testId);

            // If the RRC task is enabled
            if (desc.RRC) {
                // Set up the connection to the echo server
                Logger.d("Active inference: about to begin");
                Logger.d(desc.echoHost + ":" + desc.port);
                InetAddress serverAddr = InetAddress.getByName(desc.echoHost);

                // Perform the RRC timer and latency inference task
                Logger.d("Demotion inference: about to begin");
                desc = inferDemotion(serverAddr, desc, data, utils);

                Logger.d("About to save data");

                try {
                    Logger.w("RRC: update the model on the GAE datastore");
                    uploadRrcInferenceData(data);
                    Logger.d("Saving data complete");
                } catch (IOException e) {
                    e.printStackTrace();
                    Logger.e("Data not saved: " + e.getMessage());
                }
            }

            // Check if the upper layer tasks are enabled
            if (desc.runUpperLayerTests) {
                if (desc.DNS) {
                    Logger.w("Start DNS task");
                    // Test the dependence of DNS latency on the RRC state, using
                    // the previously constructed model if available.
                    runDnsTest(desc.times, desc);
                }
                if (desc.TCP) {
                    Logger.w("Start TCP task");
                    // Test the dependence of TCP latency on the RRC state.
                    runTCPHandshakeTest(desc.times, desc);
                }
                if (desc.HTTP) {
                    Logger.w("Start HTTP task");
                    // Test the dependence of HTTP latency on the RRC state.
                    runHTTPTest(desc.times, desc);
                }

                if (desc.SIZES) {
                    Logger.w("Start size dependence task");
                    runSizeThresholdTest(desc.times, desc, data, desc.testId);
                    uploadRrcInferenceSizeData(data);
                }
            }

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return desc;
    }

    /**
     * Impact of packet sizes on rrc inference results.
     * 
     * @param sizeData Contains data to upload
     */
    private void uploadRrcInferenceSizeData(RRCTestData sizeData) {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
        Checkin checkin = new Checkin(PhoneUtils.getGlobalContext());
        DeviceInfo info = phoneUtils.getDeviceInfo();
        String network_id = phoneUtils.getNetwork();
        String[] sizeParameters = sizeData.sizeDataToJSON(network_id, info.deviceId);

        try {
            for (String parameter : sizeParameters) {
                Logger.w("Uploading RRC size data: " + parameter);
                String response = checkin.serviceRequest("rrc/uploadRRCInferenceSizes", parameter);
                Logger.w("Response from GAE: " + response);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Send the RRC data to the server.
     * 
     * Sent as a separate call because the data is formatted in a different, more complicated way than
     * other measurement tasks.
     * 
     * @param data Contains data to upload
     * @throws IOException
     */
    private void uploadRrcInferenceData(RRCTestData data) throws IOException {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
        Checkin checkin = new Checkin(PhoneUtils.getGlobalContext());
        DeviceInfo info = phoneUtils.getDeviceInfo();
        String network_id = phoneUtils.getNetwork();
        String[] parameters = data.toJSON(network_id, info.deviceId);
        try {
            for (String parameter : parameters) {
                Logger.w("Uploading RRC raw data: " + parameter);
                String response = checkin.serviceRequest("rrc/uploadRRCInference", parameter);
                Logger.w("Response from GAE: " + response);

                Logger.i("TaskSchedule.uploadMeasurementResult() complete");
            }
        } catch (IOException e) {
            throw new IOException(e.getMessage());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }

    /**
     * Determines the packet size dependence of the RRC task.
     * 
     * Given a specified list of inter-packet intervals, perform the RRC inference measurement for
     * packets of sizes incremented by the size granularity specified.
     * 
     * @param times Inter-packet intervals to test
     * @param desc Parameters for the RRC inference task
     * @param data Stores results of the RRC inference task
     * @param testId A unique ID identifying this set of tests
     */
    private void runSizeThresholdTest(final Integer[] times, RRCDesc desc, RRCTestData data, long testId) {

        InetAddress serverAddr;
        try {
            serverAddr = InetAddress.getByName(desc.echoHost);
        } catch (UnknownHostException e) {
            Logger.e("Invalid or unreachable echo host. Test aborted.");
            e.printStackTrace();
            return;
        }
        for (int i = 0; i < times.length; i++) {
            for (int j = desc.sizeGranularity; j <= 1024; j += desc.sizeGranularity) {
                try {
                    long result = inferDemotionPacketSize(serverAddr, times[i], desc, j);
                    data.setRrcSizeTestData(times[i], j, result, testId);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Due to problems with how we detect interfering traffic, this test does not always work and
     * results should be treated with caution. I am keeping this test around, though, as we can still
     * get data on how much HTTP handshakes vary in how long they take.
     * 
     * The "times" are the inter-packet intervals at which to run the test. Ideally these should be
     * based on the model constructed by the server, a default assumed value is used in their absence.
     * 
     * Based on the time it takes to load a response from the page.
     * 
     * This test is not currently as accurate as the other tests, for reasons described below, and has
     * thus been disabled.
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the
     *        tests
     * @param desc Stores parameters for the RRC inference tests in general
     * @throws MeasurementError
     */
    private void runHTTPTest(final Integer[] times, RRCDesc desc) throws MeasurementError {
        /*
         * Length of time it takes to request and read in a page.
         */

        Logger.d("Active inference HTTP test: about to begin");
        if (times.length != desc.httpTest.length) {
            desc.httpTest = new int[times.length];
        }
        long startTime = 0;
        long endTime = 0;
        try {
            for (int i = 0; i < times.length; i++) {
                // We try until we reach a threshhold or until there is no
                // competing traffic.
                for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
                    // Sometimes the network can change in the middle of a test
                    checkIfWifi();
                    if (stopFlag) {
                        throw new MeasurementError("Cancelled");
                    }

                    /*
                     * We keep track of the packets sent at the beginning and end of the test so we can detect
                     * if there is competing traffic anywhere on the phone.
                     */
                    PacketMonitor packetMonitor = new PacketMonitor();

                    // Initiate the desired RRC state by sending a large enough packet
                    // to go to DCH and waiting for the specified amount of time
                    try {
                        InetAddress serverAddr;
                        serverAddr = InetAddress.getByName(desc.echoHost);
                        sendPacket(serverAddr, desc.MAX, desc);

                        waitTime(times[i] * desc.GRANULARITY, true);

                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                        continue;
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                        continue;
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                    startTime = System.currentTimeMillis();
                    HttpClient client = new DefaultHttpClient();
                    HttpGet request = new HttpGet();

                    request.setURI(new URI("http://" + desc.target + "?dummy=" + i + "" + j));

                    HttpResponse response = client.execute(request);
                    endTime = System.currentTimeMillis();

                    BufferedReader in = null;
                    in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                    StringBuffer sb = new StringBuffer("");
                    String line = "";

                    while ((line = in.readLine()) != null) {
                        sb.append(line + "\n");
                    }
                    in.close();

                    // not really accurate, just rules out the worst cases of interference
                    if (!packetMonitor.isTrafficInterfering(100, 100)) {
                        break;
                    }
                    startTime = 0;
                    endTime = 0;

                }

                long rtt = endTime - startTime;
                try {
                    desc.setHttp(i, (int) rtt);
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
                Logger.d("Time for Http" + rtt);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * The "times" are the inter-packet intervals at which to run the test. Ideally these should be
     * based on the model constructed by the server, a default assumed value is used in their absence.
     * 
     * <ol>
     * <li>vSend a packet to initiate the RRC state desired.</li>
     * <li>Create a randomly generated host name (to ensure that the host name is not cached). I found
     * on some devices that even when you clear the cache manually, the data remains in the cache.</li>
     * <li>Time how long it took to look it up.</li>
     * <li>Count the total packets sent, globally on the phone. If more packets were sent than</li>
     * expected, abort and try again. </li>
     * <li>Otherwise, save the data for that test and move to the next inter-packet interval.</li>
     * </ol>
     * 
     * Test is similar to the approach taken in DnsLookUpTask.java.
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the
     *        test
     * @param desc Stores parameters for the RRC inference tests in general
     * @throws MeasurementError
     */

    public void runDnsTest(final Integer[] times, RRCDesc desc) throws MeasurementError {
        Logger.d("Active inference DNS test: about to begin");
        if (times.length != desc.dnsTest.length) {
            desc.dnsTest = new int[times.length];
        }

        long startTime = 0;
        long endTime = 0;

        // For each inter-packet interval...
        for (int i = 0; i < times.length; i++) {
            // On a failure, try again until a threshold is reached.
            for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {

                // Sometimes the network can change in the middle of a test
                checkIfWifi();
                if (stopFlag) {
                    throw new MeasurementError("Cancelled");
                }

                /*
                 * We keep track of the packets sent at the beginning and end of the test so we can detect
                 * if there is competing traffic anywhere on the phone.
                 */

                PacketMonitor packetMonitor = new PacketMonitor();

                // Initiate the desired RRC state by sending a large enough packet
                // to go to DCH and waiting for the specified amount of time
                try {
                    InetAddress serverAddr;
                    serverAddr = InetAddress.getByName(desc.echoHost);
                    sendPacket(serverAddr, desc.MAX, desc);
                    waitTime(times[i] * desc.GRANULARITY, true);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                    continue;
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                    continue;
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }

                // Create a random URL, to avoid the caching problem
                UUID uuid = UUID.randomUUID();
                String host = uuid.toString() + ".com";
                // Start measuring the time to complete the task
                startTime = System.currentTimeMillis();
                try {
                    @SuppressWarnings("unused")
                    InetAddress serverAddr = InetAddress.getByName(host);
                } catch (UnknownHostException e) {
                    // we do this on purpose! Since it's a fake URL the lookup will fail
                }
                // When we fail to find the URL, we stop timing
                endTime = System.currentTimeMillis();

                // Check how many packets were sent again. If the expected number
                // of packets were sent, we can finish and go to the next task.
                // Otherwise, we have to try again.
                if (!packetMonitor.isTrafficInterfering(5, 5)) {
                    break;
                }

                startTime = 0;
                endTime = 0;
            }

            // If we broke out of the try-again loop, the last set of results are
            // valid and we can save them.
            long rtt = endTime - startTime;
            try {
                desc.setDns(i, (int) rtt);
            } catch (MeasurementError e) {
                e.printStackTrace();
            }
            Logger.d("Time for DNS" + rtt);
        }
    }

    /**
     * Time how long it takes to do a TCP 3-way handshake, starting from the induced RRC state.
     * 
     * <ol>
     * <li>Send a packet to initiate the RRC state desired.</li>
     * <li>Open a TCP connection to the echo host server.</li>
     * <li>Time how long it took to look it up.</li>
     * <li>Count the total packets sent, globally on the phone. If more packets were sent than
     * expected, abort and try again.</li>
     * <li>Otherwise, save the data for that test and move to the next inter-packet interval.
     * </ol>
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the
     *        test
     * @param desc Stores parameters for the RRC inference tests in general
     * @throws MeasurementError
     */
    public void runTCPHandshakeTest(final Integer[] times, RRCDesc desc) throws MeasurementError {
        Logger.d("Active inference TCP test: about to begin");
        if (times.length != desc.tcpTest.length) {
            desc.tcpTest = new int[times.length];
        }
        long startTime = 0;
        long endTime = 0;

        try {
            // For each inter-packet interval...
            for (int i = 0; i < times.length; i++) {
                // On a failure, try again until a threshhold is reached.
                for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
                    checkIfWifi();
                    if (stopFlag) {
                        throw new MeasurementError("Cancelled");
                    }

                    PacketMonitor packetMonitor = new PacketMonitor();

                    // Induce DCH then wait for specified time
                    InetAddress serverAddr;
                    serverAddr = InetAddress.getByName(desc.echoHost);
                    sendPacket(serverAddr, desc.MAX, desc);
                    waitTime(times[i] * 500, true);

                    // begin test. We test the time to do a 3-way handshake only.
                    startTime = System.currentTimeMillis();

                    serverAddr = InetAddress.getByName(desc.target);
                    // three-way handshake done when socket created
                    Socket socket = new Socket(serverAddr, 80);
                    endTime = System.currentTimeMillis();

                    // Check how many packets were sent again. If the expected number
                    // of packets were sent, we can finish and go to the next task.
                    // Otherwise, we have to try again.
                    if (!packetMonitor.isTrafficInterfering(5, 4)) {
                        socket.close();
                        break;
                    }
                    startTime = 0;
                    endTime = 0;
                    socket.close();
                }
                long rtt = endTime - startTime;
                try {
                    desc.setTcp(i, (int) rtt);
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
                Logger.d("Time for TCP" + rtt);
            }
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * For all time intervals specified, go through and perform the RRC inference test.
     * 
     * The way this test works, at a high level, is:
     * <ol>
     * <li>Send a large packet to induce DCH (or the equivalent)</li>
     * <li>Wait for an amount of time, t</li>
     * <li>Send a large packet and measure the round-trip time</li>
     * <li>Wait for another amount of time, t</li>
     * <li>Send a small packet and measure the round-trip time 6. Repeat for all specified values of
     * t. These start at 0 and increase by GRANULARITY, "size" times.</li>
     * </ol>
     * 
     * The size of "small" and "large" packets are defined in the parameters. We observe the total
     * packets sent to make sure there is no interfering traffic.
     * 
     * Packets are UDP packets.
     * 
     * From this, we can infer the timers associated with RRC states. By sending a large packet, we
     * induce the highest power state. Waiting a number of seconds afterwards allows us to demote to
     * the next state. Sending a packet and observing the RTT allows us to infer if a state promotion
     * had to take place.
     * 
     * FACH is characterized by different state promotion times for large and small packets.
     * 
     * @param serverAddr Address of the echo server
     * @param desc Stores the parameters for the RRC tests
     * @param data Stores the results of the RRC tests
     * @param utils For fetching the signal strength when the test is performed
     * @return The parameters for the RRC tests
     * @throws InterruptedException
     * @throws IOException
     * @throws MeasurementError
     */
    private RRCDesc inferDemotion(InetAddress serverAddr, RRCDesc desc, RRCTestData data, PhoneUtils utils)
            throws InterruptedException, IOException, MeasurementError {
        Logger.d("Demotion basic test");

        for (int i = 0; i <= desc.size; i++) {

            checkIfWifi();
            if (stopFlag) {
                throw new MeasurementError("Cancelled");
            }
            inferDemotionHelper(serverAddr, i, data, desc, utils);
            Logger.d("Finished demotion test with length" + i);

            // Note that we scale from 0-90 to save some stuff for upper layer tests.
            // If we wanted to really do this properly we could scale
            // according to how long each task should take.
        }
        return desc;
    }

    @Override
    public String toString() {
        RRCDesc desc = (RRCDesc) measurementDesc;
        return "[RRC]\n  Echo Server: " + desc.echoHost + "\n  Target: " + desc.target + "\n  Interval (sec): "
                + desc.intervalSec + "\n  Next run: " + desc.startTime;
    }

    /*********************************************************************
     * UTILITIES *
     *********************************************************************/

    /**
     * 
     * Sleep for the amount of time indicated.
     * 
     * @param timeToSleep Time for which we pause the current thread
     * @param useMs Toggles between units of milliseconds for the first parameter (true) and
     *        seconds(false).
     * @throws InterruptedException
     */
    public static void waitTime(int timeToSleep, boolean useMs) throws InterruptedException {
        Logger.d("Wait for n ms: " + timeToSleep);

        if (!useMs) {
            timeToSleep = timeToSleep * 1000;
        }
        Thread.sleep(timeToSleep);
    }

    /**
     * Sends a bunch of UDP packets of the size indicated and wait for the response.
     * 
     * Counts how long it takes for all the packets to return. PAckets are currently not labelled: the
     * total time is the time for the first packet to leave until the last packet arrives. AFter 7000
     * ms it is assumed packets are lost and the socket times out. In that case, the number of packets
     * lost is recorded.
     * 
     * @param serverAddr server to which to send the packets
     * @param size size of the packets
     * @param num number of packets to send
     * @param packetSize size of the packets sent
     * @param port port to send the packets to
     * @return first value: the amount of time to send all packets and get a response. second value:
     *         number of packets lost, on a timeout.
     * @throws IOException
     */
    public static long[] sendMultiPackets(InetAddress serverAddr, int size, int num, int packetSize, int port)
            throws IOException {

        long startTime = 0;
        long endTime = 0;
        byte[] buf = new byte[size];
        byte[] rcvBuf = new byte[packetSize];
        long[] retval = { -1, -1 };
        long numLost = 0;
        int i = 0;

        DatagramSocket socket = new DatagramSocket();
        DatagramPacket packetRcv = new DatagramPacket(rcvBuf, rcvBuf.length);
        DatagramPacket packet = new DatagramPacket(buf, buf.length, serverAddr, port);

        try {
            socket.setSoTimeout(7000);
            startTime = System.currentTimeMillis();
            Logger.d("Sending packet, waiting for response ");
            for (i = 0; i < num; i++) {
                socket.send(packet);
            }
            for (i = 0; i < num; i++) {
                socket.receive(packetRcv);
                if (i == 0) {

                    endTime = System.currentTimeMillis();
                }
            }
        } catch (SocketTimeoutException e) {
            Logger.d("Timed out");
            numLost += (num - i);
            socket.close();
        }
        Logger.d("Sending complete: " + endTime);

        retval[0] = endTime - startTime;
        retval[1] = numLost;

        return retval;
    }

    /**
     * Helper function that sends a single packet and receives an empty packet back.
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param size size of packet to send in bytes
     * @param desc Holds parameters for the RRC inference task
     * @return The round trip time for the packet
     * @throws IOException
     */
    private static long sendPacket(InetAddress serverAddr, int size, RRCDesc desc) throws IOException {
        return sendPacket(serverAddr, size, desc.MIN, desc.port);
    }

    /**
     * Send a single packet of the size indicated and wait for a response.
     * 
     * After 7000 ms, time out and return a value of -1 (meaning no response). Otherwise, return the
     * time from when the packet was sent to when a response was returned by the echo server.
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param size size of packet to send in bytes
     * @param rcvSize size of packets sent from the echo server
     * @param port where the echo server is listening
     * @return The round trip time for the packet
     * @throws IOException
     */
    public static long sendPacket(InetAddress serverAddr, int size, int rcvSize, int port) throws IOException {
        long startTime = 0;
        byte[] buf = new byte[size];
        byte[] rcvBuf = new byte[rcvSize];

        DatagramSocket socket = new DatagramSocket();
        DatagramPacket packetRcv = new DatagramPacket(rcvBuf, rcvBuf.length);

        DatagramPacket packet = new DatagramPacket(buf, buf.length, serverAddr, port);

        try {
            socket.setSoTimeout(7000);
            startTime = System.currentTimeMillis();
            Logger.d("Sending packet, waiting for response ");

            socket.send(packet);
            socket.receive(packetRcv);
        } catch (SocketTimeoutException e) {
            Logger.d("Timed out, trying again");
            socket.close();
            return -1;
        }
        long endTime = System.currentTimeMillis();
        Logger.d("Sending complete: " + endTime);

        return endTime - startTime;
    }

    /**
     * Performs a single RRC inference test to account for packet sizes.
     * 
     * Sends a packet, waits for the specified length of time, then sends a cluster of packets of the
     * specified size.
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param wait Time to wait between packets, in milliseconds.
     * @param desc Holds parameters for the RRC inference task
     * @param size Size, in bytes, of the packet to send.
     * @return The amount of time to send all packets and get a response.
     * @throws IOException
     * @throws InterruptedException
     */
    public static long inferDemotionPacketSize(InetAddress serverAddr, int wait, RRCDesc desc, int size)
            throws IOException, InterruptedException {
        long retval = -1;
        for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
            Logger.d("Active inference: determine packet size, size " + size + " interval " + wait);

            // Induce the highest power state
            sendPacket(serverAddr, desc.MAX, desc.MIN, desc.port);

            // WAit for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send the specified packet size
            long[] rtts = sendMultiPackets(serverAddr, size, 1, desc.MIN, desc.port);
            long rttPacket = rtts[0];

            PacketMonitor packetMonitor = new PacketMonitor();
            if (!packetMonitor.isTrafficInterfering(3, 3)) {
                retval = rttPacket;
                break;
            }
        }
        return retval;
    }

    private long[] inferDemotionHelper(InetAddress serverAddr, int wait, RRCTestData data, RRCDesc desc,
            PhoneUtils utils) throws IOException, InterruptedException {
        return inferDemotionHelper(serverAddr, wait, data, desc, wait, utils);
    }

    /**
     * One component of the RRC inference task.
     * 
     * <ol>
     * <li>Induce the highest-power RRC state by sending a large packet.</li>
     * <li>Wait the indicated number of seconds.</li>
     * <li>Send a series of 10 large packets at once. Measure: a) Time for all packets to be echoed
     * back b) number of packets lost, if any c) associated signal strength d) error rate is currently
     * not implemented.</li>
     * <li>Check if the expected number of packets were sent while performing a test. If too many
     * packets were sent, abort.</li>
     * </ol>
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param wait Time in milliseconds to pause between packets sent
     * @param data Stores the results of the RRC inference tests
     * @param desc Stores parameters for the RRC inference tests
     * @param index Index of the current test, corresponds to the inter-packet time in intervals of
     *        half milliseconds
     * @param utils Used to retrieve the phone's RSSI at the time of collecting the data
     * @return first value: the amount of time to send all small packets and get a response. Second
     *         value: time to send all large packets and get a response.
     * @throws IOException
     * @throws InterruptedException
     */
    public static long[] inferDemotionHelper(InetAddress serverAddr, int wait, RRCTestData data, RRCDesc desc,
            int index, PhoneUtils utils) throws IOException, InterruptedException {
        /**
         * Once we generalize the RRC state inference problem, this is what we will use (since in
         * general, RRC state can differ between large and small packets). Gives the RTT for a large
         * packet and a small packet for a given time after inducing DCH state. Granularity currently
         * half-seconds but can easily be increased.
         * 
         * Measures packets sent before and after to make sure no extra packets were sent, and retries
         * on a failure. Also checks that there was no timeout and retries on a failure.
         */
        long rttLargePacket = -1;
        long rttSmallPacket = -1;
        int packetsLostSmall = 0;
        int packetsLostLarge = 0;

        int errorCountLarge = 0;
        int errorCountSmall = 0;
        int signalStrengthLarge = 0;
        int signalStrengthSmall = 0;

        for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
            Logger.d("Active inference: about to begin helper");

            PacketMonitor packetMonitor = new PacketMonitor();

            // Induce the highest power state
            sendPacket(serverAddr, desc.MAX, desc.MIN, desc.port);

            // WAit for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send a bunch of large packets, all at once, and take measurements on the result
            signalStrengthLarge = utils.getCurrentRssi();
            long[] retval = sendMultiPackets(serverAddr, desc.MAX, 10, desc.MIN, desc.port);
            packetsLostSmall = (int) retval[1];
            rttLargePacket = retval[0];

            // wait for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send a bunch of small packets, all at once, and take measurements on the result
            signalStrengthSmall = utils.getCurrentRssi();
            retval = sendMultiPackets(serverAddr, desc.MIN, 10, desc.MIN, desc.port);
            packetsLostLarge = (int) retval[1];
            rttSmallPacket = retval[0];

            if (!packetMonitor.isTrafficInterfering(21, 21)) {
                break;
            }
            Logger.d("Try again.");
            rttLargePacket = -1;
            rttSmallPacket = -1;
            packetsLostSmall = 0;
            packetsLostLarge = 0;

            errorCountLarge = 0;
            errorCountSmall = 0;
            signalStrengthLarge = 0;
            signalStrengthSmall = 0;
        }

        Logger.d("3G demotion, lower bound: rtts are:" + rttLargePacket + " " + rttSmallPacket + " "
                + packetsLostSmall + " " + packetsLostLarge);

        long[] retval = { rttLargePacket, rttSmallPacket };
        data.updateAll(index, (int) rttLargePacket, (int) rttSmallPacket, packetsLostSmall, packetsLostLarge,
                errorCountLarge, errorCountSmall, signalStrengthLarge, signalStrengthSmall);

        return retval;
    }

    /**
     * Keep a global counter that labels each test with a unique, increasing integer.
     * 
     * @param context Any context instance, needed to fetch the last test id from permanent storage
     * @return The unique (for this device) test ID generated.
     */
    public static synchronized int getTestId(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("test_ids", Context.MODE_PRIVATE);
        int testid = prefs.getInt("test_id", 0) + 1;
        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt("test_id", testid);
        editor.commit();
        return testid;
    }

    /**
     * If on wifi, suspend the task until we go back to the cellular network. Use exponential back-off
     * to calculate the wait time, with a limit of 500 s. Once the limit is reached, unpause other
     * tasks. Repause traffic if we are good to resume again.
     * 
     * @throws MeasurementError
     * 
     */
    public void checkIfWifi() throws MeasurementError {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();

        int timeToWait = 10;
        while (true) {
            if (phoneUtils.getNetwork() != PhoneUtils.NETWORK_WIFI) {
                // RRCTrafficControl.PauseTraffic();
                return;
            }
            if (stopFlag) {
                throw new MeasurementError("Cancelled");
            }

            // if (timeToWait < 60) {
            // RRCTrafficControl.UnPauseTraffic();
            // }
            Logger.d("RRCTask: on Wifi, try again later: " + phoneUtils.getNetwork());
            if (timeToWait < 500) { // 500s, or a bit over 8 minutes.
                timeToWait = timeToWait * 2;
            } else {
                // if it's taking a while, stop pausing traffic
            }
            try {
                waitTime(timeToWait, false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public long getDuration() {
        return this.duration;
    }

    @Override
    public void setDuration(long newDuration) {
        if (newDuration < 0) {
            this.duration = 0;
        } else {
            this.duration = newDuration;
        }
    }
}