/* Copyright 2013 The MITRE Corporation, All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work 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 org.mitre.svmp.performance; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Debug.MemoryInfo; import android.telephony.PhoneStateListener; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Log; import org.mitre.svmp.common.DatabaseHandler; import java.util.TimerTask; /** * @author Joe Portner * Runs on an interval to gather performance data and write it to the database * Controlled by PerformanceTimer */ public class MeasureTask extends TimerTask { private static final String TAG = MeasureTask.class.getName(); private boolean running = true; private Context context; private SpanPerformanceData spanPerformanceData; private final PointPerformanceData pointPerformanceData; private long startDate; private DatabaseHandler databaseHandler; // used to record values to database private ActivityManager activityManager; // used to get memory usage private WifiManager wifiManager; // used to get wifi strength private TelephonyManager telephonyManager; // used to get cell signal values private int phoneType; // PHONE_TYPE_NONE, PHONE_TYPE_GSM, PHONE_TYPE_CDMA public MeasureTask(Context context, SpanPerformanceData spanPerformanceData, PointPerformanceData pointPerformanceData, long startDate) { this.context = context; this.spanPerformanceData = spanPerformanceData; this.pointPerformanceData = pointPerformanceData; this.startDate = startDate; this.databaseHandler = new DatabaseHandler(context); this.activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); context.registerReceiver(this.batteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); this.wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); this.telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); phoneType = telephonyManager.getPhoneType(); if (phoneType == TelephonyManager.PHONE_TYPE_GSM || phoneType == TelephonyManager.PHONE_TYPE_CDMA) telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); } public void run() { // create a copy of the measurement data, and reset the values for the original object SpanPerformanceData spanMeasurements = spanPerformanceData.reset(); int memoryUsage = getMemoryUsage(); double wifiStrength = getWifiStrength(); // take single-point-in-time measurements, then add them synchronized (pointPerformanceData) { // cpu usage is set by the MeasureCpuThread pointPerformanceData.setMemoryUsage(memoryUsage); // set memory used pointPerformanceData.setWifiStrength(wifiStrength); // set wifi strength // battery level is set by the batteryInfoReceiver // cell signal values are set by the phoneStateListener // ping value is set by the AppRTCActivity if (running) databaseHandler.insertPerformanceData(startDate, spanMeasurements, pointPerformanceData); Log.d(TAG, String.format("[%s, %s]", spanMeasurements, pointPerformanceData)); } } @Override public boolean cancel() { try { running = false; context.unregisterReceiver(batteryInfoReceiver); telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); // unregister listener databaseHandler.close(); } catch (Exception e) { // don't care } return super.cancel(); } // returns memory used in kB (using Debug MemoryInfo, *not* ActivityManager MemoryInfo) private int getMemoryUsage() { MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(new int[]{android.os.Process.myPid()}); // "Pss" is scaled, and it's probably the best thing to measure here; consider "PrivateDirty" too? // see http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-android return memoryInfoArray[0].dalvikPss; // "proportional set size", an estimate of how much memory the app is using // dalvikPrivateDirty: the memory that would be freed by the java virtual machine if the process is killed // dalvikSharedDirty: the shared memory used by the java virtual machine, not freed if the process is killed } // returns wifi strength % (0.0 to 1.0) private double getWifiStrength() { WifiInfo wifiInfo = wifiManager.getConnectionInfo(); int wifiStrengthInt = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), 101); // wifi strength, 0 to 100 return ((double)wifiStrengthInt) / 100.0; } private BroadcastReceiver batteryInfoReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { synchronized (pointPerformanceData) { int rawlevel = intent.getIntExtra("level", -1); double scale = intent.getIntExtra("scale", -1); double level = -1; if (rawlevel >= 0 && scale > 0) level = rawlevel / scale; pointPerformanceData.setBatteryLevel(level); } } } }; private PhoneStateListener phoneStateListener = new PhoneStateListener() { @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { int cellNetwork = telephonyManager.getNetworkType(); StringBuilder cellValuesBuilder = new StringBuilder(); // fastest way to concatenate strings // get the cell signal values via string String signalStrengthString = signalStrength.toString(); String[] parts = signalStrengthString.split(" "); /* 2.3.7: -------------------------------------------------------------------------------------------------- * [1] [2] [3] [4] [5] [6] [7] [8] * GsmSignalStrength, GsmBitErrorRate, CdmaDbm, CdmaEcio, EvdoDbm, EvdoEcio, EvdoSnr, (isGsm?"gsm":"cdma") * 4.0.3: -------------------------------------------------------------------------------------------------- * [1] [2] [3] [4] [5] [6] [7] [8] * GsmSignalStrength, GsmBitErrorRate, CdmaDbm, CdmaEcio, EvdoDbm, EvdoEcio, EvdoSnr, LteSignalStrength, * [9] [10] [11] [12] [13] * LteRsrp, LteRsrq, LteRssnr, LteCqi, (isGsm?"gsm|lte":"cdma")); */ // measuring LTE signal strength wasn't added until API 17; this is a different way to do it with an older API if (cellNetwork == 13 /* NETWORK_TYPE_LTE */) { if (parts.length >= 14) { cellValuesBuilder.append(parts[8]); // LteSignalStrength cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[9]); // LteRsrp cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[10]); // LteRsrq cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[11]); // LteRssnr cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[12]); // LteCqi } } // there are lots of different network types that make up GSM; use phone type instead else if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { /* Taken from "3GPP TS 27.007", v6.3.0 document: * GsmSignalStrength dBm * 0 -113 or less (worst) * 1 -111 * 2 ... 30 -109 ... -53 * 31 -51 (best) * 99 (unknown) */ int cellDbm = 0; int gsmSignalStrength = signalStrength.getGsmSignalStrength(); if (gsmSignalStrength != 99) cellDbm = -113 + (gsmSignalStrength * 2); // dBm (higher is better) cellValuesBuilder.append(cellDbm); // calculated GSM dBm: 0 (unknown) OR -131 (worst) to -51 (best) cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[1]); // GsmSignalStrength: 99 (unknown) OR 0 (worst) to 31 (best) cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[2]); // GsmBitErrorRate: 99 (unknown) OR 0 (best) to 7 (worst) } // there are lots of different network types that make up CDMA; use phone type instead else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { switch (cellNetwork) { // three network types are used for EVDO case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case 12 /* NETWORK_TYPE_EVDO_B */: cellValuesBuilder.append(parts[5]); // EVDO dBm (higher is better) cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[6]); // EVDO Ec/Io dB*10 (higher is better) break; // any other network types are CDMA default: cellValuesBuilder.append(parts[3]); // CDMA dBm (higher is better) cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[4]); // CDMA Ec/Io, dB*10 (higher is better) break; } cellValuesBuilder.append(" "); cellValuesBuilder.append(parts[7]); // SNR: -1 (unknown) OR 0 (worst/no signal) to 8 (best) } String cellValues = cellValuesBuilder.toString(); pointPerformanceData.setCellData(cellNetwork, cellValues); } }; }