/*
* Copyright 2012 Google Inc.
*
* 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.InvalidClassException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import com.mobilyzer.MeasurementDesc;
import com.mobilyzer.MeasurementResult;
import com.mobilyzer.MeasurementResult.TaskProgress;
import com.mobilyzer.MeasurementTask;
import com.mobilyzer.PLTExecutorService;
import com.mobilyzer.UpdateIntent;
import com.mobilyzer.exceptions.MeasurementError;
import com.mobilyzer.util.Logger;
import com.mobilyzer.util.MeasurementJsonConvertor;
import com.mobilyzer.util.PhoneUtils;
/**
* Measures the Page Load Time
*/
public class PageLoadTimeTask extends MeasurementTask {
// Type name for internal use
public static final String TYPE = "pageloadtime";
// Human readable name for the task
public static final String DESCRIPTOR = "Page Load Time";
private long duration;
private long startTimeFilter; //used in intent filter
private volatile ArrayList<String> navigationTimingResults;
private volatile ArrayList<String> resourceTimingResults;
// private long dataConsumed;
/**
* The description of PageLoadTime measurement
*/
public static class PageLoadTimeDesc extends MeasurementDesc {
public String url;
public boolean spdyTest;
public PageLoadTimeDesc(String key, Date startTime, Date endTime, double intervalSec,
long count, long priority, int contextIntervalSec, Map<String, String> params) {
super(PageLoadTimeTask.TYPE, key, startTime, endTime, intervalSec, count, priority,
contextIntervalSec, params);
initializeParams(params);
if (this.url == null) {
throw new InvalidParameterException("PageLoadTimeTask cannot be created"
+ " due to null url string");
}
}
/*
* @see com.google.wireless.speed.speedometer.MeasurementDesc#getType()
*/
@Override
public String getType() {
return PageLoadTimeTask.TYPE;
}
@Override
protected void initializeParams(Map<String, String> params) {
if (params == null) {
return;
}
this.url = params.get("url");
if(params.get("spdy").equals("True")){
this.spdyTest=true;
}else{
this.spdyTest=false;
}
}
protected PageLoadTimeDesc(Parcel in) {
super(in);
url = in.readString();
spdyTest=in.readByte() != 0;
}
public static final Parcelable.Creator<PageLoadTimeDesc> CREATOR =
new Parcelable.Creator<PageLoadTimeDesc>() {
public PageLoadTimeDesc createFromParcel(Parcel in) {
return new PageLoadTimeDesc(in);
}
public PageLoadTimeDesc[] newArray(int size) {
return new PageLoadTimeDesc[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(url);
dest.writeByte((byte) (spdyTest ? 1 : 0));
}
}
public PageLoadTimeTask(MeasurementDesc desc) {
super(new PageLoadTimeDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec,
desc.count, desc.priority, desc.contextIntervalSec, desc.parameters));
navigationTimingResults=new ArrayList<String>();
resourceTimingResults= new ArrayList<String>();
startTimeFilter=System.currentTimeMillis();
// dataConsumed=0;
this.duration=1000*60*4;
}
protected PageLoadTimeTask(Parcel in) {
super(in);
duration = in.readLong();
}
public static final Parcelable.Creator<PageLoadTimeTask> CREATOR =
new Parcelable.Creator<PageLoadTimeTask>() {
public PageLoadTimeTask createFromParcel(Parcel in) {
return new PageLoadTimeTask(in);
}
public PageLoadTimeTask[] newArray(int size) {
return new PageLoadTimeTask[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(duration);
}
/**
* Returns a copy of the PLTTask
*/
@Override
public MeasurementTask clone() {
MeasurementDesc desc = this.measurementDesc;
PageLoadTimeDesc newDesc =
new PageLoadTimeDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count,
desc.priority, desc.contextIntervalSec, desc.parameters);
return new PageLoadTimeTask(newDesc);
}
public synchronized ArrayList<String> getNavigationTimingResults(){
return navigationTimingResults;
}
public synchronized ArrayList<String> getResourceTimingResults(){
return resourceTimingResults;
}
public synchronized boolean isDone(){
if(((PageLoadTimeDesc)getDescription()).spdyTest){
if(resourceTimingResults.size()>2 && navigationTimingResults.size()==2){
return true;
}
}else{
if(resourceTimingResults.size()>2 && navigationTimingResults.size()==1){
return true;
}
}
return false;
}
@Override
public MeasurementResult[] call() throws MeasurementError {
MeasurementResult[] mrArray = new MeasurementResult[1];
PageLoadTimeDesc taskDesc = (PageLoadTimeDesc) this.measurementDesc;
Intent newintent = new Intent(PhoneUtils.getGlobalContext(), PLTExecutorService.class);
newintent.putExtra(UpdateIntent.PLT_TASK_PAYLOAD_URL, taskDesc.url);
newintent.putExtra(UpdateIntent.PLT_TASK_PAYLOAD_TEST_TYPE, taskDesc.spdyTest);
newintent.putExtra(UpdateIntent.PLT_TASK_PAYLOAD_STARTTIME, startTimeFilter);
PhoneUtils.getGlobalContext().startService(newintent);
Logger.d("ashkan_plt: PLT Test, Sending broadcast to start PLTExecutorService");
IntentFilter filter = new IntentFilter();
filter.addAction((UpdateIntent.PLT_MEASUREMENT_ACTION)+startTimeFilter);
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals((UpdateIntent.PLT_MEASUREMENT_ACTION)+startTimeFilter)) {
Logger.d("ashkan_plt: PageLoadTimeTask: "+intent.getAction() + " RECEIVED");
if (intent.hasExtra(UpdateIntent.PLT_TASK_PAYLOAD_RESULT_NAV)){
String navigationStr=intent.getStringExtra(UpdateIntent.PLT_TASK_PAYLOAD_RESULT_NAV).substring(20);
navigationTimingResults.add(navigationStr);
// if(intent.hasExtra(UpdateIntent.PLT_TASK_PAYLOAD_BYTE_USED)){
//// dataConsumed+=intent.getIntExtra(UpdateIntent.PLT_TASK_PAYLOAD_BYTE_USED, 0);
//// Logger.d("ashkan_plt: total data consumed: "+dataConsumed*2);
// }
Logger.d("ashkan_plt: >>>>navigationTimingResults: "+navigationTimingResults.size());
}else if(intent.hasExtra(UpdateIntent.PLT_TASK_PAYLOAD_RESULT_RES)){
String resrourcesStr=intent.getStringExtra(UpdateIntent.PLT_TASK_PAYLOAD_RESULT_RES).substring(18);
String[] resourcesArray=resrourcesStr.split("mobilyzer_resource");
for (String res: resourcesArray){
if(res.length()<3){
continue;
}
resourceTimingResults.add(res);
}
Logger.d("ashkan_plt: >>>>resourceTimingResults: "+resrourcesStr.length()+" "+resourceTimingResults.size());
}
}
}
};
PhoneUtils.getGlobalContext().registerReceiver(broadcastReceiver, filter);
for(int i=0;i<60*3;i++){
if(isDone()){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Logger.e("ashkan_plt: PLTTask isDone");
PhoneUtils.getGlobalContext().unregisterReceiver(broadcastReceiver);
if(isDone()){
Logger.i("ashkan_plt: Successfully measured PLT");
PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
MeasurementResult result = new MeasurementResult(
phoneUtils.getDeviceInfo().deviceId,
phoneUtils.getDeviceProperty(this.getKey()),
PageLoadTimeTask.TYPE, System.currentTimeMillis() * 1000,
TaskProgress.COMPLETED, this.measurementDesc);
if(taskDesc.spdyTest){
result.addResult("navigationTimingResults_0", getNavigationTimingResults().get(0));
result.addResult("navigationTimingResults_1", getNavigationTimingResults().get(1));
int res_index=0;
for(String resResults: getResourceTimingResults()){
result.addResult("resource_"+res_index, resResults);
res_index++;
}
}else{
result.addResult("navigationTimingResults_0", getNavigationTimingResults().get(0));
int res_index=0;
for(String resResults: getResourceTimingResults()){
result.addResult("resource_"+res_index, resResults);
res_index++;
}
}
Logger.i(MeasurementJsonConvertor.toJsonString(result));
mrArray[0]=result;
}else{
Logger.i("ashkan_plt: Not all the data is collected for this PLT measurement");
PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
MeasurementResult result = new MeasurementResult(
phoneUtils.getDeviceInfo().deviceId,
phoneUtils.getDeviceProperty(this.getKey()),
PageLoadTimeTask.TYPE, System.currentTimeMillis() * 1000,
TaskProgress.FAILED, this.measurementDesc);
int nav_index=0;
for(String navResults: getNavigationTimingResults()){
result.addResult("navigationTimingResults_"+nav_index, navResults);
nav_index++;
}
int res_index=0;
for(String resResults: getResourceTimingResults()){
result.addResult("resource_"+res_index, resResults);
res_index++;
}
Logger.i(MeasurementJsonConvertor.toJsonString(result));
mrArray[0]=result;
}
PhoneUtils.getGlobalContext().stopService(new Intent(PhoneUtils.getGlobalContext(), PLTExecutorService.class));
return mrArray;
}
@SuppressWarnings("rawtypes")
public static Class getDescClass() throws InvalidClassException {
return PageLoadTimeDesc.class;
}
@Override
public String getType() {
return PageLoadTimeTask.TYPE;
}
@Override
public String getDescriptor() {
return DESCRIPTOR;
}
@Override
public String toString() {
return null;
}
@Override
public boolean stop() {
// There is nothing we need to do to stop the PLT measurement
return false;
}
@Override
public long getDuration() {
return this.duration;
}
@Override
public void setDuration(long newDuration) {
if (newDuration < 0) {
this.duration = 0;
} else {
this.duration = newDuration;
}
}
/**
* Since it is hard to get the amount of data sent directly, use a fixed value. The data consumed
* is usually small, and the fixed value is a conservative estimate.
*
* TODO find a better way to get this value
*/
@Override
public long getDataConsumed() {
long avgTotalPageSize=1024*1024;
if (((PageLoadTimeDesc)getDescription()).spdyTest){
return 2*avgTotalPageSize;
}else{
return avgTotalPageSize;
}
}
}