Merge "camera2: Update RobustnessTest timeouts." into lmp-mr1-dev
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ac1ad03..e67a5c1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1275,8 +1275,7 @@
</activity>
- <!-- TODO: enable when the test can be executed without leaving marks -->
- <!-- activity android:name=".managedprovisioning.ByodFlowTestActivity"
+ <activity android:name=".managedprovisioning.ByodFlowTestActivity"
android:launchMode="singleTask"
android:label="@string/provisioning_byod">
<intent-filter>
@@ -1289,12 +1288,13 @@
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_managed_provisioning" />
<meta-data android:name="test_required_features" android:value="android.software.managed_users:android.software.device_admin" />
- </activity-->
+ </activity>
<activity android:name=".managedprovisioning.ByodHelperActivity">
<intent-filter>
<action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
<action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
+ <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK" />
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</activity>
@@ -1399,6 +1399,15 @@
android:value="android.software.live_tv" />
</activity>
+ <activity android:name=".screenpinning.ScreenPinningTestActivity"
+ android:label="@string/screen_pinning_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_other" />
+ </activity>
+
<activity android:name=".tv.MockTvInputSettingsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/apps/CtsVerifier/assets/scripts/execute_power_tests.py b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
index d1c2dac..d07cb36 100755
--- a/apps/CtsVerifier/assets/scripts/execute_power_tests.py
+++ b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
@@ -25,57 +25,108 @@
import pkgutil
import threading
import Queue
+import traceback
+import math
+import bisect
+from bisect import bisect_left
-# queue to signal thread to exit
-signal_exit_q = Queue.Queue()
-signal_abort = Queue.Queue()
+"""
+scipy, numpy and matplotlib are python packages that can be installed
+from: http://www.scipy.org/
+
+"""
+import scipy
+import matplotlib.pyplot as plt
# let this script know about the power monitor implementations
sys.path = [os.path.basename(__file__)] + sys.path
-available_monitors = [name for _, name, _ in pkgutil.iter_modules(
- [os.path.join(os.path.dirname(__file__),'power_monitors')]) if not name.startswith('_')]
+available_monitors = [
+ name
+ for _, name, _ in pkgutil.iter_modules(
+ [os.path.join(os.path.dirname(__file__), "power_monitors")])
+ if not name.startswith("_")]
-APK = os.path.join( os.path.dirname(__file__), '..', "CtsVerifier.apk")
+APK = os.path.join(os.path.dirname(__file__), "..", "CtsVerifier.apk")
FLAGS = flags.FLAGS
-# whether to use a strict delay to ensure screen is off, or attempt to use power measurements
-USE_STRICT_DELAY = False
-if USE_STRICT_DELAY:
- DELAY_SCREEN_OFF = 30.0
-else:
- DELAY_SCREEN_OFF = 2.0
+# DELAY_SCREEN_OFF is the number of seconds to wait for baseline state
+DELAY_SCREEN_OFF = 20.0
# whether to log data collected to a file for each sensor run:
LOG_DATA_TO_FILE = True
logging.getLogger().setLevel(logging.ERROR)
+
def do_import(name):
"""import a module by name dynamically"""
mod = __import__(name)
- components = name.split('.')
+ components = name.split(".")
for comp in components[1:]:
mod = getattr(mod, comp)
return mod
+class PowerTestException(Exception):
+ """
+ Definition of specialized Exception class for CTS power tests
+ """
+ def __init__(self, message):
+ self._error_message = message
+ def __str__(self):
+ return self._error_message
class PowerTest:
- """Class to run a suite of power tests"""
+ """Class to run a suite of power tests. This has methods for obtaining
+ measurements from the power monitor (through the driver) and then
+ processing it to determine baseline and AP suspend state and
+ measure ampere draw of various sensors.
+ Ctrl+C causes a keyboard interrupt exception which terminates the test."""
# Thresholds for max allowed power usage per sensor tested
- MAX_ACCEL_POWER = 0.08 # Amps
- MAX_MAG_POWER = 0.08 # Amps
- MAX_GYRO_POWER = 0.08 # Amps
- MAX_SIGMO_POWER = 0.08 # Amps
- MAX_STEP_COUNTER_POWER = 0.08 # Amps
- MAX_STEP_DETECTOR_POWER = 0.08 # Amps
+ # TODO: Accel, Mag and Gyro have no maximum power specified in the CDD;
+ # the following numbers are bogus and will be replaced soon by what
+ # the device reports (from Sensor.getPower())
+ MAX_ACCEL_AMPS = 0.08 # Amps
+ MAX_MAG_AMPS = 0.08 # Amps
+ MAX_GYRO_AMPS = 0.08 # Amps
+ MAX_SIGMO_AMPS = 0.08 # Amps
-
- PORT = 0 # any available port
+ # TODO: The following numbers for step counter, etc must be replaced by
+ # the numbers specified in CDD for low-power sensors. The expected current
+ # draw must be computed from the specified power and the voltage used to
+ # power the device (specified from a config file).
+ MAX_STEP_COUNTER_AMPS = 0.08 # Amps
+ MAX_STEP_DETECTOR_AMPS = 0.08 # Amps
+ # The variable EXPECTED_AMPS_VARIATION_HALF_RANGE denotes the expected
+ # variation of the ampere measurements
+ # around the mean value at baseline state. i.e. we expect most of the
+ # ampere measurements at baseline state to vary around the mean by
+ # between +/- of the number below
+ EXPECTED_AMPS_VARIATION_HALF_RANGE = 0.0005
+ # The variable THRESHOLD_BASELINE_SAMPLES_FRACTION denotes the minimum fraction of samples that must
+ # be in the range of variation defined by EXPECTED_AMPS_VARIATION_HALF_RANGE
+ # around the mean baseline for us to decide that the phone has settled into
+ # its baseline state
+ THRESHOLD_BASELINE_SAMPLES_FRACTION = 0.86
+ # The variable MAX_PERCENTILE_AP_SCREEN_OFF_AMPS denotes the maximum ampere
+ # draw that the device can consume when it has gone to suspend state with
+ # one or more sensors registered and batching samples (screen and AP are
+ # off in this case)
+ MAX_PERCENTILE_AP_SCREEN_OFF_AMPS = 0.030 # Amps
+ # The variable PERCENTILE_MAX_AP_SCREEN_OFF denotes the fraction of ampere
+ # measurements that must be below the specified maximum amperes
+ # MAX_PERCENTILE_AP_SCREEN_OFF_AMPS for us to decide that the phone has
+ # reached suspend state.
+ PERCENTILE_MAX_AP_SCREEN_OFF = 0.95
DOMAIN_NAME = "/android/cts/powertest"
+ # SAMPLE_COUNT_NOMINAL denotes the typical number of measurements of amperes
+ # to collect from the power monitor
SAMPLE_COUNT_NOMINAL = 1000
+ # RATE_NOMINAL denotes the nominal frequency at which ampere measurements
+ # are taken from the monsoon power monitor
RATE_NOMINAL = 100
+ ENABLE_PLOTTING = False
REQUEST_EXTERNAL_STORAGE = "EXTERNAL STORAGE?"
REQUEST_EXIT = "EXIT"
@@ -87,103 +138,157 @@
REQUEST_SCREEN_OFF = "SCREEN OFF"
REQUEST_SHOW_MESSAGE = "MESSAGE %s"
+ NEGATIVE_AMPERE_ERROR_MESSAGE = (
+ "Negative ampere draw measured, possibly due to power "
+ "supply from USB cable. Check the setup of device and power "
+ "monitor to make sure that the device is not connected "
+ "to machine via USB directly. The device should be "
+ "connected to the USB slot in the power monitor. It is okay "
+ "to change the wiring when the test is in progress.")
- def __init__(self):
+
+ def __init__(self, max_baseline_amps):
+ """
+ Args:
+ max_baseline_amps: The maximum value of baseline amperes
+ that we expect the device to consume at baseline state.
+ This can be different between models of phones.
+ """
power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
testid = time.strftime("%d_%m_%Y__%H__%M_%S")
self._power_monitor = power_monitors.Power_Monitor(log_file_id = testid)
+ self._tcp_connect_port = 0 # any available port
print ("Establishing connection to device...")
self.setUsbEnabled(True)
status = self._power_monitor.GetStatus()
self._native_hz = status["sampleRate"] * 1000
+ # the following describes power test being run (i.e on what sensor
+ # and what type of test. This is used for logging.
self._current_test = "None"
- self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE,
- reportErrors=True)
+ self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE)
+ self._max_baseline_amps = max_baseline_amps
def __del__(self):
self.finalize()
def finalize(self):
"""To be called upon termination of host connection to device"""
- if PowerTest.PORT > 0:
- # tell device side to exit connection loop, and remove the forwarding connection
- self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors=False)
- self.executeLocal("adb forward --remove tcp:%d" % PowerTest.PORT)
- PowerTest.PORT = 0
+ if self._tcp_connect_port > 0:
+ # tell device side to exit connection loop, and remove the forwarding
+ # connection
+ self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors = False)
+ self.executeLocal("adb forward --remove tcp:%d" % self._tcp_connect_port)
+ self._tcp_connect_port = 0
if self._power_monitor:
self._power_monitor.Close()
self._power_monitor = None
- def _send(self, msg, report_errors=True):
+ def _send(self, msg, report_errors = True):
"""Connect to the device, send the given command, and then disconnect"""
- if PowerTest.PORT == 0:
+ if self._tcp_connect_port == 0:
# on first attempt to send a command, connect to device via any open port number,
# forwarding that port to a local socket on the device via adb
logging.debug("Seeking port for communication...")
# discover an open port
dummysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dummysocket.bind(("localhost", 0))
- (_, PowerTest.PORT) = dummysocket.getsockname()
+ (_, self._tcp_connect_port) = dummysocket.getsockname()
dummysocket.close()
- assert(PowerTest.PORT > 0)
+ assert(self._tcp_connect_port > 0)
+
status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
- (PowerTest.PORT, PowerTest.DOMAIN_NAME))
- if report_errors:
- self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
- logging.info("Forwarding requests over local port %d" % PowerTest.PORT)
+ (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+ # If the status !=0, then the host machine is unable to
+ # forward requests to client over adb. Ending the test and logging error message
+ # to the console on the host.
+ self.endTestIfLostConnection(
+ status != 0,
+ "Unable to forward requests to client over adb")
+ logging.info("Forwarding requests over local port %d",
+ self._tcp_connect_port)
link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
logging.debug("Connecting to device...")
- link.connect (("localhost", PowerTest.PORT))
+ link.connect(("localhost", self._tcp_connect_port))
logging.debug("Connected.")
+ except socket.error as serr:
+ print "Socket connection error: ", serr
+ print "Finalizing and exiting the test"
+ self.endTestIfLostConnection(
+ report_errors,
+ "Unable to communicate with device: connection refused")
except:
- if report_errors:
- self.reportErrorIf(True, msg="Unable to communicate with device: connection refused")
- logging.debug("Sending '%s'" % msg)
+ print "Non socket-related exception at this block in _send(); re-raising now."
+ raise
+ logging.debug("Sending '%s'", msg)
link.sendall(msg)
logging.debug("Getting response...")
response = link.recv(4096)
- logging.debug("Got response '%s'" % response)
+ logging.debug("Got response '%s'", response)
link.close()
return response
def queryDevice(self, query):
"""Post a yes/no query to the device, return True upon successful query, False otherwise"""
- logging.info("Querying device with '%s'" % query)
+ logging.info("Querying device with '%s'", query)
return self._send(query) == "OK"
# TODO: abstract device communication (and string commands) into its own class
- def executeOnDevice(self, cmd , reportErrors=True):
+ def executeOnDevice(self, cmd, reportErrors = True):
"""Execute a (string) command on the remote device"""
- return self._send(cmd , reportErrors)
+ return self._send(cmd, reportErrors)
- def executeLocal(self, cmd, check_status=True):
+ def executeLocal(self, cmd, check_status = True):
"""execute a shell command locally (on the host)"""
from subprocess import call
- status = call(cmd.split(' '))
+ status = call(cmd.split(" "))
if status != 0 and check_status:
- logging.error("Failed to execute \"%s\"" % cmd)
+ logging.error("Failed to execute \"%s\"", cmd)
else:
- logging.debug("Executed \"%s\"" % cmd)
+ logging.debug("Executed \"%s\"", cmd)
return status
- def reportErrorIf(self, condition, msg):
+ def reportErrorRaiseExceptionIf(self, condition, msg):
"""Report an error condition to the device if condition is True.
- Will raise an exception on the device if condition is True"""
+ Will raise an exception on the device if condition is True.
+ Args:
+ condition: If true, this reports error
+ msg: Message related to exception
+ Raises:
+ A PowerTestException encapsulating the message provided in msg
+ """
if condition:
try:
logging.error("Exiting on error: %s" % msg)
- self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg), False)
+ self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg),
+ reportErrors = True)
except:
-
- logging.error("Unable to communicate with device to report error: %s" % msg)
+ logging.error("Unable to communicate with device to report "
+ "error: %s" % msg)
self.finalize()
sys.exit(msg)
- raise Exception(msg)
+ raise PowerTestException(msg)
- def setUsbEnabled(self, enabled, verbose=True):
+ def endTestIfLostConnection(self, lost_connection, error_message):
+ """
+ This function ends the test if lost_connection was true,
+ which indicates that the connection to the device was lost.
+ Args:
+ lost_connection: boolean variable, if True it indicates that
+ connection to device was lost and the test must be terminated.
+ error_message: String to print to the host console before exiting the test
+ (if lost_connection is True)
+ Returns:
+ None.
+ """
+ if lost_connection:
+ logging.error(error_message)
+ self.finalize()
+ sys.exit(error_message)
+
+ def setUsbEnabled(self, enabled, verbose = True):
if enabled:
val = 1
else:
@@ -193,6 +298,7 @@
# Sometimes command won't go through first time, particularly if immediately after a data
# collection, so allow for retries
+ # TODO: Move this retry mechanism to the power monitor driver.
status = self._power_monitor.GetStatus()
while status is None and tries < 5:
tries += 1
@@ -203,59 +309,230 @@
status = self._power_monitor.GetStatus()
if enabled:
- if verbose: print("...USB enabled, waiting for device")
- self.executeLocal ("adb wait-for-device")
- if verbose: print("...device online")
+ if verbose:
+ print("...USB enabled, waiting for device")
+ self.executeLocal("adb wait-for-device")
+ if verbose:
+ print("...device online")
else:
- if verbose: logging.info("...USB disabled")
+ if verbose:
+ logging.info("...USB disabled")
# re-establish port forwarding
- if enabled and PowerTest.PORT > 0:
+ if enabled and self._tcp_connect_port > 0:
status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
- (PowerTest.PORT, PowerTest.DOMAIN_NAME))
- self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
+ (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+ self.reportErrorRaiseExceptionIf(status != 0, msg = "Unable to forward requests to client over adb")
- def waitForScreenOff(self):
- # disconnect of USB will cause screen to go on, so must wait (1 second more than screen off
- # timeout)
- if USE_STRICT_DELAY:
- time.sleep(DELAY_SCREEN_OFF)
- return
+ def computeBaselineState(self, measurements):
+ """
+ Args:
+ measurements: List of floats containing ampere draw measurements
+ taken from the monsoon power monitor.
+ Must be atleast 100 measurements long
+ Returns:
+ A tuple (isBaseline, mean_current) where isBaseline is a
+ boolean that is True only if the baseline state for the phone is
+ detected. mean_current is an estimate of the average baseline
+ current for the device, which is valid only if baseline state is
+ detected (if not, it is set to -1).
+ """
- # need at least 100 sequential clean low-power measurements to know screen is off
- THRESHOLD_COUNT_LOW_POWER = 100
- CURRENT_LOW_POWER_THRESHOLD = 0.060 # Amps
- TIMEOUT_SCREEN_OFF = 30 # this many tries at most
- count_good = 0
- tries = 0
- print("Waiting for screen off and application processor in suspend mode...")
- while count_good < THRESHOLD_COUNT_LOW_POWER:
- measurements = self.collectMeasurements(THRESHOLD_COUNT_LOW_POWER,
- PowerTest.RATE_NOMINAL,
- ensure_screen_off=False,
- verbose=False)
- count_good = len([m for m in measurements
- if m < CURRENT_LOW_POWER_THRESHOLD])
- tries += 1
- if count_good < THRESHOLD_COUNT_LOW_POWER and measurements:
- print("Current usage: %.2f mAmps. Device is probably not in suspend mode. Waiting..." %
- (1000.0*(sum(measurements)/len(measurements))))
- if tries >= TIMEOUT_SCREEN_OFF:
- # TODO: dump the state of sensor service to identify if there are features using sensors
- self.reportErrorIf(tries>=TIMEOUT_SCREEN_OFF,
- msg="Unable to determine application processor suspend mode status.")
+ # Looks at the measurements to see if it is in baseline state
+ if len(measurements) < 100:
+ print(
+ "Need at least 100 measurements to determine if baseline state has"
+ " been reached")
+ return (False, -1)
+
+ # Assumption: At baseline state, the power profile is Gaussian distributed
+ # with low-variance around the mean current draw.
+ # Ideally we should find the mode from a histogram bin to find an estimated mean.
+ # Assuming here that the median is very close to this value; later we check that the
+ # variance of the samples is low enough to validate baseline.
+ sorted_measurements = sorted(measurements)
+ number_measurements = len(measurements)
+ if not number_measurements % 2:
+ median_measurement = (sorted_measurements[(number_measurements - 1) / 2] +
+ sorted_measurements[(number_measurements + 1) / 2]) / 2
+ else:
+ median_measurement = sorted_measurements[number_measurements / 2]
+
+ # Assume that at baseline state, a large fraction of power measurements
+ # are within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE milliAmperes of
+ # the average baseline current. Find all such measurements in the
+ # sorted measurement vector.
+ left_index = (
+ bisect_left(
+ sorted_measurements,
+ median_measurement -
+ PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+ right_index = (
+ bisect_left(
+ sorted_measurements,
+ median_measurement +
+ PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+
+ average_baseline_amps = scipy.mean(
+ sorted_measurements[left_index: (right_index - 1)])
+
+ detected_baseline = True
+ # We enforce that a fraction of more than 'THRESHOLD_BASELINE_SAMPLES_FRACTION'
+ # of samples must be within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE
+ # milliAmperes of the mean baseline current, which we have estimated as
+ # the median.
+ if ((right_index - left_index) < PowerTest.THRESHOLD_BASELINE_SAMPLES_FRACTION * len(
+ measurements)):
+ detected_baseline = False
+
+ # We check for the maximum limit of the expected baseline
+ if median_measurement > self._max_baseline_amps:
+ detected_baseline = False
+ if average_baseline_amps < 0:
+ print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+ detected_baseline = False
+
+ print("%s baseline state" % ("Could detect" if detected_baseline else "Could NOT detect"))
+ print(
+ "median amps = %f, avg amps = %f, fraction of good samples = %f" %
+ (median_measurement, average_baseline_amps,
+ float(right_index - left_index) / len(measurements)))
+ if PowerTest.ENABLE_PLOTTING:
+ plt.plot(measurements)
+ plt.show()
+ print("To continue test, please close the plot window manually.")
+ return (detected_baseline, average_baseline_amps)
+
+ def isApInSuspendState(self, measurements_amps, nominal_max_amps, test_percentile):
+ """
+ This function detects AP suspend and display off state of phone
+ after a sensor has been registered.
+
+ Because the power profile can be very different between sensors and
+ even across builds, it is difficult to specify a tight threshold for
+ mean current draw or mandate that the power measurements must have low
+ variance. We use a criteria that allows for a certain fraction of
+ peaks in power spectrum and checks that test_percentile fraction of
+ measurements must be below the specified value nominal_max_amps
+ Args:
+ measurements_amps: amperes draw measurements from power monitor
+ test_percentile: the fraction of measurements we require to be below
+ a specified amps value
+ nominal_max_amps: the specified value of the max current draw
+ Returns:
+ returns a boolean which is True if and only if the AP suspend and
+ display off state is detected
+ """
+ count_good = len([m for m in measurements_amps if m < nominal_max_amps])
+ count_negative = len([m for m in measurements_amps if m < 0])
+ if count_negative > 0:
+ print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+ return False;
+ return count_good > test_percentile * len(measurements_amps)
+
+ def getBaselineState(self):
+ """This function first disables all sensors, then collects measurements
+ through the power monitor and continuously evaluates if baseline state
+ is reached. Once baseline state is detected, it returns a tuple with
+ status information. If baseline is not detected in a preset maximum
+ number of trials, it returns as well.
+
+ Returns:
+ Returns a tuple (isBaseline, mean_current) where isBaseline is a
+ boolean that is True only if the baseline state for the phone is
+ detected. mean_current is an estimate of the average baseline current
+ for the device, which is valid only if baseline state is detected
+ (if not, it is set to -1)
+ """
+ self.setPowerOn("ALL", False)
+ self.setUsbEnabled(False)
+ print("Waiting %d seconds for baseline state" % DELAY_SCREEN_OFF)
+ time.sleep(DELAY_SCREEN_OFF)
+
+ MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION = 5 # seconds
+ NUMBER_MEASUREMENTS_BASELINE_DETECTION = (
+ PowerTest.RATE_NOMINAL *
+ MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION)
+ NUMBER_MEASUREMENTS_BASELINE_VERIFICATION = (
+ NUMBER_MEASUREMENTS_BASELINE_DETECTION * 5)
+ MAX_TRIALS = 50
+
+ collected_baseline_measurements = False
+
+ for tries in xrange(MAX_TRIALS):
+ print("Trial number %d of %d..." % (tries, MAX_TRIALS))
+ measurements = self.collectMeasurements(
+ NUMBER_MEASUREMENTS_BASELINE_DETECTION, PowerTest.RATE_NOMINAL,
+ verbose = False)
+ if self.computeBaselineState(measurements)[0] is True:
+ collected_baseline_measurements = True
break
- if DELAY_SCREEN_OFF:
- # add additional delay time if necessary
- time.sleep(DELAY_SCREEN_OFF)
- print("...Screen off and device in suspend mode.")
- def collectMeasurements(self, measurementCount, rate , ensure_screen_off=True, verbose=True,
- plot_data = False):
- assert(measurementCount > 0)
- decimate_by = self._native_hz / rate or 1
- if ensure_screen_off:
- self.waitForScreenOff()
- print ("Taking measurements...")
+ if collected_baseline_measurements:
+ print("Verifying baseline state over a longer interval "
+ "in order to double check baseline state")
+ measurements = self.collectMeasurements(
+ NUMBER_MEASUREMENTS_BASELINE_VERIFICATION, PowerTest.RATE_NOMINAL,
+ verbose = False)
+ self.reportErrorRaiseExceptionIf(
+ not measurements, "No background measurements could be taken")
+ retval = self.computeBaselineState(measurements)
+ if retval[0]:
+ print("Verified baseline.")
+ if measurements and LOG_DATA_TO_FILE:
+ with open("/tmp/cts-power-tests-background-data.log", "w") as f:
+ for m in measurements:
+ f.write("%.4f\n" % m)
+ return retval
+ else:
+ return (False, -1)
+
+ def waitForApSuspendMode(self):
+ """This function repeatedly collects measurements until AP suspend and display off
+ mode is detected. After a maximum number of trials, if this state is not reached, it
+ raises an error.
+ Returns:
+ boolean which is True if device was detected to be in suspend state
+ Raises:
+ Power monitor-related exception
+ """
+ print("waitForApSuspendMode(): Sleeping for %d seconds" % DELAY_SCREEN_OFF)
+ time.sleep(DELAY_SCREEN_OFF)
+
+ NUMBER_MEASUREMENTS = 200
+ # Maximum trials for which to collect measurements to get to Ap suspend
+ # state
+ MAX_TRIALS = 50
+
+ got_to_suspend_state = False
+ for count in xrange(MAX_TRIALS):
+ print ("waitForApSuspendMode(): Trial %d of %d" % (count, MAX_TRIALS))
+ measurements = self.collectMeasurements(NUMBER_MEASUREMENTS,
+ PowerTest.RATE_NOMINAL,
+ verbose = False)
+ if self.isApInSuspendState(
+ measurements, PowerTest.MAX_PERCENTILE_AP_SCREEN_OFF_AMPS,
+ PowerTest.PERCENTILE_MAX_AP_SCREEN_OFF):
+ got_to_suspend_state = True
+ break
+ self.reportErrorRaiseExceptionIf(
+ got_to_suspend_state is False,
+ msg = "Unable to determine application processor suspend mode status.")
+ print("Got to AP suspend state")
+ return got_to_suspend_state
+
+ def collectMeasurements(self, measurementCount, rate, verbose = True):
+ """Args:
+ measurementCount: Number of measurements to collect from the power
+ monitor
+ rate: The integer frequency in Hertz at which to collect measurements from
+ the power monitor
+ Returns:
+ A list containing measurements from the power monitor; that has the
+ requested count of the number of measurements at the specified rate
+ """
+ assert (measurementCount > 0)
+ decimate_by = self._native_hz / rate or 1
+
self._power_monitor.StartDataCollection()
sub_measurements = []
measurements = []
@@ -273,47 +550,70 @@
tries = 0
sub_measurements.extend(additional)
while len(sub_measurements) >= decimate_by:
- sub_avg = sum(sub_measurements) / len(sub_measurements)
+ sub_avg = sum(sub_measurements[0:decimate_by]) / decimate_by
measurements.append(sub_avg)
sub_measurements = sub_measurements[decimate_by:]
if verbose:
+ # "\33[1A\33[2K" is a special Linux console control
+ # sequence for moving to the previous line, and
+ # erasing it; and reprinting new text on that
+ # erased line.
sys.stdout.write("\33[1A\33[2K")
- print ("MEASURED[%d]: %f" % (len(measurements),measurements[-1]))
+ print ("MEASURED[%d]: %f" % (len(measurements), measurements[-1]))
finally:
self._power_monitor.StopDataCollection()
- self.reportErrorIf(measurementCount > len(measurements),
- "Unable to collect all requested measurements")
+ self.reportErrorRaiseExceptionIf(measurementCount > len(measurements),
+ "Unable to collect all requested measurements")
return measurements
- def request_user_acknowledgment(self, msg):
+ def requestUserAcknowledgment(self, msg):
"""Post message to user on screen and wait for acknowledgment"""
response = self.executeOnDevice(PowerTest.REQUEST_USER_RESPONSE % msg)
- self.reportErrorIf(response != "OK", "Unable to request user acknowledgment")
+ self.reportErrorRaiseExceptionIf(
+ response != "OK", "Unable to request user acknowledgment")
- def setTestResult (self, testname, condition, msg):
- if condition is False:
- val = "FAIL"
- elif condition is True:
- val = "PASS"
- else:
- val = condition
- print ("Test %s : %s" % (testname, val))
- response = self.executeOnDevice(PowerTest.REQUEST_SET_TEST_RESULT % (testname, val, msg))
- self.reportErrorIf(response != "OK", "Unable to send test status to Verifier")
+ def setTestResult(self, test_name, test_result, test_message):
+ """
+ Reports the result of a test to the device
+ Args:
+ test_name: name of the test
+ test_result: Boolean result of the test (True means Pass)
+ test_message: Relevant message
+ """
+ print ("Test %s : %s" % (test_name, test_result))
+
+ response = (
+ self.executeOnDevice(
+ PowerTest.REQUEST_SET_TEST_RESULT %
+ (test_name, test_result, test_message)))
+ self.reportErrorRaiseExceptionIf(
+ response != "OK", "Unable to send test status to Verifier")
def setPowerOn(self, sensor, powered_on):
response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_SWITCH %
- ({True:"ON", False:"OFF"}[powered_on], sensor))
- self.reportErrorIf(response == "ERR", "Unable to set sensor %s state" % sensor)
- logging.info("Set %s %s" % (sensor, {True:"ON", False:"OFF"}[powered_on]))
+ (("ON" if powered_on else "OFF"), sensor))
+ self.reportErrorRaiseExceptionIf(
+ response == "ERR", "Unable to set sensor %s state" % sensor)
+ logging.info("Set %s %s", sensor, ("ON" if powered_on else "OFF"))
return response
- def runPowerTest(self, sensor, max_power_allowed, user_request = None):
- if not signal_abort.empty():
- sys.exit( signal_abort.get() )
- self._current_test = "%s_Power_Test_While_%s" % (sensor,
- {True:"Under_Motion", False:"Still"}[user_request is not None])
+ def runSensorPowerTest(
+ self, sensor, max_amperes_allowed, baseline_amps, user_request = None):
+ """
+ Runs power test for a specific sensor; i.e. measures the amperes draw
+ of the phone using monsoon, with the specified sensor mregistered
+ and the phone in suspend state; and verifies that the incremental
+ consumed amperes is within expected bounds.
+ Args:
+ sensor: The specified sensor for which to run the power test
+ max_amperes_allowed: Maximum ampere draw of the device with the
+ sensor registered and device in suspend state
+ baseline_amps: The power draw of the device when it is in baseline
+ state (no sensors registered, display off, AP asleep)
+ """
+ self._current_test = ("%s_Power_Test_While_%s" % (
+ sensor, ("Under_Motion" if user_request is not None else "Still")))
try:
print ("\n\n---------------------------------")
if user_request is not None:
@@ -321,248 +621,299 @@
else:
print ("Running power test on %s while device is still." % sensor)
print ("---------------------------------")
- response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
+ response = self.executeOnDevice(
+ PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
if response == "UNAVAILABLE":
- self.setTestResult(self._current_test, condition="SKIPPED",
- msg="Sensor %s not available on this platform"%sensor)
+ self.setTestResult(
+ self._current_test, test_result = "SKIPPED",
+ test_message = "Sensor %s not available on this platform" % sensor)
self.setPowerOn("ALL", False)
if response == "UNAVAILABLE":
- self.setTestResult(self._current_test, condition="SKIPPED",
- msg="Sensor %s not available on this device"%sensor)
+ self.setTestResult(
+ self._current_test, test_result = "SKIPPED",
+ test_message = "Sensor %s not available on this device" % sensor)
return
-
- self.reportErrorIf(response != "OK", "Unable to set all sensor off")
- if not signal_abort.empty():
- sys.exit( signal_abort.get() )
+ self.reportErrorRaiseExceptionIf(response != "OK", "Unable to set all sensor off")
self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
self.setUsbEnabled(False)
- print("Collecting background measurements...")
- measurements = self.collectMeasurements( PowerTest.SAMPLE_COUNT_NOMINAL,
- PowerTest.RATE_NOMINAL,
- plot_data = True)
- if measurements and LOG_DATA_TO_FILE:
- with open( "/tmp/cts-power-tests-%s-%s-background-data.log"%(sensor,
- {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
- for m in measurements:
- f.write( "%.4f\n"%m)
- self.reportErrorIf(not measurements, "No background measurements could be taken")
- backgnd = sum(measurements) / len(measurements)
self.setUsbEnabled(True)
self.setPowerOn(sensor, True)
if user_request is not None:
print("===========================================\n" +
"==> Please follow the instructions presented on the device\n" +
- "==========================================="
- )
- self.request_user_acknowledgment(user_request)
+ "===========================================")
+ self.requestUserAcknowledgment(user_request)
self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
self.setUsbEnabled(False)
- self.reportErrorIf(response != "OK", "Unable to set sensor %s ON" % sensor)
+ self.reportErrorRaiseExceptionIf(
+ response != "OK", "Unable to set sensor %s ON" % sensor)
+
+ self.waitForApSuspendMode()
print ("Collecting sensor %s measurements" % sensor)
measurements = self.collectMeasurements(PowerTest.SAMPLE_COUNT_NOMINAL,
PowerTest.RATE_NOMINAL)
if measurements and LOG_DATA_TO_FILE:
- with open( "/tmp/cts-power-tests-%s-%s-sensor-data.log"%(sensor,
- {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
+ with open("/tmp/cts-power-tests-%s-%s-sensor-data.log" % (sensor,
+ ("Under_Motion" if user_request is not None else "Still")), "w") as f:
for m in measurements:
- f.write( "%.4f\n"%m)
+ f.write("%.4f\n" % m)
self.setUsbEnabled(True, verbose = False)
print("Saving raw data files to device...")
self.executeLocal("adb shell mkdir -p %s" % self._external_storage, False)
self.executeLocal("adb push %s %s/." % (f.name, self._external_storage))
self.setUsbEnabled(False, verbose = False)
- self.reportErrorIf(not measurements, "No measurements could be taken for %s" % sensor)
+ self.reportErrorRaiseExceptionIf(
+ not measurements, "No measurements could be taken for %s" % sensor)
avg = sum(measurements) / len(measurements)
- squared = [(m-avg)*(m-avg) for m in measurements]
+ squared = [(m - avg) * (m - avg) for m in measurements]
- import math
- stddev = math.sqrt(sum(squared)/len(squared))
- current_diff = avg - backgnd
+ stddev = math.sqrt(sum(squared) / len(squared))
+ current_diff = avg - baseline_amps
self.setUsbEnabled(True)
max_power = max(measurements) - avg
- if current_diff <= max_power_allowed:
+ if current_diff <= max_amperes_allowed:
# TODO: fail the test of background > current
- message = ("Draw is within limits. Current:%f Background:%f Measured: %f Stddev: %f Peak: %f")%\
- ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
+ message = (
+ "Draw is within limits. Sensor delta:%f mAmp Baseline:%f "
+ "mAmp Sensor: %f mAmp Stddev : %f mAmp Peak: %f mAmp") % (
+ current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+ stddev * 1000.0, max_power * 1000.0)
else:
- message = ("Draw is too high. Current:%f Background:%f Measured: %f Stddev: %f Peak: %f")%\
- ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
- self.setTestResult( testname = self._current_test,
- condition = current_diff <= max_power_allowed,
- msg = message)
- print("Result: "+message)
+ message = (
+ "Draw is too high. Current:%f Background:%f Measured: %f "
+ "Stddev: %f Peak: %f") % (
+ current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+ stddev * 1000.0, max_power * 1000.0)
+ self.setTestResult(
+ self._current_test,
+ ("PASS" if (current_diff <= max_amperes_allowed) else "FAIL"),
+ message)
+ print("Result: " + message)
except:
- import traceback
traceback.print_exc()
- self.setTestResult(self._current_test, condition="FAIL",
- msg="Exception occurred during run of test.")
-
+ self.setTestResult(self._current_test, test_result = "FAIL",
+ test_message = "Exception occurred during run of test.")
+ raise
@staticmethod
- def run_tests():
+ def runTests(max_baseline_amps):
testrunner = None
try:
- GENERIC_MOTION_REQUEST = "\n===> Please press Next and when the screen is off, keep " + \
- "the device under motion with only tiny, slow movements until the screen turns " + \
- "on again.\nPlease refrain from interacting with the screen or pressing any side " + \
- "buttons while measurements are taken."
- USER_STEPS_REQUEST = "\n===> Please press Next and when the screen is off, then move " + \
- "the device to simulate step motion until the screen turns on again.\nPlease " + \
- "refrain from interacting with the screen or pressing any side buttons while " + \
- "measurements are taken."
- testrunner = PowerTest()
- testrunner.executeOnDevice(PowerTest.REQUEST_SHOW_MESSAGE % "Connected. Running tests...")
- testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = GENERIC_MOTION_REQUEST)
- testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = USER_STEPS_REQUEST)
- testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = USER_STEPS_REQUEST)
- testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = GENERIC_MOTION_REQUEST)
- testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = GENERIC_MOTION_REQUEST)
- testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = GENERIC_MOTION_REQUEST)
- testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = None)
- testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = None)
- testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = None)
- testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = None)
- testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = None)
- testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = None)
+ GENERIC_MOTION_REQUEST = ("\n===> Please press Next and when the "
+ "screen is off, keep the device under motion with only tiny, "
+ "slow movements until the screen turns on again.\nPlease "
+ "refrain from interacting with the screen or pressing any side "
+ "buttons while measurements are taken.")
+ USER_STEPS_REQUEST = ("\n===> Please press Next and when the "
+ "screen is off, then move the device to simulate step motion "
+ "until the screen turns on again.\nPlease refrain from "
+ "interacting with the screen or pressing any side buttons "
+ "while measurements are taken.")
+ testrunner = PowerTest(max_baseline_amps)
+ testrunner.executeOnDevice(
+ PowerTest.REQUEST_SHOW_MESSAGE % "Connected. Running tests...")
+ is_baseline_success, baseline_amps = testrunner.getBaselineState()
+
+ if is_baseline_success:
+ testrunner.setUsbEnabled(True)
+ # TODO: Enable testing a single sensor
+ testrunner.runSensorPowerTest(
+ "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+ user_request = GENERIC_MOTION_REQUEST)
+ testrunner.runSensorPowerTest(
+ "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+ user_request = USER_STEPS_REQUEST)
+ testrunner.runSensorPowerTest(
+ "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+ user_request = USER_STEPS_REQUEST)
+ testrunner.runSensorPowerTest(
+ "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+ user_request = GENERIC_MOTION_REQUEST)
+ testrunner.runSensorPowerTest(
+ "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+ user_request = GENERIC_MOTION_REQUEST)
+ testrunner.runSensorPowerTest(
+ "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+ user_request = GENERIC_MOTION_REQUEST)
+ testrunner.runSensorPowerTest(
+ "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+ user_request = None)
+ testrunner.runSensorPowerTest(
+ "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+ user_request = None)
+ testrunner.runSensorPowerTest(
+ "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+ user_request = None)
+ testrunner.runSensorPowerTest(
+ "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+ user_request = None)
+ testrunner.runSensorPowerTest(
+ "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+ user_request = None)
+ testrunner.runSensorPowerTest(
+ "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+ user_request = None)
+ else:
+ print("Could not get to baseline state. This is either because "
+ "in several trials, the monitor could not measure a set "
+ "of power measurements that had the specified low "
+ "variance or the mean measurements were below the "
+ "expected value. None of the sensor power measurement "
+ " tests were performed due to not being able to detect "
+ "baseline state. Please re-run the power tests.")
+ except KeyboardInterrupt:
+ print "Keyboard interrupt from user."
+ raise
except:
import traceback
traceback.print_exc()
finally:
- signal_exit_q.put(0) # anything will signal thread to terminate
logging.info("TESTS COMPLETE")
if testrunner:
try:
testrunner.finalize()
except socket.error:
- sys.exit("============================\nUnable to connect to device under " + \
- "test. Make sure the device is connected via the usb pass-through, " + \
- "the CtsVerifier app is running the SensorPowerTest on the device, " + \
- "and USB pass-through is enabled.\n===========================")
-
+ sys.exit(
+ "===================================================\n"
+ "Unable to connect to device under test. Make sure \n"
+ "the device is connected via the usb pass-through, \n"
+ "the CtsVerifier app is running the SensorPowerTest on \n"
+ "the device, and USB pass-through is enabled.\n"
+ "===================================================")
def main(argv):
- """ Simple command-line interface for a power test application."""
- useful_flags = ["voltage", "status", "usbpassthrough",
- "samples", "current", "log", "power_monitor"]
- if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
- print __doc__.strip()
- print FLAGS.MainModuleHelp()
- return
+ """ Simple command-line interface for a power test application."""
+ useful_flags = ["voltage", "status", "usbpassthrough",
+ "samples", "current", "log", "power_monitor"]
+ if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
+ print __doc__.strip()
+ print FLAGS.MainModuleHelp()
+ return
- if FLAGS.avg and FLAGS.avg < 0:
- loggign.error("--avg must be greater than 0")
- return
+ if FLAGS.avg and FLAGS.avg < 0:
+ logging.error("--avg must be greater than 0")
+ return
- if FLAGS.voltage is not None:
- if FLAGS.voltage > 5.5:
- print("!!WARNING: Voltage higher than typical values!!!")
- try:
- response = raw_input("Voltage of %.3f requested. Confirm this is correct (Y/N)"%FLAGS.voltage)
- if response.upper() != "Y":
- sys.exit("Aborting")
- except:
- sys.exit("Aborting.")
+ if FLAGS.voltage is not None:
+ if FLAGS.voltage > 5.5:
+ print("!!WARNING: Voltage higher than typical values!!!")
+ try:
+ response = raw_input(
+ "Voltage of %.3f requested. Confirm this is correct (Y/N)" %
+ FLAGS.voltage)
+ if response.upper() != "Y":
+ sys.exit("Aborting")
+ except:
+ sys.exit("Aborting.")
- if not FLAGS.power_monitor:
- sys.exit("You must specify a '--power_monitor' option to specify which power monitor type " + \
- "you are using.\nOne of:\n \n ".join(available_monitors))
- power_monitors = do_import('power_monitors.%s' % FLAGS.power_monitor)
- try:
- mon = power_monitors.Power_Monitor(device=FLAGS.device)
- except:
- import traceback
- traceback.print_exc()
- sys.exit("No power monitors found")
-
- if FLAGS.voltage is not None:
-
- if FLAGS.ramp is not None:
- mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
- else:
- mon.SetVoltage(FLAGS.voltage)
-
- if FLAGS.current is not None:
- mon.SetMaxCurrent(FLAGS.current)
-
- if FLAGS.status:
- items = sorted(mon.GetStatus().items())
- print "\n".join(["%s: %s" % item for item in items])
-
- if FLAGS.usbpassthrough:
- if FLAGS.usbpassthrough == 'off':
- mon.SetUsbPassthrough(0)
- elif FLAGS.usbpassthrough == 'on':
- mon.SetUsbPassthrough(1)
- elif FLAGS.usbpassthrough == 'auto':
- mon.SetUsbPassthrough(2)
- else:
- mon.Close()
- sys.exit('bad pass-through flag: %s' % FLAGS.usbpassthrough)
-
- if FLAGS.samples:
- # Make sure state is normal
- mon.StopDataCollection()
- status = mon.GetStatus()
- native_hz = status["sampleRate"] * 1000
-
- # Collect and average samples as specified
- mon.StartDataCollection()
-
- # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
- # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
- # This is the error accumulator in a variation of Bresenham's algorithm.
- emitted = offset = 0
- collected = []
- history_deque = collections.deque() # past n samples for rolling average
-
- try:
- last_flush = time.time()
- while emitted < FLAGS.samples or FLAGS.samples == -1:
- # The number of raw samples to consume before emitting the next output
- need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
- if need > len(collected): # still need more input samples
- samples = mon.CollectData()
- if not samples: break
- collected.extend(samples)
- else:
- # Have enough data, generate output samples.
- # Adjust for consuming 'need' input samples.
- offset += need * FLAGS.hz
- while offset >= native_hz: # maybe multiple, if FLAGS.hz > native_hz
- this_sample = sum(collected[:need]) / need
-
- if FLAGS.timestamp: print int(time.time()),
-
- if FLAGS.avg:
- history_deque.appendleft(this_sample)
- if len(history_deque) > FLAGS.avg: history_deque.pop()
- print "%f %f" % (this_sample,
- sum(history_deque) / len(history_deque))
- else:
- print "%f" % this_sample
- sys.stdout.flush()
-
- offset -= native_hz
- emitted += 1 # adjust for emitting 1 output sample
- collected = collected[need:]
- now = time.time()
- if now - last_flush >= 0.99: # flush every second
- sys.stdout.flush()
- last_flush = now
- except KeyboardInterrupt:
- print("interrupted")
- return 1
- finally:
- mon.Close()
- return 0
-
- if FLAGS.run:
if not FLAGS.power_monitor:
- sys.exit("When running power tests, you must specify which type of power monitor to use" +
- " with '--power_monitor <type of power monitor>'")
- PowerTest.run_tests()
+ sys.exit(
+ "You must specify a '--power_monitor' option to specify which power "
+ "monitor type " +
+ "you are using.\nOne of:\n \n ".join(available_monitors))
+ power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
+ try:
+ mon = power_monitors.Power_Monitor(device = FLAGS.device)
+ except:
+ import traceback
+ traceback.print_exc()
+ sys.exit("No power monitors found")
+
+ if FLAGS.voltage is not None:
+
+ if FLAGS.ramp is not None:
+ mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
+ else:
+ mon.SetVoltage(FLAGS.voltage)
+
+ if FLAGS.current is not None:
+ mon.SetMaxCurrent(FLAGS.current)
+
+ if FLAGS.status:
+ items = sorted(mon.GetStatus().items())
+ print "\n".join(["%s: %s" % item for item in items])
+
+ if FLAGS.usbpassthrough:
+ if FLAGS.usbpassthrough == "off":
+ mon.SetUsbPassthrough(0)
+ elif FLAGS.usbpassthrough == "on":
+ mon.SetUsbPassthrough(1)
+ elif FLAGS.usbpassthrough == "auto":
+ mon.SetUsbPassthrough(2)
+ else:
+ mon.Close()
+ sys.exit("bad pass-through flag: %s" % FLAGS.usbpassthrough)
+
+ if FLAGS.samples:
+ # Make sure state is normal
+ mon.StopDataCollection()
+ status = mon.GetStatus()
+ native_hz = status["sampleRate"] * 1000
+
+ # Collect and average samples as specified
+ mon.StartDataCollection()
+
+ # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
+ # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
+ # This is the error accumulator in a variation of Bresenham's algorithm.
+ emitted = offset = 0
+ collected = []
+ history_deque = collections.deque() # past n samples for rolling average
+
+ # TODO: Complicated lines of code below. Refactoring needed
+ try:
+ last_flush = time.time()
+ while emitted < FLAGS.samples or FLAGS.samples == -1:
+ # The number of raw samples to consume before emitting the next output
+ need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
+ if need > len(collected): # still need more input samples
+ samples = mon.CollectData()
+ if not samples: break
+ collected.extend(samples)
+ else:
+ # Have enough data, generate output samples.
+ # Adjust for consuming 'need' input samples.
+ offset += need * FLAGS.hz
+ while offset >= native_hz: # maybe multiple, if FLAGS.hz > native_hz
+ this_sample = sum(collected[:need]) / need
+
+ if FLAGS.timestamp: print int(time.time()),
+
+ if FLAGS.avg:
+ history_deque.appendleft(this_sample)
+ if len(history_deque) > FLAGS.avg: history_deque.pop()
+ print "%f %f" % (this_sample,
+ sum(history_deque) / len(history_deque))
+ else:
+ print "%f" % this_sample
+ sys.stdout.flush()
+
+ offset -= native_hz
+ emitted += 1 # adjust for emitting 1 output sample
+ collected = collected[need:]
+ now = time.time()
+ if now - last_flush >= 0.99: # flush every second
+ sys.stdout.flush()
+ last_flush = now
+ except KeyboardInterrupt:
+ print("interrupted")
+ return 1
+ finally:
+ mon.Close()
+ return 0
+
+ if FLAGS.run:
+ if not FLAGS.power_monitor:
+ sys.exit(
+ "When running power tests, you must specify which type of power "
+ "monitor to use" +
+ " with '--power_monitor <type of power monitor>'")
+ try:
+ PowerTest.runTests(FLAGS.max_baseline_amps)
+ except KeyboardInterrupt:
+ print "Keyboard interrupt from user"
if __name__ == "__main__":
flags.DEFINE_boolean("status", None, "Print power meter status")
@@ -581,5 +932,6 @@
flags.DEFINE_boolean("log", False, "Log progress to a file or not")
flags.DEFINE_boolean("run", False, "Run the test suite for power")
flags.DEFINE_string("power_monitor", None, "Type of power monitor to use")
+ flags.DEFINE_float("max_baseline_amps", 0.005,
+ "Set maximum baseline current for device being tested")
sys.exit(main(FLAGS(sys.argv)))
-
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
new file mode 100644
index 0000000..f1d7b65
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/screen_pinning.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:id="@+id/error_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true" >
+
+ <RelativeLayout
+ android:id="@+id/instructions_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/instructions_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/next_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/instructions_list"
+ android:text="@string/next_button_text" />
+
+ </RelativeLayout>
+ </ScrollView>
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ layout="@layout/pass_fail_buttons" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index f600aa3..0724e0f 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1104,6 +1104,20 @@
<string name="widget_pass">Pass</string>
<string name="widget_fail">Fail</string>
+ <string name="provisioning_byod_nonmarket_allow">Enable non-market apps</string>
+ <string name="provisioning_byod_nonmarket_allow_info">
+ This test verifies that non-market apps can be installed if permitted.\n
+ 1. A package installation UI should appear.\n
+ 2. Accept the package and verify that it installs.
+ </string>
+
+ <string name="provisioning_byod_nonmarket_deny">Disable non-market apps</string>
+ <string name="provisioning_byod_nonmarket_deny_info">
+ This test verifies that non-market apps cannot be installed unless permitted.\n
+ 1. A package installation UI should appear.\n
+ 2. Verify that the installation of the package is refused.
+ </string>
+
<!-- Strings for DeskClock -->
<string name="deskclock_tests">Alarms and Timers Tests</string>
<string name="deskclock_tests_info">
@@ -1426,4 +1440,18 @@
<!-- A list of fully-qualified test classes that should not be run. -->
<string-array name="disabled_tests" />
+
+ <!-- Strings for screen pinning test -->
+ <string name="screen_pinning_test">Screen Pinning Test</string>
+ <string name="screen_pin_instructions">Pressing next will prompt you to enter screen pinning, allow this app to enter screen pinning.</string>
+ <string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
+ <string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
+ <string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
+ <string name="screen_pinning_done">All tests completed successfully.</string>
+
+ <string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
+ <string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
+ <string name="error_screen_pinning_did_not_start">Screen was not pinned.</string>
+ <string name="error_screen_pinning_did_not_exit">Screen was not unpinned.</string>
+ <string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
</resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
index b697be8..12aa37b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -75,6 +75,8 @@
private TestItem mDeviceAdminVisibleTest;
private TestItem mWorkAppVisibleTest;
private TestItem mCrossProfileIntentFiltersTest;
+ private TestItem mDisableNonMarketTest;
+ private TestItem mEnableNonMarketTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -179,6 +181,16 @@
}
};
+ mDisableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_deny,
+ R.string.provisioning_byod_nonmarket_deny_info,
+ new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+ .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
+
+ mEnableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_allow,
+ R.string.provisioning_byod_nonmarket_allow_info,
+ new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+ .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
+
Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
Intent chooser = Intent.createChooser(intent, getResources().getString(R.string.provisioning_cross_profile_chooser));
mCrossProfileIntentFiltersTest = new TestItem(this,
@@ -191,6 +203,8 @@
mTests.add(mDeviceAdminVisibleTest);
mTests.add(mWorkAppVisibleTest);
mTests.add(mCrossProfileIntentFiltersTest);
+ mTests.add(mDisableNonMarketTest);
+ mTests.add(mEnableNonMarketTest);
}
@Override
@@ -389,5 +403,4 @@
return view;
}
}
-
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index 5dac4bd..277324c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -24,10 +24,14 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
+import static android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS;
+
import com.android.cts.verifier.R;
import com.android.cts.verifier.managedprovisioning.ByodFlowTestActivity.TestResult;
@@ -53,17 +57,33 @@
public static final String EXTRA_PROVISIONED = "extra_provisioned";
- private ComponentName mAdminReceiverComponent;
+ // Primary -> managed intent: set unknown sources restriction and install package
+ public static final String ACTION_INSTALL_APK = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK";
+ public static final String EXTRA_ALLOW_NON_MARKET_APPS = INSTALL_NON_MARKET_APPS;
+ private static final int REQUEST_INSTALL_PACKAGE = 1;
+
+ private static final String ORIGINAL_SETTINGS_NAME = "original settings";
+ private Bundle mOriginalSettings;
+
+ private ComponentName mAdminReceiverComponent;
private DevicePolicyManager mDevicePolicyManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Restored state");
+ mOriginalSettings = savedInstanceState.getBundle(ORIGINAL_SETTINGS_NAME);
+ } else {
+ mOriginalSettings = new Bundle();
+ }
+
mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
mDevicePolicyManager = (DevicePolicyManager) getSystemService(
Context.DEVICE_POLICY_SERVICE);
- String action = getIntent().getAction();
+ Intent intent = getIntent();
+ String action = intent.getAction();
Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
// we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
@@ -83,16 +103,70 @@
mDevicePolicyManager.wipeData(0);
showToast(R.string.provisioning_byod_profile_deleted);
}
+ } else if (action.equals(ACTION_INSTALL_APK)) {
+ boolean allowNonMarket = intent.getBooleanExtra(EXTRA_ALLOW_NON_MARKET_APPS, false);
+ boolean wasAllowed = getAllowNonMarket();
+
+ // Update permission to install non-market apps
+ setAllowNonMarket(allowNonMarket);
+ mOriginalSettings.putBoolean(INSTALL_NON_MARKET_APPS, wasAllowed);
+
+ // Request to install a non-market application- easiest way is to reinstall ourself
+ final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
+ .setData(Uri.parse("package:" + getPackageName()))
+ .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
+ .putExtra(Intent.EXTRA_RETURN_RESULT, true);
+ startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
+
+ // Not yet ready to finish- wait until the result comes back
+ return;
}
// This activity has no UI and is only used to respond to CtsVerifier in the primary side.
finish();
}
+ @Override
+ protected void onSaveInstanceState(final Bundle savedState) {
+ super.onSaveInstanceState(savedState);
+
+ savedState.putBundle(ORIGINAL_SETTINGS_NAME, mOriginalSettings);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_INSTALL_PACKAGE: {
+ Log.w(TAG, "Received REQUEST_INSTALL_PACKAGE, resultCode = " + resultCode);
+ if (mOriginalSettings.containsKey(INSTALL_NON_MARKET_APPS)) {
+ // Restore original setting
+ setAllowNonMarket(mOriginalSettings.getBoolean(INSTALL_NON_MARKET_APPS));
+ mOriginalSettings.remove(INSTALL_NON_MARKET_APPS);
+ }
+ finish();
+ break;
+ }
+ default: {
+ Log.wtf(TAG, "Unknown requestCode " + requestCode + "; data = " + data);
+ break;
+ }
+ }
+ }
+
private boolean isProfileOwner() {
return mDevicePolicyManager.isAdminActive(mAdminReceiverComponent) &&
mDevicePolicyManager.isProfileOwnerApp(mAdminReceiverComponent.getPackageName());
}
+ private boolean getAllowNonMarket() {
+ String value = Settings.Secure.getString(getContentResolver(), INSTALL_NON_MARKET_APPS);
+ return "1".equals(value);
+ }
+
+ private void setAllowNonMarket(boolean allow) {
+ mDevicePolicyManager.setSecureSetting(mAdminReceiverComponent, INSTALL_NON_MARKET_APPS,
+ (allow ? "1" : "0"));
+ }
+
private void startActivityInPrimary(Intent intent) {
// Disable app components in the current profile, so only the counterpart in the other
// profile can respond (via cross-profile intent filter)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
index 8dccac3..1f78daf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -50,6 +50,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
filter.addAction(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
+ filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK);
filter.addAction(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
dpm.addCrossProfileIntentFilter(getWho(context), filter,
DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
new file mode 100644
index 0000000..200865e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.cts.verifier.screenpinning;
+
+import android.app.ActivityManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+
+ private static final String TAG = "ScreenPinningTestActivity";
+ private static final String KEY_CURRENT_TEST = "keyCurrentTest";
+
+ private Test[] mTests;
+ private int mTestIndex;
+
+ private ActivityManager mActivityManager;
+ private Button mNextButton;
+ private LinearLayout mInstructions;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.screen_pinning);
+ setPassFailButtonClickListeners();
+
+ mTests = new Test[] {
+ // Verify not already pinned.
+ mCheckStartedUnpinned,
+ // Enter pinning, verify pinned, try leaving and have the user exit.
+ mCheckStartPinning,
+ mCheckIsPinned,
+ mCheckTryLeave,
+ mCheckUnpin,
+ // Enter pinning, verify pinned, and use APIs to exit.
+ mCheckStartPinning,
+ mCheckIsPinned,
+ mCheckUnpinFromCode,
+ // All done.
+ mDone,
+ };
+
+ mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
+
+ mNextButton = (Button) findViewById(R.id.next_button);
+ mNextButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+ mTests[mTestIndex].onNextClick();
+ }
+ }
+ });
+
+ // Don't allow pass until all tests complete.
+ findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+ // Figure out if we are in a test or starting from the beginning.
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+ mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+ } else {
+ mTestIndex = 0;
+ }
+ // Display any pre-existing text.
+ for (int i = 0; i < mTestIndex; i++) {
+ mTests[i].showText();
+ }
+ mTests[mTestIndex].run();
+ };
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Block back button so we can test screen pinning exit functionality.
+ // Users can still leave by pressing fail (or when done the pass) button.
+ }
+
+ private void show(int id) {
+ TextView tv = new TextView(this);
+ tv.setPadding(10, 10, 10, 30);
+ tv.setText(id);
+ mInstructions.addView(tv);
+ }
+
+ private void succeed() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTestIndex++;
+ if (mTestIndex < mTests.length) {
+ mTests[mTestIndex].run();
+ } else {
+ mNextButton.setVisibility(View.GONE);
+ findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+
+ private void error(int errorId) {
+ error(errorId, new Throwable());
+ }
+
+ private void error(final int errorId, final Throwable cause) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String error = getString(errorId);
+ Log.d(TAG, error, cause);
+ // No more instructions needed.
+ findViewById(R.id.instructions_group).setVisibility(View.GONE);
+
+ ((TextView) findViewById(R.id.error_text)).setText(error);
+ }
+ });
+ }
+
+ // Verify we don't start in screen pinning.
+ private final Test mCheckStartedUnpinned = new Test(0) {
+ public void run() {
+ if (mActivityManager.isInLockTaskMode()) {
+ error(R.string.error_screen_already_pinned);
+ } else {
+ succeed();
+ }
+ }
+ };
+
+ // Start screen pinning by having the user click next then confirm it for us.
+ private final Test mCheckStartPinning = new Test(R.string.screen_pin_instructions) {
+ protected void onNextClick() {
+ startLockTask();
+ succeed();
+ }
+ };
+
+ // Click next and check that we got pinned.
+ // Wait for the user to click next to verify that they got back from the prompt
+ // successfully.
+ private final Test mCheckIsPinned = new Test(R.string.screen_pin_check_pinned) {
+ protected void onNextClick() {
+ if (mActivityManager.isInLockTaskMode()) {
+ succeed();
+ } else {
+ error(R.string.error_screen_pinning_did_not_start);
+ }
+ }
+ };
+
+ // Tell user to try to leave.
+ private final Test mCheckTryLeave = new Test(R.string.screen_pin_no_exit) {
+ protected void onNextClick() {
+ if (mActivityManager.isInLockTaskMode()) {
+ succeed();
+ } else {
+ error(R.string.error_screen_no_longer_pinned);
+ }
+ }
+ };
+
+ // Verify that the user unpinned and it worked.
+ private final Test mCheckUnpin = new Test(R.string.screen_pin_exit) {
+ protected void onNextClick() {
+ if (!mActivityManager.isInLockTaskMode()) {
+ succeed();
+ } else {
+ error(R.string.error_screen_pinning_did_not_exit);
+ }
+ }
+ };
+
+ // Unpin from code and check that it worked.
+ private final Test mCheckUnpinFromCode = new Test(0) {
+ protected void run() {
+ if (!mActivityManager.isInLockTaskMode()) {
+ error(R.string.error_screen_pinning_did_not_start);
+ return;
+ }
+ stopLockTask();
+ if (!mActivityManager.isInLockTaskMode()) {
+ succeed();
+ } else {
+ error(R.string.error_screen_pinning_couldnt_exit);
+ }
+ };
+ };
+
+ private final Test mDone = new Test(R.string.screen_pinning_done) {
+ protected void run() {
+ showText();
+ succeed();
+ };
+ };
+
+ private abstract class Test {
+ private final int mResId;
+
+ public Test(int showId) {
+ mResId = showId;
+ }
+
+ protected void run() {
+ showText();
+ }
+
+ public void showText() {
+ if (mResId == 0) {
+ return;
+ }
+ show(mResId);
+ }
+
+ protected void onNextClick() {
+ }
+ }
+
+}
diff --git a/tests/tests/app/src/android/app/cts/AlarmManagerTest.java b/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
index bfc3e1d..b9caae9 100644
--- a/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
@@ -47,6 +47,7 @@
private static final int TIME_DELTA = 1000;
private static final int TIME_DELAY = 10000;
+ private static final int REPEAT_PERIOD = 60000;
// Receiver registration/unregistration between tests races with the system process, so
// we add a little buffer time here to allow the system to process before we proceed.
@@ -221,46 +222,54 @@
public void testSetRepeating() throws Exception {
mMockAlarmReceiver.setAlarmedFalse();
- mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
- mAm.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, TIME_DELAY / 2, mSender);
- new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
+ mWakeupTime = System.currentTimeMillis() + TEST_ALARM_FUTURITY;
+ mAm.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, REPEAT_PERIOD, mSender);
+
+ // wait slightly beyond the initial alarm to verify that it fires the first time
+ new PollingCheck(TEST_ALARM_FUTURITY + TIME_DELTA) {
@Override
protected boolean check() {
return mMockAlarmReceiver.alarmed;
}
}.run();
+ assertTrue(mMockAlarmReceiver.alarmed);
+
+ // Now reset the receiver and wait for the intended repeat alarm to fire as expected
mMockAlarmReceiver.setAlarmedFalse();
- new PollingCheck(TIME_DELAY) {
+ new PollingCheck(REPEAT_PERIOD*2 + TIME_DELTA) {
@Override
protected boolean check() {
return mMockAlarmReceiver.alarmed;
}
}.run();
+ assertTrue(mMockAlarmReceiver.alarmed);
+
mAm.cancel(mSender);
}
public void testCancel() throws Exception {
mMockAlarmReceiver.setAlarmedFalse();
- mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
- mAm.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, 1000, mSender);
- new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
- @Override
- protected boolean check() {
- return mMockAlarmReceiver.alarmed;
- }
- }.run();
- mMockAlarmReceiver.setAlarmedFalse();
+ mMockAlarmReceiver2.setAlarmedFalse();
+
+ // set two alarms
+ final long when1 = System.currentTimeMillis() + TEST_ALARM_FUTURITY;
+ mAm.setExact(AlarmManager.RTC_WAKEUP, when1, mSender);
+ final long when2 = when1 + TIME_DELTA; // will fire after when1's target time
+ mAm.setExact(AlarmManager.RTC_WAKEUP, when2, mSender2);
+
+ // cancel the earlier one
+ mAm.cancel(mSender);
+
+ // and verify that only the later one fired
new PollingCheck(TIME_DELAY) {
@Override
protected boolean check() {
- return mMockAlarmReceiver.alarmed;
+ return mMockAlarmReceiver2.alarmed;
}
}.run();
- mAm.cancel(mSender);
- Thread.sleep(TIME_DELAY);
- mMockAlarmReceiver.setAlarmedFalse();
- Thread.sleep(TIME_DELAY * 5);
+
assertFalse(mMockAlarmReceiver.alarmed);
+ assertTrue(mMockAlarmReceiver2.alarmed);
}
public void testSetInexactRepeating() throws Exception {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 9dd5ed8..250601d 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraCharacteristics.Key;
import android.hardware.camera2.CameraManager;
@@ -25,11 +26,13 @@
import android.hardware.camera2.params.BlackLevelPattern;
import android.hardware.camera2.params.ColorSpaceTransform;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.ImageReader;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Rational;
import android.util.Range;
import android.util.Size;
+import android.view.Surface;
import java.util.ArrayList;
import java.util.Arrays;
@@ -437,6 +440,179 @@
}
/**
+ * Cross-check StreamConfigurationMap output
+ */
+ public void testStreamConfigurationMap() {
+ int counter = 0;
+ for (CameraCharacteristics c : mCharacteristics) {
+ Log.i(TAG, "testStreamConfigurationMap: Testing camera ID " + mIds[counter]);
+ StreamConfigurationMap config =
+ c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ assertNotNull(String.format("No stream configuration map found for: ID %s",
+ mIds[counter]), config);
+
+ assertTrue("ImageReader must be supported",
+ config.isOutputSupportedFor(android.media.ImageReader.class));
+ assertTrue("MediaRecorder must be supported",
+ config.isOutputSupportedFor(android.media.MediaRecorder.class));
+ assertTrue("MediaCodec must be supported",
+ config.isOutputSupportedFor(android.media.MediaCodec.class));
+ assertTrue("Allocation must be supported",
+ config.isOutputSupportedFor(android.renderscript.Allocation.class));
+ assertTrue("SurfaceHolder must be supported",
+ config.isOutputSupportedFor(android.view.SurfaceHolder.class));
+ assertTrue("SurfaceTexture must be supported",
+ config.isOutputSupportedFor(android.graphics.SurfaceTexture.class));
+
+ assertTrue("YUV_420_888 must be supported",
+ config.isOutputSupportedFor(ImageFormat.YUV_420_888));
+ assertTrue("JPEG must be supported",
+ config.isOutputSupportedFor(ImageFormat.JPEG));
+
+ // Legacy YUV formats should not be listed
+ assertTrue("NV21 must not be supported",
+ !config.isOutputSupportedFor(ImageFormat.NV21));
+
+ int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ assertNotNull("android.request.availableCapabilities must never be null",
+ actualCapabilities);
+ if (arrayContains(actualCapabilities,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ assertTrue("RAW_SENSOR must be supported if RAW capability is advertised",
+ config.isOutputSupportedFor(ImageFormat.RAW_SENSOR));
+ }
+
+ // Cross check public formats and sizes
+
+ int[] supportedFormats = config.getOutputFormats();
+ for (int format : supportedFormats) {
+ assertTrue("Format " + format + " fails cross check",
+ config.isOutputSupportedFor(format));
+ Size[] supportedSizes = config.getOutputSizes(format);
+ assertTrue("Supported format " + format + " has no sizes listed",
+ supportedSizes.length > 0);
+ for (Size size : supportedSizes) {
+ if (VERBOSE) {
+ Log.v(TAG,
+ String.format("Testing camera %s, format %d, size %s",
+ mIds[counter], format, size.toString()));
+ }
+
+ long stallDuration = config.getOutputStallDuration(format, size);
+ switch(format) {
+ case ImageFormat.YUV_420_888:
+ assertTrue("YUV_420_888 may not have a non-zero stall duration",
+ stallDuration == 0);
+ break;
+ default:
+ assertTrue("Negative stall duration for format " + format,
+ stallDuration >= 0);
+ break;
+ }
+ long minDuration = config.getOutputMinFrameDuration(format, size);
+ if (arrayContains(actualCapabilities,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ assertTrue("MANUAL_SENSOR capability, need positive min frame duration for"
+ + "format " + format,
+ minDuration > 0);
+ } else {
+ assertTrue("Need non-negative min frame duration for format " + format,
+ minDuration >= 0);
+ }
+
+ ImageReader testReader = ImageReader.newInstance(
+ size.getWidth(),
+ size.getHeight(),
+ format,
+ 1);
+ Surface testSurface = testReader.getSurface();
+
+ assertTrue(
+ String.format("isOutputSupportedFor fails for config %s, format %d",
+ size.toString(), format),
+ config.isOutputSupportedFor(testSurface));
+
+ testReader.close();
+
+ } // sizes
+
+ // Try an invalid size in this format, should round
+ Size invalidSize = findInvalidSize(supportedSizes);
+ int MAX_ROUNDING_WIDTH = 1920;
+ if (invalidSize.getWidth() <= MAX_ROUNDING_WIDTH) {
+ ImageReader testReader = ImageReader.newInstance(
+ invalidSize.getWidth(),
+ invalidSize.getHeight(),
+ format,
+ 1);
+ Surface testSurface = testReader.getSurface();
+
+ assertTrue(
+ String.format("isOutputSupportedFor fails for config %s, %d",
+ invalidSize.toString(), format),
+ config.isOutputSupportedFor(testSurface));
+
+ testReader.close();
+ }
+ } // formats
+
+ // Cross-check opaque format and sizes
+
+ SurfaceTexture st = new SurfaceTexture(1);
+ Surface surf = new Surface(st);
+
+ Size[] opaqueSizes = config.getOutputSizes(SurfaceTexture.class);
+ assertTrue("Opaque format has no sizes listed",
+ opaqueSizes.length > 0);
+ for (Size size : opaqueSizes) {
+ long stallDuration = config.getOutputStallDuration(SurfaceTexture.class, size);
+ assertTrue("Opaque output may not have a non-zero stall duration",
+ stallDuration == 0);
+
+ long minDuration = config.getOutputMinFrameDuration(SurfaceTexture.class, size);
+ if (arrayContains(actualCapabilities,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ assertTrue("MANUAL_SENSOR capability, need positive min frame duration for"
+ + "opaque format",
+ minDuration > 0);
+ } else {
+ assertTrue("Need non-negative min frame duration for opaque format ",
+ minDuration >= 0);
+ }
+ st.setDefaultBufferSize(size.getWidth(), size.getHeight());
+
+ assertTrue(
+ String.format("isOutputSupportedFor fails for SurfaceTexture config %s",
+ size.toString()),
+ config.isOutputSupportedFor(surf));
+
+ } // opaque sizes
+
+ // Try invalid opaque size, should get rounded
+ Size invalidSize = findInvalidSize(opaqueSizes);
+ st.setDefaultBufferSize(invalidSize.getWidth(), invalidSize.getHeight());
+ assertTrue(
+ String.format("isOutputSupportedFor fails for SurfaceTexture config %s",
+ invalidSize.toString()),
+ config.isOutputSupportedFor(surf));
+
+ counter++;
+ } // mCharacteristics
+
+ }
+
+ /**
+ * Create an invalid size that's close to one of the good sizes in the list, but not one of them
+ */
+ private Size findInvalidSize(Size[] goodSizes) {
+ Size invalidSize = new Size(goodSizes[0].getWidth() + 1, goodSizes[0].getHeight());
+ while(arrayContains(goodSizes, invalidSize)) {
+ invalidSize = new Size(invalidSize.getWidth() + 1, invalidSize.getHeight());
+ }
+ return invalidSize;
+ }
+
+ /**
* Check key is present in characteristics if the hardware level is at least {@code hwLevel};
* check that the key is present if the actual capabilities are one of {@code capabilities}.
*
@@ -509,6 +685,20 @@
return false;
}
+ private static <T> boolean arrayContains(T[] arr, T needle) {
+ if (arr == null) {
+ return false;
+ }
+
+ for (T elem : arr) {
+ if (elem.equals(needle)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private static boolean arrayContainsAnyOf(int[] arr, int[] needles) {
for (int needle : needles) {
if (arrayContains(arr, needle)) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
index db6d6ba..7aa6a9d 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
@@ -16,10 +16,11 @@
package android.keystore.cts;
+import android.content.pm.PackageManager;
import android.security.KeyChain;
-import junit.framework.TestCase;
+import android.test.AndroidTestCase;
-public class KeyChainTest extends TestCase {
+public class KeyChainTest extends AndroidTestCase {
public void testIsKeyAlgorithmSupported_RequiredAlgorithmsSupported() throws Exception {
assertTrue("DSA must be supported", KeyChain.isKeyAlgorithmSupported("DSA"));
assertTrue("EC must be supported", KeyChain.isKeyAlgorithmSupported("EC"));
@@ -34,11 +35,21 @@
* tests in hardware/libhardware/tests/keymaster/
*/
public void testIsBoundKeyAlgorithm_RequiredAlgorithmsSupported() throws Exception {
- assertTrue("RSA must be hardware-backed by a hardware-specific Keymaster HAL",
- KeyChain.isBoundKeyAlgorithm("RSA"));
+ if (isLeanbackOnly()) {
+ KeyChain.isBoundKeyAlgorithm("RSA");
+ }
+ else {
+ assertTrue("RSA must be hardware-backed by a hardware-specific Keymaster HAL",
+ KeyChain.isBoundKeyAlgorithm("RSA"));
+ }
// These are not required, but must not throw an exception
KeyChain.isBoundKeyAlgorithm("DSA");
KeyChain.isBoundKeyAlgorithm("EC");
}
+
+ private boolean isLeanbackOnly() {
+ PackageManager pm = getContext().getPackageManager();
+ return (pm != null && pm.hasSystemFeature("android.software.leanback_only"));
+ }
}
diff --git a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
index e9bfb43..eddb416 100644
--- a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
@@ -53,6 +53,9 @@
/** Maximum size of the packets we're using to test. */
private static final int MAX_SIZE = 4096;
+ /** Size of the ICMPv6 header. */
+ private static final int ICMP_HEADER_SIZE = 8;
+
/** Number of packets to test. */
private static final int NUM_PACKETS = 10;
@@ -65,7 +68,7 @@
* Returns a byte array containing an ICMPv6 echo request with the specified payload length.
*/
private byte[] pingPacket(int payloadLength) {
- byte[] packet = new byte[payloadLength + 8];
+ byte[] packet = new byte[payloadLength + ICMP_HEADER_SIZE];
new Random().nextBytes(packet);
System.arraycopy(PING_HEADER, 0, packet, 0, PING_HEADER.length);
return packet;
@@ -155,7 +158,7 @@
assertEquals("localhost/::1", ipv6Loopback.toString());
for (int i = 0; i < NUM_PACKETS; i++) {
- byte[] packet = pingPacket((int) (Math.random() * MAX_SIZE));
+ byte[] packet = pingPacket((int) (Math.random() * (MAX_SIZE - ICMP_HEADER_SIZE)));
FileDescriptor s = createPingSocket();
// Use both recvfrom and read().
sendPing(s, ipv6Loopback, packet);
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
index 3d92eb3..72dccd4 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
@@ -185,6 +185,11 @@
}
@Override
+ public void setTestCollection(boolean b) {
+ throw new UnsupportedOperationException("Test Collection mode is not supported");
+ }
+
+ @Override
public void setTestSize(TestSize size) {
addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/);
}