Merge "Camera 2: Add three metrics to cts performance test." into lmp-dev
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 021f724..0a4e6e4 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -41,7 +41,7 @@
# Builds and launches CTS Verifier on a device.
.PHONY: cts-verifier
cts-verifier: CtsVerifier adb
- adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier.apk \
+ adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
#
@@ -59,6 +59,19 @@
verifier-zip-name := $(verifier-dir-name).zip
verifier-zip := $(cts-dir)/$(verifier-zip-name)
+$(PRODUCT_OUT)/data/app/CtsVerifier.apk $(verifier-zip): $(verifier-dir)/power/execute_power_tests.py
+$(PRODUCT_OUT)/data/app/CtsVerifier.apk $(verifier-zip): $(verifier-dir)/power/power_monitors/monsoon.py
+
+#Copy the necessary host-side scripts to include in the
+#zip file:
+$(verifier-dir)/power/power_monitors/monsoon.py: cts/apps/CtsVerifier/assets/scripts/power_monitors/monsoon.py
+ $(hide) mkdir -p $(verifier-dir)/power/power_monitors
+ $(hide) $(ACP) -fp cts/apps/CtsVerifier/assets/scripts/power_monitors/*.py $(verifier-dir)/power/power_monitors/.
+
+$(verifier-dir)/power/execute_power_tests.py: cts/apps/CtsVerifier/assets/scripts/execute_power_tests.py
+ $(hide) mkdir -p $(verifier-dir)/power
+ $(hide) $(ACP) -fp cts/apps/CtsVerifier/assets/scripts/execute_power_tests.py $@
+
cts : $(verifier-zip)
ifeq ($(HOST_OS),linux)
$(verifier-zip) : $(HOST_OUT)/bin/cts-usb-accessory
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 1f95501..4e57588 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -878,6 +878,16 @@
<meta-data android:name="test_required_features" android:value="android.hardware.usb.accessory" />
</activity>
+ <activity android:name=".sensors.SensorPowerTestActivity"
+ android:label="@string/sensor_power_test"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <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_sensors" />
+ </activity>
+
<activity android:name=".p2p.P2pTestListActivity"
android:label="@string/p2p_test"
android:configChanges="keyboardHidden|orientation|screenSize">
diff --git a/apps/CtsVerifier/assets/scripts/execute_power_tests.py b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
new file mode 100755
index 0000000..71d2850
--- /dev/null
+++ b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
@@ -0,0 +1,563 @@
+#!/usr/bin/python
+
+# 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.
+
+import logging
+import os.path
+import select
+import sys
+import time
+import collections
+import socket
+import gflags as flags # http://code.google.com/p/python-gflags/
+import pkgutil
+import threading
+
+# let this script know about the power monitor impementations
+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('_')]
+
+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 to do so
+USE_STRICT_DELAY = False
+if USE_STRICT_DELAY:
+ DELAY_SCREEN_OFF = 30.0
+else:
+ DELAY_SCREEN_OFF = 2.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('.')
+ for comp in components[1:]:
+ mod = getattr(mod, comp)
+ return mod
+
+class PowerTest:
+ """Class to run a suite of power tests"""
+
+ #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
+
+
+
+ PORT = 0 # any available port
+ DOMAIN_NAME = "/android/cts/powertest"
+ SAMPLE_COUNT_NOMINAL = 1000
+ RATE_NOMINAL = 100
+
+ QUERY_EXTERNAL_STORAGE = "EXTERNAL STORAGE?"
+
+ def __init__(self):
+ 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)
+ print ("Establishing connection to device...")
+ self.setUSBEnabled(True)
+ status = self._power_monitor.GetStatus()
+ self._native_hz = status["sampleRate"] * 1000
+ self._current_test = "None"
+ self._external_storage = self.executeOnDevice(PowerTest.QUERY_EXTERNAL_STORAGE, reportErrors=True )
+
+ 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("EXIT", reportErrors=False)
+ self.executeLocal("adb forward --remove tcp:%d" % PowerTest.PORT)
+ PowerTest.PORT = 0
+ if self._power_monitor:
+ self._power_monitor.Close()
+ self._power_monitor = None
+
+ def _send(self, msg, report_errors=True):
+ """Connect to the device, send the given commmand, and then disconnect"""
+ if PowerTest.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()
+ dummysocket.close()
+ assert(PowerTest.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)
+
+ link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ try:
+ logging.debug("Connecting to device...")
+ link.connect (("localhost", PowerTest.PORT))
+ logging.debug("Connected.")
+ except:
+ if report_errors:
+ self.reportErrorIf(True, msg="Unable to communicate with device: connection refused")
+ logging.debug("Sending '%s'" % msg)
+ link.sendall(msg)
+ logging.debug("Getting response...")
+ response = link.recv(4096)
+ 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)
+ return self._send(query) == "OK"
+
+ def executeOnDevice(self, cmd , reportErrors=True):
+ """Execute a (string) command on the remote device"""
+ return self._send(cmd , reportErrors)
+
+ def executeLocal(self, cmd, check_status=True):
+ """execute a shell command locally (on the host)"""
+ from subprocess import call
+ status = call(cmd.split(' '))
+ if status != 0 and check_status:
+ logging.error("Failed to execute \"%s\"" % cmd)
+ else:
+ logging.debug("Executed \"%s\"" % cmd)
+ return status
+
+ def reportErrorIf(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"""
+ if condition:
+ try:
+ logging.error("Exiting on error: %s" % msg)
+ self.executeOnDevice("RAISE " + self._current_test + " " + msg , False)
+ except:
+
+ logging.error("Unable to communicate with device to report error: %s" % msg)
+ self.finalize()
+ sys.exit(msg)
+ raise Exception(msg)
+
+ def setUSBEnabled(self, enabled, verbose=True):
+ if enabled:
+ val = 1
+ else:
+ val = 0
+ self._power_monitor.SetUsbPassthrough(val)
+ tries = 0
+
+ #Sometimes command won't go through first time, paricularly if
+ #immediately after a data collection, so allow for retries
+ status = self._power_monitor.GetStatus()
+ while status is None and tries < 5:
+ tries += 1
+ time.sleep(2.0)
+ logging.error("Retrying get status call...")
+ self._power_monitor.StopDataCollection()
+ self._power_monitor.SetUsbPassthrough(val)
+ 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")
+ else:
+ if verbose: logging.info("...USB disabled")
+ # re-establish port forwarding
+ if enabled and PowerTest.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")
+
+ 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
+
+ # 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 # mAmps
+ TIMEOUT_SCREEN_OFF = 30 #this many tries at most
+ count_good = 0
+ tries = 0
+ print("Waiting for screen off...")
+ 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 tries >= TIMEOUT_SCREEN_OFF:
+ self.reportErrorIf(tries>=TIMEOUT_SCREEN_OFF, msg="Unable to determine screen off status.")
+ break
+ if DELAY_SCREEN_OFF:
+ #add additional delay time if necessary
+ time.sleep(DELAY_SCREEN_OFF)
+ print("...Screen off.")
+
+ 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...")
+ self._power_monitor.StartDataCollection()
+ sub_measurements = []
+ measurements = []
+ tries = 0
+ if verbose: print("")
+ try:
+ while len(measurements) < measurementCount and tries < 5:
+ if tries:
+ #logging.error("Failed attempt %d" % tries)
+ self._power_monitor.StopDataCollection()
+ self._power_monitor.StartDataCollection()
+ time.sleep(1.0)
+ tries += 1
+ additional = self._power_monitor.CollectData()
+ if additional is not None:
+ tries = 0
+ sub_measurements.extend(additional)
+ while len(sub_measurements) >= decimate_by:
+ sub_avg = sum(sub_measurements) / len(sub_measurements)
+ measurements.append(sub_avg)
+ sub_measurements = sub_measurements[decimate_by:]
+ if verbose:
+ sys.stdout.write("\33[1A\33[2K")
+ print ("MEASURED[%d]: %f" % (len(measurements),measurements[-1]))
+ finally:
+ self._power_monitor.StopDataCollection()
+
+ self.reportErrorIf(measurementCount > len(measurements),
+ "Unable to collect all requested measurements")
+ return measurements
+
+ def request_user_acknowledgment(self, msg):
+ """Post message to user on screen and wait for acknowledgment"""
+ response = self.executeOnDevice("REQUEST USER RESPONSE " + msg)
+ self.reportErrorIf(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("SET TEST RESULT %s %s %s" % (testname, val, msg))
+ self.reportErrorIf(response != "OK", "Unable to send test status to Verifier")
+
+ def setPowerOn(self, sensor, powered_on):
+ response = self.executeOnDevice("SENSOR %s %s" % ({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]))
+ return response
+
+ def runPowerTest(self, sensor, max_power_allowed, user_request = None):
+ self._current_test = "%s_Power_Test_While_%s" % (sensor, {True:"Under_Motion", False:"Still"}[user_request is not None])
+ try:
+ print ("\n\n---------------------------------")
+ if user_request is not None:
+ print ("Running power test on %s under motion." % sensor)
+ else:
+ print ("Running power test on %s while device is still." % sensor)
+ print ("---------------------------------")
+ response = self.executeOnDevice("SENSOR? %s"%sensor)
+ if response == "UNAVAILABLE":
+ self.setTestResult(self._current_test, condition="SKIPPED", msg="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)
+ return
+
+ self.reportErrorIf(response != "OK", "Unable to set all sensor 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.setUSBEnabled(False)
+ self.reportErrorIf(response != "OK", "Unable to set sensor %s ON" % sensor)
+ 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:
+ for m in measurements:
+ f.write( "%.4f\n"%m)
+ self.setUSBEnabled(True, verbose = False)
+ print("Saving raw data files to device...")
+ self.executeLocal("adb shell mkdir -p %s/sensor_power_test_data"%self._external_storage, False)
+ self.executeLocal("adb push %s %s/sensor_power_test_data/."%(f.name, self._external_storage))
+ self.setUSBEnabled(False, verbose = False)
+ self.reportErrorIf(not measurements, "No measurements could be taken for %s" % sensor)
+ avg = sum(measurements) / len(measurements)
+ squared = [(m-avg)*(m-avg) for m in measurements]
+
+ import math
+ stddev = math.sqrt(sum(squared)/len(squared))
+ current_diff = avg - backgnd
+ self.setUSBEnabled(True)
+ max_power = max(measurements) - avg
+ if current_diff <= max_power_allowed:
+ 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)
+ 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)
+ except:
+ import traceback
+ traceback.print_exc()
+ self.setTestResult(self._current_test, condition="FAIL", msg="Exception occurred during run of test.")
+
+
+ @staticmethod
+ def run_tests():
+
+ 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.\n" + \
+ "Please 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.\n" + \
+ "Please refrain from interacting with the screen or pressing any side buttons " + \
+ "while measurements are taken."
+ testrunner = PowerTest()
+ testrunner.reportErrorIf(not testrunner.queryDevice("AIRPLANE MODE ON?"),
+ "Airplane mode not off as expected")
+ #testrunner.reportErrorIf(int(testrunner.executeOnDevice("SCREEN OFF TIMEOUT?")) > 15000,
+ # "Screen sleep time not set to 15 seconds")
+ testrunner.executeOnDevice("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)
+ except:
+ import traceback
+ traceback.print_exc()
+ finally:
+ 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,"+\
+ " the CtsVerifier app is running the SensorPowerTest on the device, and USB passthrough 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
+
+ if FLAGS.avg and FLAGS.avg < 0:
+ loggign.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 not FLAGS.power_monitor:
+ sys.exit("You must specify a '--power_monitor' option to specify which power monitor type you are using.\n"+
+ "One 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 passthrough 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()
+
+if __name__ == "__main__":
+ flags.DEFINE_boolean("status", None, "Print power meter status")
+ flags.DEFINE_integer("avg", None,
+ "Also report average over last n data points")
+ flags.DEFINE_float("voltage", None, "Set output voltage (0 for off)")
+ flags.DEFINE_float("current", None, "Set max output current")
+ flags.DEFINE_string("usbpassthrough", None, "USB control (on, off, auto)")
+ flags.DEFINE_integer("samples", None, "Collect and print this many samples")
+ flags.DEFINE_integer("hz", 5000, "Print this many samples/sec")
+ flags.DEFINE_string("device", None,
+ "Path to the device in /dev/... (ex:/dev/ttyACM1)")
+ flags.DEFINE_boolean("timestamp", None,
+ "Also print integer (seconds) timestamp on each line")
+ flags.DEFINE_boolean("ramp", True, "Gradually increase voltage")
+ 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")
+ sys.exit(main(FLAGS(sys.argv)))
+
+
diff --git a/apps/CtsVerifier/assets/scripts/power_monitors/__init__.py b/apps/CtsVerifier/assets/scripts/power_monitors/__init__.py
new file mode 100644
index 0000000..cec4545
--- /dev/null
+++ b/apps/CtsVerifier/assets/scripts/power_monitors/__init__.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+
+# 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.
+#
+
+from abc import abstractmethod
+
+class Abstract_Power_Monitor(object):
+ """
+ Provides a simple interface to use the power meter.
+ The implementer should establish communications with the power monitor
+ on __init__:
+
+ monitor = <<Implementing Class>>(...)
+
+ Afterward, data can be collected multiple times via the
+ series of calls, for example:
+
+ montior.StartDataCollection()
+ data = True
+ while data is not None:
+ data = monitor.CollectData()
+ <<do-something-with-data>>
+ monitor.StopDataCollection()
+
+ On exit, Close should be called:
+
+ monitor.Close()
+
+ The method GetStatus() can be used to check voltage setting as well as
+ ensure status of the USB passthrough connection.
+ """
+
+
+
+ def __init__(self ):
+ pass
+
+ @abstractmethod
+ def Close(self):
+ """Close the connection to the device, preventing
+ further interactions. This should be called only
+ when the power monitor is no longer needed
+ (e.g. on exit)
+ """
+ pass
+
+ @abstractmethod
+ def GetStatus(self):
+ """ Requests and waits for status. Returns status dictionary.
+ This should at a minimum contain entries for "usbPassthroughMode"
+ and "outputVoltageSetting" """
+ pass
+
+ @abstractmethod
+ def SetVoltage(self, v):
+ """ Set the output voltage, 0 to disable. """
+ pass
+
+ @abstractmethod
+ def SetMaxCurrent(self, i):
+ """Set the max output current."""
+ pass
+
+ @abstractmethod
+ def SetUsbPassthrough(self, val):
+ """ Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto. """
+ pass
+
+ @abstractmethod
+ def StartDataCollection(self):
+ """ Tell the device to start collecting and sending measurement data. """
+ pass
+
+ @abstractmethod
+ def StopDataCollection(self):
+ """ Tell the device to stop collecting measurement data. """
+ pass
+
+ @abstractmethod
+ def CollectData(self, verbose = True):
+ """ Return some current samples. Call StartDataCollection() first.
+ Returns None if no data available"""
+ pass
diff --git a/apps/CtsVerifier/assets/scripts/power_monitors/_dummy.py b/apps/CtsVerifier/assets/scripts/power_monitors/_dummy.py
new file mode 100644
index 0000000..16c2f3c
--- /dev/null
+++ b/apps/CtsVerifier/assets/scripts/power_monitors/_dummy.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+# 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.
+
+from . import Abstract_Power_Monitor
+
+class Power_Monitor(Abstract_Power_Monitor):
+ """
+ Dummy implementation for internal use only to test host-to-device
+ interactions without need for a real power monitor. This is not
+ to be used in any way as part of CtsVerifier or CTS verification
+ activities in general.
+ """
+
+
+ def __init__(self, device = None, wait = False, log_file_id = None ):
+ self._device = device
+ self._usbpassthroughmode = 1
+ self._voltage = 0.0
+ self._data_active = False
+ self._sequence = 0
+
+ def __del__(self):
+ self.Close()
+
+ def Close(self):
+ pass
+
+ @staticmethod
+ def Discover():
+ return ["dummy_monitor"]
+
+ def GetStatus(self):
+ """ Requests and waits for status. Returns status dictionary. """
+ return {"usbPassthroughMode": self._usbpassthroughmode,
+ "sampleRate":1}
+
+
+ def RampVoltage(self, start, end):
+ self._voltage = end
+
+ def SetVoltage(self, v):
+ """ Set the output voltage, 0 to disable. """
+ self._voltage = v
+
+ def SetMaxCurrent(self, i):
+ """Set the max output current."""
+ self._max_current = i
+
+ def SetUsbPassthrough(self, val):
+ """ Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto. """
+ self._usbpassthroughmode = val
+
+ def StartDataCollection(self):
+ """ Tell the device to start collecting and sending measurement data. """
+ self._data_active = True
+
+ def StopDataCollection(self):
+ """ Tell the device to stop collecting measurement data. """
+ self._data_active = False
+
+ def CollectData(self, verbose = True):
+ """ Return some current samples. Call StartDataCollection() first. """
+ #self.log("Collecting data ...", debug = True)
+ import random
+ if self._data_active:
+ base = [0.003, 0.003, 0.003, (self._sequence%4)*0.0005]
+ self._sequence += 1
+ values = [ random.gauss(base[(self._sequence-1)%4], 0.0005) for _ in range(100)]
+ else:
+ values = None
+ return values
diff --git a/apps/CtsVerifier/assets/scripts/power_monitors/monsoon.py b/apps/CtsVerifier/assets/scripts/power_monitors/monsoon.py
new file mode 100644
index 0000000..c02f113
--- /dev/null
+++ b/apps/CtsVerifier/assets/scripts/power_monitors/monsoon.py
@@ -0,0 +1,433 @@
+#!/usr/bin/python
+
+# 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.
+
+
+import fcntl
+import logging
+logging.getLogger().setLevel(logging.ERROR)
+
+import os.path
+import select
+import stat
+import struct
+import sys
+import time
+import collections
+import socket
+import glob
+import signal
+import serial # http://pyserial.sourceforge.net/
+
+#Set to True if you want log output to go to screen:
+LOG_TO_SCREEN = False
+
+TIMEOUT_SERIAL = 1 #seconds
+
+#ignore SIG CONTINUE signals
+for signum in [signal.SIGCONT]:
+ signal.signal(signum, signal.SIG_IGN)
+
+try:
+ from . import Abstract_Power_Monitor
+except:
+ sys.exit("You cannot run 'monsoon.py' directly. Run 'execut_power_tests.py' instead.")
+
+class Power_Monitor(Abstract_Power_Monitor):
+ """
+ Provides a simple class to use the power meter, e.g.
+ mon = monsoon.Power_Monitor()
+ mon.SetVoltage(3.7)
+ mon.StartDataCollection()
+ mydata = []
+ while len(mydata) < 1000:
+ mydata.extend(mon.CollectData())
+ mon.StopDataCollection()
+ """
+ _do_log = False
+
+ @staticmethod
+ def lock( device ):
+ tmpname = "/tmp/monsoon.%s.%s" % ( os.uname()[0],
+ os.path.basename(device))
+ lockfile = open(tmpname, "w")
+ try: # use a lockfile to ensure exclusive access
+ fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ logging.debug("Locked device %s"%device)
+ except IOError as e:
+ self.log("device %s is in use" % dev)
+ sys.exit('device in use')
+ return lockfile
+
+ def to_string(self):
+ return self._devicename
+
+ def __init__(self, device = None, wait = False, log_file_id= None ):
+ """
+ Establish a connection to a Power_Monitor.
+ By default, opens the first available port, waiting if none are ready.
+ A particular port can be specified with "device".
+ With wait=0, IOError is thrown if a device is not immediately available.
+ """
+ self._lockfile = None
+ self._logfile = None
+ self.ser = None
+ for signum in [signal.SIGALRM, signal.SIGHUP, signal.SIGINT,
+ signal.SIGILL, signal.SIGQUIT,
+ signal.SIGTRAP,signal.SIGABRT, signal.SIGIOT, signal.SIGBUS,
+ signal.SIGFPE, signal.SIGSEGV, signal.SIGUSR2, signal.SIGPIPE,
+ signal.SIGTERM]:
+ signal.signal(signum, self.handle_signal)
+
+ self._coarse_ref = self._fine_ref = self._coarse_zero = self._fine_zero = 0
+ self._coarse_scale = self._fine_scale = 0
+ self._last_seq = 0
+ self.start_voltage = 0
+
+ if device:
+ if isinstance( device, serial.Serial ):
+ self.ser = device
+
+ else:
+ device_list = None
+ while not device_list:
+ device_list = Power_Monitor.Discover()
+ if not device_list and wait:
+ time.sleep(1.0)
+ logging.info("No power monitor serial devices found. Retrying...")
+ elif not device_list and not wait:
+ logging.error("No power monitor serial devices found. Exiting")
+ self.Close()
+ sys.exit("No power monitor serial devices found")
+
+ if device_list:
+ if len(device_list) > 1:
+ logging.error("=======================================")
+ logging.error("More than one power monitor discovered!")
+ logging.error("Test may not execute properly.Aborting test.")
+ logging.error("=======================================")
+ sys.exit("More than one power monitor connected.")
+ device = device_list[0].to_string() # choose the first one
+ if len(device_list) > 1:
+ logging.info("More than one device found. Using %s"%device)
+ else:
+ logging.info("Power monitor @ %s"%device)
+ else: raise IOError("No device found")
+
+ self._lockfile = Power_Monitor.lock( device )
+ if log_file_id is not None:
+ self._logfilename = "/tmp/monsoon_%s_%s.%s.log" % (os.uname()[0], os.path.basename(device),
+ log_file_id)
+ self._logfile = open(self._logfilename,'a')
+ else:
+ self._logfile = None
+ try:
+ self.ser = serial.Serial(device, timeout= TIMEOUT_SERIAL)
+ except Exception as e:
+ self.log( "error opening device %s: %s" % (dev, e))
+ self._lockfile.close()
+ raise
+ logging.debug("Setting up power monitor...")
+ self._devicename = device
+ #just in case, stop any active data collection on monsoon
+ self._dataCollectionActive = True
+ self.StopDataCollection()
+ logging.debug("Flushing input...")
+ self._FlushInput() # discard stale input
+ logging.debug("Getting status....")
+ status = self.GetStatus()
+
+ if not status:
+ self.log( "no response from device %s" % device)
+ self._lockfile.close()
+ raise IOError("Failed to get status from device")
+ self.start_voltage = status["voltage1"]
+
+ def __del__(self):
+ self.Close()
+
+ def Close(self):
+ if self._logfile:
+ print("=============\n"+\
+ "Power Monitor log file can be found at '%s'"%self._logfilename +
+ "=============\n")
+ self._logfile.close()
+ self._logfile = None
+ if (self.ser):
+ #self.StopDataCollection()
+ self.ser.flush()
+ self.ser.close()
+ self.ser = None
+ if self._lockfile:
+ self._lockfile.close()
+
+ def log(self, msg , debug = False):
+ if self._logfile: self._logfile.write( msg + "\n")
+ if not debug and LOG_TO_SCREEN:
+ logging.error( msg )
+ else:
+ logging.debug(msg)
+
+ def handle_signal( self, signum, frame):
+ if self.ser:
+ self.ser.flush()
+ self.ser.close()
+ self.ser = None
+ self.log("Got signal %d"%signum)
+ sys.exit("\nGot signal %d\n"%signum)
+
+ @staticmethod
+ def Discover():
+ monsoon_list = []
+ elapsed = 0
+ logging.info("Discovering power monitor(s)...")
+ ser_device_list = glob.glob("/dev/ttyACM*")
+ logging.info("Seeking devices %s"%ser_device_list)
+ for dev in ser_device_list:
+ try:
+ lockfile = Power_Monitor.lock( dev )
+ except:
+ logging.info( "... device %s in use, skipping"%dev)
+ continue
+ tries = 0
+ ser = None
+ while ser is None and tries < 100:
+ try: # try to open the device
+ ser = serial.Serial( dev, timeout=TIMEOUT_SERIAL)
+ except Exception as e:
+ logging.error( "error opening device %s: %s" % (dev, e) )
+ tries += 1
+ time.sleep(2);
+ ser = None
+ logging.info("... found device %s"%dev)
+ lockfile.close()#will be re-locked once monsoon instance created
+ logging.debug("unlocked")
+ if not ser:
+ continue
+ if ser is not None:
+ try:
+ monsoon = Power_Monitor(device = dev)
+ status = monsoon.GetStatus()
+
+ if not status:
+ monsoon.log("... no response from device %s, skipping")
+ continue
+ else:
+ logging.info("... found power monitor @ %s"%dev)
+ monsoon_list.append( monsoon )
+ except:
+ import traceback
+ traceback.print_exc()
+ logging.error("... %s appears to not be a monsoon device"%dev)
+ logging.debug("Returning list of %s"%monsoon_list)
+ return monsoon_list
+
+ def GetStatus(self):
+ """ Requests and waits for status. Returns status dictionary. """
+
+ # status packet format
+ self.log("Getting status...", debug = True)
+ STATUS_FORMAT = ">BBBhhhHhhhHBBBxBbHBHHHHBbbHHBBBbbbbbbbbbBH"
+ STATUS_FIELDS = [
+ "packetType", "firmwareVersion", "protocolVersion",
+ "mainFineCurrent", "usbFineCurrent", "auxFineCurrent", "voltage1",
+ "mainCoarseCurrent", "usbCoarseCurrent", "auxCoarseCurrent", "voltage2",
+ "outputVoltageSetting", "temperature", "status", "leds",
+ "mainFineResistor", "serialNumber", "sampleRate",
+ "dacCalLow", "dacCalHigh",
+ "powerUpCurrentLimit", "runTimeCurrentLimit", "powerUpTime",
+ "usbFineResistor", "auxFineResistor",
+ "initialUsbVoltage", "initialAuxVoltage",
+ "hardwareRevision", "temperatureLimit", "usbPassthroughMode",
+ "mainCoarseResistor", "usbCoarseResistor", "auxCoarseResistor",
+ "defMainFineResistor", "defUsbFineResistor", "defAuxFineResistor",
+ "defMainCoarseResistor", "defUsbCoarseResistor", "defAuxCoarseResistor",
+ "eventCode", "eventData", ]
+
+ self._SendStruct("BBB", 0x01, 0x00, 0x00)
+ while True: # Keep reading, discarding non-status packets
+ bytes = self._ReadPacket()
+ if not bytes: return None
+ if len(bytes) != struct.calcsize(STATUS_FORMAT) or bytes[0] != "\x10":
+ self.log("wanted status, dropped type=0x%02x, len=%d" % (
+ ord(bytes[0]), len(bytes)))
+ continue
+
+ status = dict(zip(STATUS_FIELDS, struct.unpack(STATUS_FORMAT, bytes)))
+ assert status["packetType"] == 0x10
+ for k in status.keys():
+ if k.endswith("VoltageSetting"):
+ status[k] = 2.0 + status[k] * 0.01
+ elif k.endswith("FineCurrent"):
+ pass # needs calibration data
+ elif k.endswith("CoarseCurrent"):
+ pass # needs calibration data
+ elif k.startswith("voltage") or k.endswith("Voltage"):
+ status[k] = status[k] * 0.000125
+ elif k.endswith("Resistor"):
+ status[k] = 0.05 + status[k] * 0.0001
+ if k.startswith("aux") or k.startswith("defAux"): status[k] += 0.05
+ elif k.endswith("CurrentLimit"):
+ status[k] = 8 * (1023 - status[k]) / 1023.0
+ #self.log( "Returning requested status: \n %s"%(status), debug = True)
+ return status
+
+ def RampVoltage(self, start, end):
+ v = start
+ if v < 3.0: v = 3.0 # protocol doesn't support lower than this
+ while (v < end):
+ self.SetVoltage(v)
+ v += .1
+ time.sleep(.1)
+ self.SetVoltage(end)
+
+ def SetVoltage(self, v):
+ """ Set the output voltage, 0 to disable. """
+ self.log("Setting voltage to %s..."%v, debug = True)
+ if v == 0:
+ self._SendStruct("BBB", 0x01, 0x01, 0x00)
+ else:
+ self._SendStruct("BBB", 0x01, 0x01, int((v - 2.0) * 100))
+ self.log("...Set voltage", debug = True)
+
+ def SetMaxCurrent(self, i):
+ """Set the max output current."""
+ assert i >= 0 and i <= 8
+ self.log("Setting max current to %s..."%i, debug = True)
+ val = 1023 - int((i/8)*1023)
+ self._SendStruct("BBB", 0x01, 0x0a, val & 0xff)
+ self._SendStruct("BBB", 0x01, 0x0b, val >> 8)
+ self.log("...Set max current.", debug = True)
+
+ def SetUsbPassthrough(self, val):
+ """ Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto. """
+ self._SendStruct("BBB", 0x01, 0x10, val)
+
+ def StartDataCollection(self):
+ """ Tell the device to start collecting and sending measurement data. """
+ self.log("Starting data collection...", debug = True)
+ self._SendStruct("BBB", 0x01, 0x1b, 0x01) # Mystery command
+ self._SendStruct("BBBBBBB", 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
+ self.log("...started", debug = True)
+ self._dataCollectionActive = True
+
+ def StopDataCollection(self):
+ """ Tell the device to stop collecting measurement data. """
+ self._SendStruct("BB", 0x03, 0x00) # stop
+ if self._dataCollectionActive:
+ while self.CollectData(False) is not None:
+ pass
+ self._dataCollectionActive = False
+
+ def CollectData(self, verbose = True):
+ """ Return some current samples. Call StartDataCollection() first. """
+ #self.log("Collecting data ...", debug = True)
+ while True: # loop until we get data or a timeout
+ bytes = self._ReadPacket(verbose)
+
+ if not bytes: return None
+ if len(bytes) < 4 + 8 + 1 or bytes[0] < "\x20" or bytes[0] > "\x2F":
+ if verbose: self.log( "wanted data, dropped type=0x%02x, len=%d" % (
+ ord(bytes[0]), len(bytes)), debug=verbose)
+ continue
+
+ seq, type, x, y = struct.unpack("BBBB", bytes[:4])
+ data = [struct.unpack(">hhhh", bytes[x:x+8])
+ for x in range(4, len(bytes) - 8, 8)]
+
+ if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
+ self.log( "data sequence skipped, lost packet?" )
+ self._last_seq = seq
+
+ if type == 0:
+ if not self._coarse_scale or not self._fine_scale:
+ self.log("waiting for calibration, dropped data packet")
+ continue
+
+ out = []
+ for main, usb, aux, voltage in data:
+ if main & 1:
+ out.append(((main & ~1) - self._coarse_zero) * self._coarse_scale)
+ else:
+ out.append((main - self._fine_zero) * self._fine_scale)
+ #self.log("...Collected %d samples"%(len(out)), debug = True)
+ return out
+
+ elif type == 1:
+ self._fine_zero = data[0][0]
+ self._coarse_zero = data[1][0]
+
+ elif type == 2:
+ self._fine_ref = data[0][0]
+ self._coarse_ref = data[1][0]
+
+ else:
+ self.log( "discarding data packet type=0x%02x" % type)
+ continue
+
+ if self._coarse_ref != self._coarse_zero:
+ self._coarse_scale = 2.88 / (self._coarse_ref - self._coarse_zero)
+ if self._fine_ref != self._fine_zero:
+ self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
+
+
+ def _SendStruct(self, fmt, *args):
+ """ Pack a struct (without length or checksum) and send it. """
+ data = struct.pack(fmt, *args)
+ data_len = len(data) + 1
+ checksum = (data_len + sum(struct.unpack("B" * len(data), data))) % 256
+ out = struct.pack("B", data_len) + data + struct.pack("B", checksum)
+ self.ser.write(out)
+ self.ser.flush()
+
+ def _ReadPacket(self, verbose = True):
+ """ Read a single data record as a string (without length or checksum). """
+ len_char = self.ser.read(1)
+ if not len_char:
+ if verbose: self.log( "timeout reading from serial port" )
+ return None
+
+ data_len = struct.unpack("B", len_char)
+ data_len = ord(len_char)
+ if not data_len: return ""
+
+ result = self.ser.read(data_len)
+ if len(result) != data_len: return None
+ body = result[:-1]
+ checksum = (data_len + sum(struct.unpack("B" * len(body), body))) % 256
+ if result[-1] != struct.pack("B", checksum):
+ self.log( "Invalid checksum from serial port" )
+ return None
+ return result[:-1]
+
+ def _FlushInput(self):
+ """ Flush all read data until no more available. """
+ self.ser.flushInput()
+ flushed = 0
+ self.log("Flushing input...", debug = True)
+ while True:
+ ready_r, ready_w, ready_x = select.select([self.ser], [], [self.ser], 0)
+ if len(ready_x) > 0:
+ self.log( "exception from serial port" )
+ return None
+ elif len(ready_r) > 0:
+ flushed += 1
+ self.ser.read(1) # This may cause underlying buffering.
+ self.ser.flush() # Flush the underlying buffer too.
+ else:
+ break
+ if flushed > 0:
+ self.log( "flushed >%d bytes" % flushed, debug = True )
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 5683391..15747b3 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -424,10 +424,35 @@
<string name="offhostService">OffhostService</string>
<!-- Strings for Sensor Test Activities -->
+ <string name="snsr_test_summary">Tests passed: %1$d, Tests skipped: %2$d, Tests failed: %3$d</string>
+ <string name="snsr_test_complete">Test completed.</string>
+ <string name="snsr_test_pass">PASS</string>
+ <string name="snsr_test_skipped">SKIPPED</string>
+ <string name="snsr_test_fail">FAIL</string>
+ <string name="snsr_executing_test">\nExecuting test case \'%1$s\'..\n</string>
+ <string name="snsr_orientation_portrait">[Device orientation]: Portrait.</string>
+ <string name="snsr_orientation_landscape">[Device orientation]: Landscape.</string>
+
+ <!-- Strings to interact with users in Sensor Tests -->
+ <string name="snsr_test_play_sound">A sound will be played once the verification is complete...</string>
+ <string name="snsr_device_steady">Keep the device steady.</string>
+ <string name="snsr_wait_for_user">Press \'Next\' to continue.</string>
+ <string name="snsr_on_complete_return">After completing the task, go back to this test.</string>
+ <string name="snsr_airplane_mode_set">Airplane mode set.</string>
+ <string name="snsr_airplane_mode_request">You will be redirected to set \'Airplane Mode\' ON.</string>
+ <string name="snsr_screen_off_timeout">Screen Off Timeout set to: %1$d seconds.</string>
+ <string name="snsr_screen_off_request">You will be redirected to set \'Display Sleep\' to %1$d seconds.</string>
+
<!-- Accelerometer -->
<string name="snsr_accel_test">Accelerometer Test</string>
<string name="snsr_accel_test_info">This test verifies that the accelerometer is working properly. As you move the device around through space, the triangle should always point down (i.e. in the direction of gravity.) If it does not, the accelerometer is improperly configured.</string>
<string name="snsr_accel_m_test">Accelerometer Measurement Tests</string>
+ <string name="snsr_accel_test_face_up">Place the device in a flat surface with the screen facing the ceiling.</string>
+ <string name="snsr_accel_test_face_down">Place the device in a flat surface with the screen facing it.</string>
+ <string name="snsr_accel_test_right_side">Place the device in a flat surface resting vertically on its right side.</string>
+ <string name="snsr_accel_test_left_side">Place the device in a flat surface resting vertically on its left side.</string>
+ <string name="snsr_accel_test_top_side">Place the device in a flat surface resting vertically on its top side.</string>
+ <string name="snsr_accel_test_bottom_side">Place the device in a flat surface resting vertically on its bottom side.</string>
<!-- Gyroscope -->
<string name="snsr_gyro_test">Gyroscope Test</string>
@@ -438,6 +463,16 @@
<string name="snsr_gyro_test_degrees_title">Wrong units?</string>
<string name="snsr_gyro_test_degrees_message">These values looks like degrees per second. These should be radians per second!</string>
<string name="snsr_gyro_m_test">Gyroscope Measurement Test</string>
+ <string name="snsr_gyro_device_placement">Place the device in a flat surface with the screen
+ facing the ceiling, make sure the device aligns with the orientation specified for each
+ scenario. Then follow the instructions:</string>
+ <string name="snsr_gyro_device_static">Leave the device static.</string>
+ <string name="snsr_gyro_rotate_clockwise">Rotate the device clockwise.</string>
+ <string name="snsr_gyro_rotate_counter_clockwise">Rotate the device counter clockwise.</string>
+ <string name="snsr_gyro_rotate_right_side">Rotate the device on its right side until it stands on its side.</string>
+ <string name="snsr_gyro_rotate_left_side">Rotate the device on its left side until it stands on its side.</string>
+ <string name="snsr_gyro_rotate_top_side">Rotate the device on its top side until it stands perpendicular.</string>
+ <string name="snsr_gyro_rotate_bottom_side">Rotate the device on its bottom side util it stands perpendicular.</string>
<!-- Heart Rate -->
<string name="snsr_heartrate_test">Heart Rate Test</string>
@@ -447,6 +482,11 @@
<!-- Magnetic Field -->
<string name="snsr_mag_m_test">Magnetic Field Measurement Tests</string>
+ <string name="snsr_mag_verify_norm">Verifying the Norm...</string>
+ <string name="snsr_mag_verify_std_dev">Verifying the Standard Deviation...</string>
+ <string name="snsr_mag_calibration_description">Please calibrate the Magnetometer by moving
+ it in 8 shapes in different orientations.</string>
+ <string name="snsr_mag_calibration_complete">When done, leave the device in a flat surface.</string>
<!-- Sensor Value Accuracy -->
<string name="snsr_val_acc_test">Sensor Value Accuracy Tests</string>
@@ -612,6 +652,7 @@
<!-- Strings for USB accessory test activity -->
<string name="usb_accessory_test">USB Accessory Test</string>
+ <string name="sensor_power_test">Sensor Power Test</string>
<string name="usb_accessory_test_info">
1. Connect your Android device to a computer and run the \'cts-usb-accessory\' program
included with the CTS Verifier bundle.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
index edac3c5..f40bc75 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.sensors;
+import com.android.cts.verifier.R;
+
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
@@ -33,37 +35,37 @@
public String testFaceUp() throws Throwable {
return verifyMeasurements(
- "Place the device in a flat surface with the screen facing the ceiling",
+ R.string.snsr_accel_test_face_up,
0, 0, SensorManager.STANDARD_GRAVITY);
}
public String testFaceDown() throws Throwable {
return delayedVerifyMeasurements(
- "Press 'Next' and place the device in a flat surface with the screen facing it",
+ R.string.snsr_accel_test_face_down,
0, 0, -SensorManager.STANDARD_GRAVITY);
}
public String testRightSide() throws Throwable {
return verifyMeasurements(
- "Place the device in a flat surface resting vertically on its right side",
+ R.string.snsr_accel_test_right_side,
-SensorManager.STANDARD_GRAVITY, 0, 0);
}
public String testLeftSide() throws Throwable {
return verifyMeasurements(
- "Place the device in a flat surface resting vertically on its left side",
+ R.string.snsr_accel_test_left_side,
SensorManager.STANDARD_GRAVITY, 0, 0);
}
public String testTopSide() throws Throwable {
return verifyMeasurements(
- "Place the device in a flat surface resting vertically on its top side",
+ R.string.snsr_accel_test_top_side,
0, -SensorManager.STANDARD_GRAVITY, 0);
}
public String testBottomSide() throws Throwable {
return verifyMeasurements(
- "Place the device in a flat surface resting vertically on its bottom side",
+ R.string.snsr_accel_test_bottom_side,
0, SensorManager.STANDARD_GRAVITY, 0);
}
@@ -101,11 +103,10 @@
return null;
}
- private String delayedVerifyMeasurements(
- String message,
- float ... expectations) throws Throwable {
- appendText(String.format("\n%s.", message));
- appendText("A sound will be played once the verification is complete...");
+ private String delayedVerifyMeasurements(int descriptionResId, float ... expectations)
+ throws Throwable {
+ appendText(descriptionResId);
+ appendText(R.string.snsr_test_play_sound);
waitForUser();
Thread.sleep(TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS));
@@ -116,9 +117,10 @@
}
}
- private String verifyMeasurements(String message, float ... expectations) throws Throwable {
- appendText(String.format("\n%s.", message));
- appendText("Press 'Next' when ready and keep the device steady.");
+ private String verifyMeasurements(int descriptionResId, float ... expectations)
+ throws Throwable {
+ appendText(descriptionResId);
+ appendText(R.string.snsr_device_steady);
waitForUser();
return verifyMeasurements(expectations);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
index a550cde..8685d43 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.sensors;
+import com.android.cts.verifier.R;
+
import android.graphics.Color;
import android.hardware.cts.helpers.SensorNotSupportedException;
@@ -47,7 +49,7 @@
message = e.getMessage();
}
setTestResult(getTestId(), testResult, message);
- appendText("\nTest completed. Press 'Next' to finish.\n");
+ appendText(R.string.snsr_test_complete);
waitForUser();
finish();
}
@@ -63,7 +65,7 @@
protected abstract void onRun() throws Throwable;
protected void logSuccess() {
- appendText("SUCCESS", Color.GREEN);
+ appendText(R.string.snsr_test_pass, Color.GREEN);
}
private String getTestId() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
index f63c5b7..7953f25 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
@@ -71,6 +71,10 @@
private Thread mWorkerThread;
private CountDownLatch mCountDownLatch;
+ private volatile int mTestPassedCounter;
+ private volatile int mTestSkippedCounter;
+ private volatile int mTestFailedCounter;
+
protected BaseSensorTestActivity(Class testClass) {
mTestClass = testClass;
}
@@ -97,58 +101,39 @@
@Override
public void run() {
- String testClassName = mTestClass.getName();
-
try {
activitySetUp();
} catch (Throwable e) {
- setTestResult(testClassName, SensorTestResult.SKIPPED, e.getMessage());
+ SensorTestResult testSkipped = SensorTestResult.SKIPPED;
+ String testSummary = e.getMessage();
+ setTestResult(getTestClassName(), testSkipped, testSummary);
+ logTestDetails(testSkipped, testSummary);
return;
}
// TODO: it might be necessary to implement fall through so passed tests do not need to
// be re-executed
- int testPassedCounter = 0;
- int testSkippedCounter = 0;
- int testFailedCounter = 0;
+ StringBuilder overallTestResults = new StringBuilder();
for (Method testMethod : findTestMethods()) {
- String testName = String.format("%s.%s", testClassName, testMethod.getName());
- try {
- appendText("\nExecuting test case '" + testName + "'...");
- String testDetails = (String) testMethod.invoke(this);
- setTestResult(testName, SensorTestResult.PASS, testDetails);
- ++testPassedCounter;
- } catch (InvocationTargetException e) {
- // get the inner exception, because we use reflection APIs to execute the test
- Throwable cause = e.getCause();
- SensorTestResult testResult;
- if (cause instanceof SensorNotSupportedException) {
- testResult = SensorTestResult.SKIPPED;
- ++testSkippedCounter;
- } else {
- testResult = SensorTestResult.FAIL;
- ++testFailedCounter;
- }
- setTestResult(testName, testResult, cause.getMessage());
- } catch (Throwable e) {
- setTestResult(testName, SensorTestResult.FAIL, e.getMessage());
- ++testFailedCounter;
- }
+ SensorTestDetails testDetails = executeTest(testMethod);
+ setTestResult(testDetails.name, testDetails.result, testDetails.summary);
+ logTestDetails(testDetails.result, testDetails.summary);
+ overallTestResults.append(testDetails.toString() + "\n");
}
- setOverallTestResult(
- testClassName,
- testPassedCounter,
- testSkippedCounter,
- testFailedCounter);
+ // log to screen and save the overall test summary (activity level)
+ SensorTestDetails testDetails = getOverallTestDetails();
+ logTestDetails(testDetails.result, testDetails.summary);
+ overallTestResults.append(testDetails.summary);
+ setTestResult(testDetails.name, testDetails.result, overallTestResults.toString());
try {
activityCleanUp();
} catch (Throwable e) {
- appendText("An error occurred on Activity CleanUp.");
- appendText(e.getLocalizedMessage(), Color.RED);
+ appendText(e.getMessage(), Color.RED);
+ Log.e(LOG_TAG, "An error occurred on Activity CleanUp.", e);
}
- appendText("\nTest completed. Press 'Next' to finish.\n");
+ appendText(R.string.snsr_test_complete);
waitForUser();
finish();
}
@@ -158,7 +143,20 @@
mCountDownLatch.countDown();
}
- protected enum SensorTestResult {
+ private static class SensorTestDetails {
+ public SensorTestResult result;
+ public String name;
+ public String summary;
+
+ @Override
+ public String toString() {
+ return String.format("%s|%s|%s", name, result.name(), summary);
+ }
+ }
+
+ // TODO: this should be protected currently it is used by power tests, but the result should
+ // only be available in this class
+ public enum SensorTestResult {
SKIPPED,
PASS,
FAIL
@@ -168,6 +166,22 @@
* For use only by {@link BaseSensorSemiAutomatedTestActivity} and other base classes.
*/
protected void setTestResult(String testId, SensorTestResult testResult, String testDetails) {
+ switch(testResult) {
+ case SKIPPED:
+ TestResult.setPassedResult(this, testId, testDetails);
+ break;
+ case PASS:
+ TestResult.setPassedResult(this, testId, testDetails);
+ break;
+ case FAIL:
+ TestResult.setFailedResult(this, testId, testDetails);
+ break;
+ default:
+ throw new InvalidParameterException("Unrecognized testResult.");
+ }
+ }
+
+ private void logTestDetails(SensorTestResult testResult, String testSummary) {
int textColor;
int logPriority;
String testResultString;
@@ -175,29 +189,26 @@
case SKIPPED:
textColor = Color.YELLOW;
logPriority = Log.INFO;
- testResultString = "SKIPPED";
- TestResult.setPassedResult(this, testId, testDetails);
+ testResultString = getString(R.string.snsr_test_skipped);
break;
case PASS:
textColor = Color.GREEN;
logPriority = Log.DEBUG;
- testResultString = "PASS";
- TestResult.setPassedResult(this, testId, testDetails);
+ testResultString = getString(R.string.snsr_test_pass);
break;
case FAIL:
textColor = Color.RED;
logPriority = Log.ERROR;
- testResultString = "FAIL";
- TestResult.setFailedResult(this, testId, testDetails);
+ testResultString = getString(R.string.snsr_test_fail);
break;
default:
throw new InvalidParameterException("Unrecognized testResult.");
}
- if (TextUtils.isEmpty(testDetails)) {
- testDetails = testResultString;
+ if (TextUtils.isEmpty(testSummary)) {
+ testSummary = testResultString;
}
- appendText(testDetails, textColor);
- Log.println(logPriority, LOG_TAG, testDetails);
+ appendText("\n" + testSummary, textColor);
+ Log.println(logPriority, LOG_TAG, testSummary);
}
/**
@@ -216,10 +227,20 @@
*/
protected void activityCleanUp() throws Throwable {}
+ protected void appendText(int resId, int textColor) {
+ appendText(getString(resId), textColor);
+ }
+
+ @Deprecated
protected void appendText(String text, int textColor) {
this.runOnUiThread(new TextAppender(mLogView, text, textColor));
}
+ protected void appendText(int resId) {
+ appendText(getString(resId));
+ }
+
+ @Deprecated
protected void appendText(String text) {
this.runOnUiThread(new TextAppender(mLogView, text));
}
@@ -238,6 +259,7 @@
}
protected void waitForUser() {
+ appendText(R.string.snsr_wait_for_user);
updateButton(true);
try {
mSemaphore.acquire();
@@ -269,24 +291,60 @@
return testMethods;
}
- private void setOverallTestResult(
- String testClassName,
- int testPassedCount,
- int testSkippedCount,
- int testFailedCount) {
- SensorTestResult overallTestResult = SensorTestResult.PASS;
- if (testFailedCount > 0) {
- overallTestResult = SensorTestResult.FAIL;
- } else if (testSkippedCount > 0 || testPassedCount == 0) {
- overallTestResult = SensorTestResult.SKIPPED;
+ private SensorTestDetails executeTest(Method testMethod) {
+ SensorTestDetails testDetails = new SensorTestDetails();
+ testDetails.name = String.format("%s.%s", getTestClassName(), testMethod.getName());
+
+ try {
+ appendText(getString(R.string.snsr_executing_test, testDetails.name));
+ testDetails.summary = (String) testMethod.invoke(this);
+ testDetails.result = SensorTestResult.PASS;
+ ++mTestPassedCounter;
+ } catch (InvocationTargetException e) {
+ // get the inner exception, because we use reflection APIs to execute the test
+ Throwable cause = e.getCause();
+ testDetails.summary = cause.getMessage();
+ if (cause instanceof SensorNotSupportedException) {
+ testDetails.result = SensorTestResult.SKIPPED;
+ ++mTestSkippedCounter;
+ } else {
+ testDetails.result = SensorTestResult.FAIL;
+ ++mTestFailedCounter;
+ }
+ } catch (Throwable e) {
+ testDetails.summary = e.getMessage();
+ testDetails.result = SensorTestResult.FAIL;
+ ++mTestFailedCounter;
}
- String testSummary = String.format(
- "\n\nTestsPassed=%d, TestsSkipped=%d, TestFailed=%d",
- testPassedCount,
- testSkippedCount,
- testFailedCount);
- setTestResult(testClassName, overallTestResult, testSummary);
+ return testDetails;
+ }
+
+ private SensorTestDetails getOverallTestDetails() {
+ SensorTestDetails testDetails = new SensorTestDetails();
+ testDetails.name = getTestClassName();
+
+ testDetails.result = SensorTestResult.PASS;
+ if (mTestFailedCounter > 0) {
+ testDetails.result = SensorTestResult.FAIL;
+ } else if (mTestSkippedCounter > 0 || mTestPassedCounter == 0) {
+ testDetails.result = SensorTestResult.SKIPPED;
+ }
+
+ testDetails.summary = getString(
+ R.string.snsr_test_summary,
+ mTestPassedCounter,
+ mTestSkippedCounter,
+ mTestFailedCounter);
+
+ return testDetails;
+ }
+
+ private String getTestClassName() {
+ if (mTestClass == null) {
+ return "<unknown>";
+ }
+ return mTestClass.getName();
}
private class TextAppender implements Runnable {
@@ -335,12 +393,12 @@
protected void askToSetAirplaneMode() throws InterruptedException {
if (isAirplaneModeOn()) {
- appendText("Airplane mode set.");
+ appendText(R.string.snsr_airplane_mode_set);
+ appendText(R.string.snsr_on_complete_return);
return;
}
- appendText("You will be redirected to set 'Airplane Mode' ON, after doing so, go back to " +
- "this App. Press Next to continue.\n");
+ appendText(R.string.snsr_airplane_mode_request);
waitForUser();
launchAndWaitForSubactivity(Settings.ACTION_WIRELESS_SETTINGS);
@@ -352,12 +410,11 @@
protected void askToSetScreenOffTimeout(int timeoutInSec) throws InterruptedException {
long timeoutInMs = TimeUnit.SECONDS.toMillis(timeoutInSec);
if (isScreenOffTimeout(timeoutInMs)) {
- appendText("Screen Off Timeout set to: " + timeoutInSec + " seconds.");
+ appendText(getString(R.string.snsr_screen_off_timeout, timeoutInSec));
return;
}
- appendText("You will be redirected to set 'Display Sleep' to " + timeoutInSec + " seconds" +
- ", after doing so, go back to this App. Press Next to continue.\n");
+ appendText(getString(R.string.snsr_screen_off_request, timeoutInSec));
waitForUser();
launchAndWaitForSubactivity(Settings.ACTION_DISPLAY_SETTINGS);
@@ -410,4 +467,4 @@
}
return screenOffTimeoutInMs <= expectedTimeoutInMs;
}
-}
\ No newline at end of file
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
index 066bda4..0c6b931 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.sensors;
+import com.android.cts.verifier.R;
+
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
@@ -24,45 +26,61 @@
/**
* Semi-automated test that focuses on characteristics associated with Accelerometer measurements.
*/
-public class GyroscopeMeasurementTestActivity extends BaseSensorSemiAutomatedTestActivity {
- @Override
- protected void onRun() throws Throwable {
- appendText("Place the device in a flat surface with the screen facing the ceiling, make "
- + "sure the device aligns with the orientation specified for each scenario. Then "
- + "follow the instructions for each scenario:");
+public class GyroscopeMeasurementTestActivity extends BaseSensorTestActivity {
+ public GyroscopeMeasurementTestActivity() {
+ super(GyroscopeMeasurementTestActivity.class);
+ }
- verifyMeasurements(
- "leave the device static",
+ @Override
+ protected void activitySetUp() {
+ appendText(R.string.snsr_gyro_device_placement);
+ }
+
+ public String testDeviceStatic() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_device_static,
true /*portrait*/,
0, 0, 0);
+ }
- verifyMeasurements(
- "rotate the device clockwise",
+ public String testRotateClockwise() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_clockwise,
true /*portrait*/,
0, 0, -1);
+ }
- verifyMeasurements(
- "rotate the device counter-clockwise",
+ public String testRotateCounterClockwise() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_counter_clockwise,
true /*portrait*/,
0, 0, +1);
+ }
- verifyMeasurements(
- "rotate the device on its right until it stands on its side",
+ public String testRotateRightSide() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_right_side,
true /*portrait*/,
0, +1, 0);
+ }
- verifyMeasurements(
- "rotate the device on its left until it stands on its side",
+ public String testRotateLeftSide() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_left_side,
true /*portrait*/,
0, -1, 0);
+ }
- verifyMeasurements(
- "rotate the device on its top until it stands perpendicular",
+ public String testRotateTopSide() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_top_side,
false /*portrait*/,
-1, 0, 0);
+ }
- verifyMeasurements(
- "rotate the device on its bottom until it stands perpendicular",
+ public String testRotateBottomSide() throws Throwable {
+ return verifyMeasurements(
+ R.string.snsr_gyro_rotate_bottom_side,
false /*portrait*/,
+1, 0, 0);
}
@@ -87,13 +105,16 @@
* - the values representing the expectation of the test
* - the mean of values sampled from the sensor
*/
- private void verifyMeasurements(
- String scenarioInstructions,
+ private String verifyMeasurements(
+ int scenarioInstructionsResId,
boolean usePortraitOrientation,
int ... expectations) throws Throwable {
- final String orientation = usePortraitOrientation ? "Portrait": "Landscape";
- appendText(String.format("\n[Device orientation]: %s", orientation));
- appendText(String.format("Press 'Next' and %s.", scenarioInstructions));
+ if (usePortraitOrientation) {
+ appendText(R.string.snsr_orientation_portrait);
+ } else {
+ appendText(R.string.snsr_orientation_landscape);
+ }
+ appendText(scenarioInstructionsResId);
waitForUser();
Thread.sleep(500 /*ms*/);
@@ -107,6 +128,6 @@
expectations,
new float[]{0.2f, 0.2f, 0.2f} /*noiseThreshold*/));
verifySignum.execute();
- logSuccess();
+ return null;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index a131b2b..0177a85 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.sensors;
+import com.android.cts.verifier.R;
+
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
@@ -33,16 +35,24 @@
* Also, it is recommended to execute these tests outdoors, or at least far from magnetic
* disturbances.
*/
-public class MagneticFieldMeasurementTestActivity extends BaseSensorSemiAutomatedTestActivity {
+public class MagneticFieldMeasurementTestActivity extends BaseSensorTestActivity {
+ public MagneticFieldMeasurementTestActivity() {
+ super(MagneticFieldMeasurementTestActivity.class);
+ }
+
@Override
- protected void onRun() throws Throwable {
+ public void activitySetUp() {
calibrateMagnetometer();
+ }
- appendText("Verifying the Norm...");
- verifyNorm();
+ public String testNorm() throws Throwable {
+ appendText(R.string.snsr_mag_verify_norm);
+ return verifyNorm();
+ }
- appendText("\nVerifying the Standard Deviation...");
- verifyStandardDeviation();
+ public String testStandardDeviation() throws Throwable {
+ appendText(R.string.snsr_mag_verify_std_dev);
+ return verifyStandardDeviation();
}
private void calibrateMagnetometer() {
@@ -51,11 +61,12 @@
public void onSensorChanged(SensorEvent event) {
float values[] = event.values;
clearText();
- appendText("Please calibrate the Magnetometer by moving it in 8 shapes in "
- + "different orientations.");
+ appendText(R.string.snsr_mag_calibration_description);
appendText(String.format("-> (%.2f, %.2f, %.2f) uT", values[0], values[1],
values[2]), Color.GRAY);
- appendText("Then leave the device in a flat surface and press Next...\n");
+
+ // TODO: automate finding out when the magnetometer is calibrated
+ appendText(R.string.snsr_mag_calibration_complete);
}
@Override
@@ -96,7 +107,7 @@
* - the values representing the expectation of the test
* - the values sampled from the sensor
*/
- private void verifyNorm() throws Throwable {
+ private String verifyNorm() throws Throwable {
float expectedMagneticFieldEarth =
(SensorManager.MAGNETIC_FIELD_EARTH_MAX + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
float magneticFieldEarthThreshold =
@@ -111,7 +122,7 @@
expectedMagneticFieldEarth,
magneticFieldEarthThreshold));
verifyNorm.execute();
- logSuccess();
+ return null;
}
/**
@@ -137,7 +148,7 @@
* Additionally, the device's debug output (adb logcat) dumps the set of values associated with
* the failure to help track down the issue.
*/
- private void verifyStandardDeviation() throws Throwable {
+ private String verifyStandardDeviation() throws Throwable {
TestSensorOperation verifyStdDev = new TestSensorOperation(
this.getApplicationContext(),
Sensor.TYPE_MAGNETIC_FIELD,
@@ -147,6 +158,6 @@
verifyStdDev.addVerification(new StandardDeviationVerification(
new float[]{2f, 2f, 2f} /* uT */));
verifyStdDev.execute();
- logSuccess();
+ return null;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
new file mode 100644
index 0000000..97901ac
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.sensors;
+
+import com.android.cts.verifier.sensors.helpers.PowerTestHostLink;
+
+import junit.framework.Assert;
+
+public class SensorPowerTestActivity extends BaseSensorTestActivity implements
+ PowerTestHostLink.HostToDeviceInterface {
+
+ public class TestExecutionException extends Exception {
+ public TestExecutionException(final String message) {
+ super(message);
+ }
+ }
+
+ public SensorPowerTestActivity() {
+ super(SensorPowerTestActivity.class);
+ }
+
+ private String TAG = "SensorPowerTestActivity";
+ private PowerTestHostLink mHostLink;
+
+ /** HostToDeviceInterface implementation **/
+ public void waitForUserAcknowledgement(final String message) {
+ appendText(message);
+ waitForUser();
+ }
+
+ /* channel for host to raise an exception on the device if needed */
+ public void raiseError(final String testname,
+ final String message) throws Exception {
+ setTestResult(testname, SensorTestResult.SKIPPED, message);
+ throw new TestExecutionException(message);
+ }
+
+ public void logText(String text) {
+ appendText(text);
+ }
+
+ public void logTestResult(String testId, SensorTestResult testResult, String testDetails) {
+ setTestResult(testId, testResult, testDetails);
+ }
+
+ public String testSensorsPower() throws Throwable {
+ String testDetails = "";
+ if (mHostLink == null) {
+ // prepare Activity screen to show instructions to the operator
+ clearText();
+
+ // test setup, make sure the device is in the correct state before
+ // executing the scenarios
+ askToSetAirplaneMode();
+ askToSetScreenOffTimeout(15 /* seconds */);
+
+ // ask the operator to set up the host
+ appendText("Connect the device to the host machine.");
+ appendText("Execute the following script (the command is available in CtsVerifier.zip):");
+ appendText(" # python power/execute_power_tests.py --power_monitor <implementation> --run");
+ appendText("where \"<implementation>\" is the power monitor implementation being used, for example \"monsoon\"");
+ try {
+ mHostLink = new PowerTestHostLink(this, this);
+
+ appendText("Waiting for connection from Host...");
+
+ // this will block until first connection from host,
+ // and then allow the host to execute tests one by on
+ // until it issues an "EXIT" command to break out
+ // of the run loop. The host will run all associated tests
+ // sequentially here:
+ final PowerTestHostLink.PowerTestResult testResult = mHostLink.run();
+ testDetails = testResult.testDetails;
+ Assert.assertEquals(testDetails, 0, testResult.failedCount );
+ } finally {
+ mHostLink.close();
+ mHostLink = null;
+ }
+
+ } else {
+ throw new IllegalStateException("Attempt to run test twice");
+ }
+ return testDetails;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/PowerTestHostLink.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/PowerTestHostLink.java
new file mode 100644
index 0000000..f8b7dda
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/PowerTestHostLink.java
@@ -0,0 +1,483 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.sensors.helpers;
+
+import com.android.cts.verifier.sensors.BaseSensorTestActivity.SensorTestResult;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/*
+ * This class handles communication with the host to respond to commands
+ * The command/response link is through a TCP socket on the host side,
+ * forwarded via adb to a local socket on the device. The system uses a
+ * standard "accept-read_command-send_response-close" to execute commands
+ * sent from the host.
+ *
+ * CAUTION: The local socket name (SOCKET_NAME below) must match that used by the host
+ * to set up the adb-forwarding.
+ */
+public class PowerTestHostLink {
+
+ /*
+ * Host-to-device bridge will use a Listener instance to drive the test via
+ * the CtsVerifier running on the device.
+ */
+ public interface HostToDeviceInterface {
+
+ public void logTestResult(final String testName,
+ final SensorTestResult result,
+ final String message);
+
+ public void raiseError(final String testName, final String message) throws Exception;
+
+ public void waitForUserAcknowledgement(final String message);
+
+ public void logText(String text);
+
+ };
+
+ /** This is a data-only message to communicate result of a power test **/
+ public class PowerTestResult{
+ public int passedCount = 0;
+ public int skippedCount = 0;
+ public int failedCount = 0;
+ public String testDetails = "";
+ };
+
+
+ public final String TAG = "PowerTestHostLink";
+
+ /**
+ * Standard response types back to host. Host-side code must match these
+ * definitions
+ */
+ private final static String RESPONSE_OK = "OK";
+ private final static String RESPONSE_ERR = "ERR";
+ private final static String RESPONSE_UNAVAILABLE = "UNAVAILABLE";
+
+ /**
+ * Socket name for host adb forwarded communications. Must match naem in
+ * host-side code
+ */
+ public final static String SOCKET_NAME = "/android/cts/powertest";
+
+ private LocalServerSocket mServerSocket;
+ private volatile boolean mStopThread;
+ private final SensorManager mSensorManager;
+ private final PowerManager mPowerManager;
+ private final Context mContext;
+ private final HostToDeviceInterface mHostToDeviceExecutor;
+ private PowerTestResult mTestResult;
+ private StringBuilder mStringBuilder;
+
+ public PowerTestHostLink(Context context, final HostToDeviceInterface listener) {
+ Log.d(TAG, " +++ Begin of localSocketServer() +++ ");
+ mHostToDeviceExecutor = listener;
+ mContext = context;
+ try {
+ mServerSocket = new LocalServerSocket(SOCKET_NAME);
+ Log.i(TAG, "OKAY");
+
+ } catch (IOException e) {
+ Log.e(TAG, "The local Socket Server create failed");
+ e.printStackTrace();
+ }
+ if (mServerSocket != null) {
+ Log.d(TAG, "Bound to local server socket");
+ } else {
+ Log.e(TAG, "Unable to bind to local socket ");
+ }
+ mStopThread = false;
+
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ }
+
+ /**
+ * Ensure connection to host is closed; stop accepting requests
+ **/
+ public void close() {
+ mStopThread = true;
+ }
+
+
+
+ /**
+ * Run the suite of tests via the host, responding to host requests
+ *
+ * @return number of failed test cases
+ * @throws Exception
+ */
+ public PowerTestResult run() throws Exception {
+ mTestResult = new PowerTestResult();
+ mStringBuilder = new StringBuilder();
+ // define buffer to receive data from host
+ final int BUFFER_SIZE = 4096;
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ if (null == mServerSocket) {
+ Log.d(TAG, "The localSocketServer is NULL !!!");
+ mStopThread = true;
+ }
+ InputStream streamIn;
+ OutputStream streamOut;
+ LocalSocket receiverSocket;
+ while (!mStopThread) {
+
+ try {
+ Log.d(TAG, "localSocketServer accept...");
+ receiverSocket = mServerSocket.accept();
+ Log.d(TAG, "Got new connection");
+ } catch (IOException e) {
+ Log.d(TAG, "localSocketServer accept() failed !!!", e);
+ continue;
+ }
+
+ try {
+ streamIn = receiverSocket.getInputStream();
+ } catch (IOException e) {
+ Log.d(TAG, "getInputStream() failed !!!", e);
+ continue;
+ }
+
+ try {
+ streamOut = receiverSocket.getOutputStream();
+ } catch (IOException e) {
+ Log.e(TAG, "getOutputStream() failed", e);
+ continue;
+ }
+
+ Log.d(TAG, "The client connected to LocalServerSocket");
+
+ try {
+ int total = 0;
+ // command and response handshake, so read all data
+ while (streamIn.available() > 0 || total == 0) {
+ if (total < BUFFER_SIZE) {
+ int bytesRead = streamIn.read(buffer, total,
+ (BUFFER_SIZE - total));
+ if (bytesRead > 0) {
+ total += bytesRead;
+ }
+ } else {
+ Log.e(TAG, "Message too long: truncating");
+ }
+ }
+ String clientRequest = new String(buffer);
+ clientRequest = clientRequest.substring(0, total);
+ if (clientRequest.length() > 0) {
+
+ Log.d(TAG, "Client requested: " + clientRequest);
+ try {
+ String response = processClientRequest(clientRequest);
+ if (response != null) {
+ Log.d(TAG, "Sending response " + response);
+ streamOut.write(response.getBytes(), 0, response.length());
+ }
+ // null response means response is defered awaiting user
+ // response
+ } catch (Exception e) {
+ Log.e(TAG, "Error executing " + clientRequest, e);
+ streamOut.write(RESPONSE_ERR.getBytes(), 0, RESPONSE_ERR.length());
+ }
+ }
+ receiverSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "There is an exception when reading from or writing tosocket", e);
+ break;
+ }
+ }
+ Log.d(TAG, "The LocalSocketServer thread is going to stop !!!");
+
+ if (mServerSocket != null) {
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ Log.d(TAG, "Exception on close of server socket", e);
+ }
+ }
+ mHostToDeviceExecutor.logText("Device disconnected.");
+ if (mStringBuilder!=null){
+ mTestResult.testDetails = mStringBuilder.toString();
+ }
+ Log.d(TAG, "Returning " + mTestResult.passedCount + "passed " + mTestResult.skippedCount + "skipped " +
+ mTestResult.failedCount + "failed :" + mTestResult.testDetails);
+ return mTestResult;
+ }
+
+
+ protected String processClientRequest(String request) throws SettingNotFoundException,
+ Exception {
+ final String USER_REQUEST = "REQUEST USER RESPONSE";
+ String response = RESPONSE_ERR;
+ // Queries must appear first and then commands to direct actions after
+ // in this if/else construct
+ if (request.startsWith("SCREEN OFF TIMEOUT?")) {
+ int timeout = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_OFF_TIMEOUT);
+ response = "" + timeout;
+ } else if (request.startsWith("AIRPLANE MODE ON?")) {
+ boolean airplaneModeOn = Settings.Global.getInt
+ (mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ response = airplaneModeOn ? RESPONSE_OK : RESPONSE_ERR;
+ } else if (request.startsWith("SENSOR?")) {
+ final String sensor = request.substring(9);
+ final int sensorId = getSensorId(sensor);
+ if (mSensorManager.getDefaultSensor(sensorId) == null) {
+ response = RESPONSE_UNAVAILABLE;
+ } else {
+ response = RESPONSE_OK;
+ }
+ } else if (request.startsWith("EXTERNAL STORAGE?")){
+ response = System.getenv("EXTERNAL_STORAGE");
+ Log.d(TAG,"External storage is " + response);
+ }else if (request.startsWith("SCREEN OFF?")) {
+ boolean screenOn = mPowerManager.isScreenOn();
+ response = screenOn ? RESPONSE_ERR : RESPONSE_OK;
+ } else if (request.startsWith("SCREEN ON?")) {
+ boolean screenOn = mPowerManager.isScreenOn();
+ response = screenOn ? RESPONSE_OK : RESPONSE_ERR;
+ } else if (request.startsWith("SENSOR ON ")) {
+ String sensorList = request.substring(10).trim();
+ response = handleSensorSensorSwitchCmd(sensorList, true);
+ } else if (request.startsWith("SENSOR OFF")) {
+ String sensorList = request.substring(10).trim();
+ response = handleSensorSensorSwitchCmd(sensorList, false);
+ } else if (request.startsWith("MESSAGE")) {
+ final String message = request.substring(8);
+ mHostToDeviceExecutor.logText(message);
+ response = RESPONSE_OK;
+ } else if (request.startsWith(USER_REQUEST)) {
+ final String message = request.substring(USER_REQUEST.length() + 1);
+ mHostToDeviceExecutor.waitForUserAcknowledgement(message);
+ response = RESPONSE_OK;
+ } else if (request.startsWith("SET TEST RESULT")) {
+ response = handleSetTestResultCmd(request);
+ } else if (request.startsWith("RAISE")) {
+ StringTokenizer tokenizer = new StringTokenizer(request);
+ try {
+ tokenizer.nextToken();/* RAISE */
+ final String testName = tokenizer.nextToken();
+ final String message = request.substring(7 + testName.length());
+ mHostToDeviceExecutor.raiseError(testName, message);
+ response = RESPONSE_OK;
+ } catch (Exception e) {
+ Log.e(TAG, "Invalid RAISE command received (bad arguments): " + request);
+ response = RESPONSE_ERR;
+ }
+ } else if (request.startsWith("EXIT")) {
+ mStopThread = true;
+ response = RESPONSE_OK;
+ } else {
+ Log.e(TAG, "Unknown request: " + request);
+ }
+ return response;
+ }
+
+ protected String getCurrentTime() {
+ return DateFormat.getDateTimeInstance().format(new Date());
+ }
+
+ protected String handleSetTestResultCmd(final String request) {
+ String response = RESPONSE_OK;
+ StringTokenizer tokenizer = new StringTokenizer(request, " ");
+ String testName = "";
+ SensorTestResult result = SensorTestResult.FAIL;
+ String message = "";
+
+ try {
+ tokenizer.nextToken();/* SET */
+ tokenizer.nextToken();/* TEST */
+ tokenizer.nextToken();/* RESULT */
+ testName = tokenizer.nextToken();
+ final String resultToken = tokenizer.nextToken();
+
+ if (resultToken.equals("PASS")) {
+ result = SensorTestResult.PASS;
+ ++mTestResult.passedCount;
+ message = "PASSED: ";
+ response = RESPONSE_OK;
+ } else if (resultToken.equals("FAIL")) {
+ result = SensorTestResult.FAIL;
+ ++mTestResult.failedCount;
+ message = "FAILED: ";
+ response = RESPONSE_OK;
+ } else if (resultToken.equals("SKIPPED")) {
+ result = SensorTestResult.SKIPPED;
+ ++mTestResult.skippedCount;
+ message = "SKIPPED: ";
+ response = RESPONSE_OK;
+ } else {
+ response = RESPONSE_ERR;
+ }
+ while (tokenizer.hasMoreTokens()) {
+ message += tokenizer.nextToken() + " ";
+ }
+ Log.i(TAG, message);
+ } catch (Exception e) {
+ Log.e(TAG, "Improperly formatted command received: " + request, e);
+ response = RESPONSE_ERR;
+ }
+ String fullMessage = testName + " " + message;
+ mStringBuilder.append(fullMessage + "\n");
+ mHostToDeviceExecutor.logTestResult(testName, result, fullMessage );
+ return response;
+ }
+
+ protected String handleSensorSensorSwitchCmd(String sensorList, boolean switchOn) {
+ String response = RESPONSE_ERR;
+ try {
+ StringTokenizer tokenizer = new StringTokenizer(sensorList, " ");
+ int n = tokenizer.countTokens();
+ if (n == 0) {
+ response = switchAllSensors(switchOn);
+ } else {
+ String sensorName = tokenizer.nextToken();
+ String requestRate = "";
+ if (n > 1) {
+ requestRate = tokenizer.nextToken();
+ }
+ if (sensorName.equals("ALL")) {
+ response = switchAllSensors(switchOn);
+ } else {
+ int sensorId = getSensorId(sensorName);
+ if (sensorId >= 0) {
+ response = switchSensor(sensorId, switchOn, requestRate);
+ } else {
+ Log.e(TAG, "Unknown sensor in request: " + sensorName);
+ response = RESPONSE_UNAVAILABLE;
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Improperly formatted command received on setting sensor state");
+ response = RESPONSE_ERR;
+ }
+ return response;
+ }
+
+ protected int getSensorId(String sensorName) {
+ int sensorId = -1;
+
+ if (sensorName.compareToIgnoreCase("ACCELEROMETER") == 0) {
+ sensorId = Sensor.TYPE_ACCELEROMETER;
+ } else if (sensorName.compareToIgnoreCase("AMBIENT_TEMPERATURE") == 0) {
+ sensorId = Sensor.TYPE_AMBIENT_TEMPERATURE;
+ } else if (sensorName.compareToIgnoreCase("GAME_ROTATION_VECTOR") == 0) {
+ sensorId = Sensor.TYPE_GAME_ROTATION_VECTOR;
+ } else if (sensorName.compareToIgnoreCase("GEOMAGNETIC_ROTATION_VECTOR") == 0) {
+ sensorId = Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR;
+ } else if (sensorName.compareToIgnoreCase("GRAVITY") == 0) {
+ sensorId = Sensor.TYPE_GRAVITY;
+ } else if (sensorName.compareToIgnoreCase("GYROSCOPE") == 0) {
+ sensorId = Sensor.TYPE_GYROSCOPE;
+ } else if (sensorName.compareToIgnoreCase("LIGHT") == 0) {
+ sensorId = Sensor.TYPE_LIGHT;
+ } else if (sensorName.compareToIgnoreCase("MAGNETIC_FIELD") == 0) {
+ sensorId = Sensor.TYPE_MAGNETIC_FIELD;
+ } else if (sensorName.compareToIgnoreCase("PRESSURE") == 0) {
+ sensorId = Sensor.TYPE_PRESSURE;
+ } else if (sensorName.compareToIgnoreCase("PROXIMITY") == 0) {
+ sensorId = Sensor.TYPE_PROXIMITY;
+ } else if (sensorName.compareToIgnoreCase("RELATIVE_HUMIDITY") == 0) {
+ sensorId = Sensor.TYPE_RELATIVE_HUMIDITY;
+ } else if (sensorName.compareToIgnoreCase("ROTATION_VECTOR") == 0) {
+ sensorId = Sensor.TYPE_ROTATION_VECTOR;
+ } else if (sensorName.compareToIgnoreCase("SIGNIFICANT_MOTION") == 0) {
+ sensorId = Sensor.TYPE_SIGNIFICANT_MOTION;
+ } else if (sensorName.compareToIgnoreCase("STEP_COUNTER") == 0) {
+ sensorId = Sensor.TYPE_STEP_COUNTER;
+ } else if (sensorName.compareToIgnoreCase("STEP_DETECTOR") == 0) {
+ sensorId = Sensor.TYPE_STEP_DETECTOR;
+ }
+
+ return sensorId;
+ }
+
+ protected String switchSensor(int sensorId, boolean switchOn) {
+ return switchSensor(sensorId, switchOn, "SENSOR_DELAY_NORMAL");
+ }
+
+ protected String switchSensor(int sensorId, boolean switchOn, String requestFrequency) {
+ String response = RESPONSE_ERR;
+ int rateUs = SensorManager.SENSOR_DELAY_NORMAL;
+
+ if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_FASTEST") == 0) {
+ rateUs = SensorManager.SENSOR_DELAY_FASTEST;
+ } else if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_GAME") == 0) {
+ rateUs = SensorManager.SENSOR_DELAY_GAME;
+ } else if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_UI") == 0) {
+ rateUs = SensorManager.SENSOR_DELAY_UI;
+ }
+
+ if (switchOn) {
+ mSensorManager.registerListener(mSensorEventListener,
+ mSensorManager.getDefaultSensor(sensorId), rateUs);
+ response = RESPONSE_OK;
+ Log.v(TAG, "Switching ON " + String.valueOf(sensorId) + " | " + String.valueOf(rateUs));
+ } else {
+ mSensorManager.unregisterListener(mSensorEventListener,
+ mSensorManager.getDefaultSensor(sensorId));
+ response = RESPONSE_OK;
+ Log.v(TAG, "Switching OFF " + String.valueOf(sensorId));
+ }
+
+ return response;
+ }
+
+ protected String switchAllSensors(boolean on) {
+ List<Sensor> allSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+ String response = RESPONSE_OK;
+ for (Sensor sensor : allSensors) {
+ response = switchSensor(sensor.getType(), on);
+ if (response == null) {
+ response = RESPONSE_ERR;
+ }
+ }
+ return response;
+ }
+
+ private SensorEventListener mSensorEventListener = new SensorEventListener() {
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ }
+ };
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
index 7c9852e..7cf6f24 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -126,8 +126,7 @@
}
}
- private void validateRequestKeysPresence(
- String capabilityName,
+ private void validateRequestKeysPresence(String capabilityName,
List<CaptureRequest.Key<?>> requestKeys, boolean expectedPresence) {
boolean actualPresence = mStaticInfo.areRequestKeysAvailable(requestKeys);
if (expectedPresence != actualPresence) {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index e199eb9..1b82b1d 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -35,6 +35,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -1489,10 +1490,11 @@
* <p>If this returns {@code true}, then querying for this key from a characteristics
* object will always return a non-{@code null} value.</p>
*
- * @param keys list of camera characteristics keys
+ * @param keys collection of camera characteristics keys
* @return whether or not all characteristics keys are available
*/
- public final boolean areCharacteristicsKeysAvailable(List<CameraCharacteristics.Key<?>> keys) {
+ public final boolean areCharacteristicsKeysAvailable(
+ Collection<CameraCharacteristics.Key<?>> keys) {
return mCharacteristics.getKeys().containsAll(keys);
}
@@ -1506,10 +1508,10 @@
* <p>In some cases (e.g. lens shading map), the request must have additional settings
* configured in order for the key to correspond to a value.</p>
*
- * @param keys list of capture result keys
+ * @param keys collection of capture result keys
* @return whether or not all result keys are available
*/
- public final boolean areResultKeysAvailable(List<CaptureResult.Key<?>> keys) {
+ public final boolean areResultKeysAvailable(Collection<CaptureResult.Key<?>> keys) {
return mCharacteristics.getAvailableCaptureResultKeys().containsAll(keys);
}
@@ -1524,10 +1526,10 @@
* <p>In some cases (e.g. manual control of exposure), other keys must be also be set
* in order for a key to take effect (e.g. control.mode set to OFF).</p>
*
- * @param keys list of capture request keys
+ * @param keys collection of capture request keys
* @return whether or not all result keys are available
*/
- public final boolean areRequestKeysAvailable(List<CaptureRequest.Key<?>> keys) {
+ public final boolean areRequestKeysAvailable(Collection<CaptureRequest.Key<?>> keys) {
return mCharacteristics.getAvailableCaptureRequestKeys().containsAll(keys);
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
index d5d7972..1b923fc 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
@@ -98,7 +98,7 @@
TestSensorOperation continuousOperation = new TestSensorOperation(
context,
sensorType,
- SensorManager.SENSOR_DELAY_NORMAL,
+ SensorManager.SENSOR_DELAY_FASTEST,
0 /* reportLatencyInUs */,
100 /* event count */);
continuousOperation.addVerification(new EventOrderingVerification());
@@ -220,7 +220,7 @@
TestSensorOperation tester = new TestSensorOperation(
context,
mSensorTypeTester,
- SensorManager.SENSOR_DELAY_NORMAL,
+ SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
tester.addVerification(new EventOrderingVerification());
@@ -228,7 +228,7 @@
TestSensorOperation testee = new TestSensorOperation(
context,
mSensorTypeTestee,
- SensorManager.SENSOR_DELAY_UI,
+ SensorManager.SENSOR_DELAY_FASTEST,
0 /*reportLatencyInUs*/,
100 /* event count */);
testee.addVerification(new EventOrderingVerification());
@@ -254,16 +254,6 @@
case 0:
rate = SensorManager.SENSOR_DELAY_FASTEST;
break;
- case 1:
- rate = SensorManager.SENSOR_DELAY_GAME;
- break;
- case 2:
- rate = SensorManager.SENSOR_DELAY_NORMAL;
- break;
- case 3:
- rate = SensorManager.SENSOR_DELAY_UI;
- break;
- case 4:
default:
int maxSamplingRate = SensorCtsHelper.getSensor(getContext(), sensorType)
.getMinDelay();