/* 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.mobiperf; import android.content.Context; import android.content.Intent; import com.mobiperf.measurements.DnsLookupTask; import com.mobiperf.measurements.HttpTask; import com.mobiperf.measurements.PingTask; import com.mobiperf.measurements.TracerouteTask; import com.mobiperf.measurements.UDPBurstTask; import com.mobiperf.measurements.TCPThroughputTask; import com.mobiperf.measurements.RRCTask; import java.io.InvalidClassException; import java.util.HashMap; import java.util.Set; import java.util.concurrent.Callable; /** * Represents a scheduled measurement task. Subclasses implement functionality * for performing the actual measurement. Comparable interface allow comparison * inside the priority queue. */ @SuppressWarnings("rawtypes") public abstract class MeasurementTask implements Callable<MeasurementResult>, Comparable { // the priority queue we use puts the smallest element in the head of the queue public static final int USER_PRIORITY = Integer.MIN_VALUE; public static final int INVALID_PRIORITY = Integer.MAX_VALUE; public static final int INFINITE_COUNT = -1; protected MeasurementDesc measurementDesc; protected Context parent; /* When updating the 'progress' field, ensure that it's within the range between 0 and * Config.MAX_PROGRESS_BAR_VALUE, inclusive. Values outside this range have special meanings and * can trigger unexpected results. */ protected int progress; private static HashMap<String, Class> measurementTypes; // Maps between the type of task and its readable name private static HashMap<String, String> measurementDescToType; // Maps between the type of task and its visibility to UI private static HashMap<String, Boolean> measurementUIVisibility; // TODO(Wenjie): Static initializer for type -> Measurement map // Add new measurement types here to enable them static { measurementTypes = new HashMap<String, Class>(); measurementDescToType = new HashMap<String, String>(); measurementUIVisibility = new HashMap<String, Boolean>(); measurementTypes.put(PingTask.TYPE, PingTask.class); measurementDescToType.put(PingTask.DESCRIPTOR, PingTask.TYPE); measurementUIVisibility.put(PingTask.DESCRIPTOR, true); measurementTypes.put(HttpTask.TYPE, HttpTask.class); measurementDescToType.put(HttpTask.DESCRIPTOR, HttpTask.TYPE); measurementUIVisibility.put(HttpTask.DESCRIPTOR, true); measurementTypes.put(TracerouteTask.TYPE, TracerouteTask.class); measurementDescToType.put(TracerouteTask.DESCRIPTOR, TracerouteTask.TYPE); measurementUIVisibility.put(TracerouteTask.DESCRIPTOR, true); measurementTypes.put(DnsLookupTask.TYPE, DnsLookupTask.class); measurementDescToType.put(DnsLookupTask.DESCRIPTOR, DnsLookupTask.TYPE); measurementUIVisibility.put(DnsLookupTask.DESCRIPTOR, true); measurementTypes.put(TCPThroughputTask.TYPE, TCPThroughputTask.class); measurementDescToType.put(TCPThroughputTask.DESCRIPTOR, TCPThroughputTask.TYPE); measurementUIVisibility.put(TCPThroughputTask.DESCRIPTOR, true); measurementTypes.put(RRCTask.TYPE, RRCTask.class); measurementDescToType.put(RRCTask.DESCRIPTOR, RRCTask.TYPE); // Currently RRC task is not visible to users measurementUIVisibility.put(RRCTask.DESCRIPTOR, false); measurementTypes.put(UDPBurstTask.TYPE, UDPBurstTask.class); measurementDescToType.put(UDPBurstTask.DESCRIPTOR, UDPBurstTask.TYPE); measurementUIVisibility.put(UDPBurstTask.DESCRIPTOR, true); } /** Gets the currently available measurement descriptions*/ public static Set<String> getMeasurementNames() { return measurementDescToType.keySet(); } /** Gets the currently available measurement types*/ public static Set<String> getMeasurementTypes() { return measurementTypes.keySet(); } /** Get the type of a measurement based on its name. Type is for JSON interface only * where as measurement name is a readable string for the UI */ public static String getTypeForMeasurementName(String name) { return measurementDescToType.get(name); } /** * Get UI visibility for a measurement task */ public static boolean getVisibilityForMeasurementName(String name) { return (boolean)(measurementUIVisibility.get(name)); } public static Class getTaskClassForMeasurement(String type) { return measurementTypes.get(type); } /* This is put here for consistency that all MeasurementTask should * have a getDescClassForMeasurement() method. However, the MeasurementDesc is abstract * and cannot be instantiated */ public static Class getDescClass() throws InvalidClassException { throw new InvalidClassException("getDescClass() should only be invoked on " + "subclasses of MeasurementTask."); } /** * @param measurementDesc * @param parent */ protected MeasurementTask(MeasurementDesc measurementDesc, Context parent) { super(); this.measurementDesc = measurementDesc; this.parent = parent; this.progress = 0; } /* Compare priority as the first order. Then compare start time.*/ @Override public int compareTo(Object t) { MeasurementTask another = (MeasurementTask) t; Long myPrority = this.measurementDesc.priority; Long anotherPriority = another.measurementDesc.priority; int priorityComparison = myPrority.compareTo(anotherPriority); if (priorityComparison == 0 && this.measurementDesc.startTime != null && another.measurementDesc.startTime != null) { return this.measurementDesc.startTime.compareTo(another.measurementDesc.startTime); } else { return priorityComparison; } } public long timeFromExecution() { return this.measurementDesc.startTime.getTime() - System.currentTimeMillis(); } public boolean isPassedDeadline() { if (this.measurementDesc.endTime == null) { return false; } else { long endTime = this.measurementDesc.endTime.getTime(); return endTime <= System.currentTimeMillis(); } } public String getMeasurementType() { return this.measurementDesc.type; } public MeasurementDesc getDescription() { return this.measurementDesc; } /** * Returns a brief human-readable descriptor of the task. */ public abstract String getDescriptor(); @Override public abstract MeasurementResult call() throws MeasurementError; /** Return the string indicating the measurement type. */ public abstract String getType(); /* Place holder in case user wants to view the progress of active measurements*/ public int getProgress() { return this.progress; } @Override public String toString() { String result = "[Measurement " + getDescriptor() + " scheduled to run at " + getDescription().startTime + "]"; return this.measurementDesc.toString(); } @Override public abstract MeasurementTask clone(); /** * Stop the measurement, even when it is running. There is no side effect * if the measurement has not started or is already finished. */ public abstract void stop(); /** * All measurement tasks must provide measurements of how much data they have * used to be fetched when the task completes. This allows us to make sure we * stay under the data limit. * * @return Data consumed, in bytes */ public abstract long getDataConsumed(); public void broadcastProgressForUser(int progress) { if (measurementDesc.priority == MeasurementTask.USER_PRIORITY) { Intent intent = new Intent(); intent.setAction(UpdateIntent.MEASUREMENT_PROGRESS_UPDATE_ACTION); intent.putExtra(UpdateIntent.PROGRESS_PAYLOAD, progress); intent.putExtra(UpdateIntent.TASK_PRIORITY_PAYLOAD, MeasurementTask.USER_PRIORITY); parent.sendBroadcast(intent); } } }