diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index 0dbae5bd..34bb8059 100644 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -138,6 +138,11 @@ class ApkWorkload(Workload): :view: The class of the main view pane of the app. This needs to be defined in order to collect SurfaceFlinger-derived statistics (such as FPS) for the app, but may otherwise be left as ``None``. + :launch_main: If ``False``, the default activity will not be launched (during setup), + allowing workloads to start the app with an intent of their choice in + the run step. This is useful for apps without a launchable default/main + activity or those where it cannot be launched without intent data (which + is provided at the run phase). :install_timeout: Timeout for the installation of the APK. This may vary wildly based on the size and nature of a specific APK, and so should be defined on per-workload basis. @@ -160,6 +165,7 @@ class ApkWorkload(Workload): min_apk_version = None max_apk_version = None supported_platforms = ['android'] + launch_main = True parameters = [ Parameter('install_timeout', kind=int, default=300, @@ -214,7 +220,8 @@ class ApkWorkload(Workload): if self.check_apk: self.check_apk_version() - self.launch_package() + if self.launch_main: + self.launch_package() # launch default activity without intent data self.device.execute('am kill-all') # kill all *background* activities self.device.clear_logcat() diff --git a/wlauto/workloads/skype/__init__.py b/wlauto/workloads/skype/__init__.py new file mode 100755 index 00000000..f84c8ec8 --- /dev/null +++ b/wlauto/workloads/skype/__init__.py @@ -0,0 +1,99 @@ +# Copyright 2014-2016 ARM Limited +# +# 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 time +from wlauto import AndroidUxPerfWorkload, Parameter + + +class Skype(AndroidUxPerfWorkload): + + name = 'skype' + description = ''' + A workload to perform standard productivity tasks within Skype. The + workload logs in to the Skype application, selects a recipient from the + contacts list and then initiates either a voice or video call. + + Test description: + + 1. Open Skype application + 2. Log in to a pre-defined account + 3. Select a recipient from the Contacts list + 4. Initiate either a ``voice`` or ``video`` call for ``duration`` time (in seconds) + + **Skype Setup** + + - You should install Skype client from Google Play Store on the device + (this was tested with client version 7.01.0.669; other recent versions + should also work). + - You must have a Skype account set up. + - The contact to be called must be added (and has accepted) to the + account. It's possible to have multiple contacts in the list, however + the contact to be called *must* be visible on initial navigation to the + list. + - For video calls the contact must be able to received the call. This + means that there must be a Skype client running (somewhere) with the + contact logged in and that client must have been configured to + auto-accept calls from the account on the device (how to set this + varies between different versions of Skype and between platforms -- + please search online for specific instructions). + https://support.skype.com/en/faq/FA3751/can-i-automatically-answer-all-my-calls-with-video-in-skype-for-windows-desktop + ''' + package = 'com.skype.raider' + min_apk_version = '7.01.0.669' + view = [package + '/com.skype.android.app.calling.CallActivity', + package + '/com.skype.android.app.calling.PreCallActivity', + package + '/com.skype.android.app.chat.ChatActivity', + package + '/com.skype.android.app.main.HubActivity', + package + '/com.skype.android.app.main.SplashActivity', + package + '/com.skype.android.app.signin.SignInActivity', + package + '/com.skype.android.app.signin.UnifiedLandingPageActivity'] + + activity = '' # Skype has no default 'main' activity + launch_main = False # overrides extended class + + parameters = [ + Parameter('login_name', kind=str, mandatory=True, + description=''' + Account to use when logging into the device from which the call will be made + '''), + Parameter('login_pass', kind=str, mandatory=True, + description='Password associated with the account to log into the device'), + Parameter('contact_name', kind=str, mandatory=True, default='Echo / Sound Test Service', + description='This is the contact display name as it appears in the people list'), + Parameter('duration', kind=int, default=10, + description='This is the duration of the call in seconds'), + Parameter('action', kind=str, allowed_values=['voice', 'video'], default='voice', + description='Action to take - either voice call (default) or video'), + ] + + # This workload relies on the internet so check that there is a working + # internet connection + requires_network = True + + def __init__(self, device, **kwargs): + super(Skype, self).__init__(device, **kwargs) + self.run_timeout = self.duration + 240 + + def validate(self): + super(Skype, self).validate() + self.uiauto_params['my_id'] = self.login_name + self.uiauto_params['my_pwd'] = self.login_pass + self.uiauto_params['name'] = self.contact_name.replace(' ', '0space0') + self.uiauto_params['duration'] = self.duration + self.uiauto_params['action'] = self.action + + def setup(self, context): + super(Skype, self).setup(context) + self.device.execute('am start -W -a android.intent.action.VIEW -d skype:dummy?dummy') diff --git a/wlauto/workloads/skype/com.arm.wlauto.uiauto.skype.jar b/wlauto/workloads/skype/com.arm.wlauto.uiauto.skype.jar new file mode 100644 index 00000000..44f99436 Binary files /dev/null and b/wlauto/workloads/skype/com.arm.wlauto.uiauto.skype.jar differ diff --git a/wlauto/workloads/skype/uiauto/build.sh b/wlauto/workloads/skype/uiauto/build.sh new file mode 100755 index 00000000..d9673df8 --- /dev/null +++ b/wlauto/workloads/skype/uiauto/build.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# CD into build dir if possible - allows building from any directory +script_path='.' +if `readlink -f $0 &>/dev/null`; then + script_path=`readlink -f $0 2>/dev/null` +fi +script_dir=`dirname $script_path` +cd $script_dir + +# Ensure build.xml exists before starting +if [[ ! -f build.xml ]]; then + echo 'Ant build.xml file not found! Check that you are in the right directory.' + exit 9 +fi + +# Copy base classes from wlauto dist +class_dir=bin/classes/com/arm/wlauto/uiauto +base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"` +mkdir -p $class_dir +cp $base_classes $class_dir + +# Build and return appropriate exit code if failed +ant build +exit_code=$? +if [[ $exit_code -ne 0 ]]; then + echo "ERROR: 'ant build' exited with code $exit_code" + exit $exit_code +fi + +# If successful move JAR file to workload folder (overwrite previous) +package=com.arm.wlauto.uiauto.skype.jar +rm -f ../$package +if [[ -f bin/$package ]]; then + cp bin/$package .. +else + echo 'ERROR: UiAutomator JAR could not be found!' + exit 9 +fi diff --git a/wlauto/workloads/skypevideo/uiauto/build.xml b/wlauto/workloads/skype/uiauto/build.xml similarity index 98% rename from wlauto/workloads/skypevideo/uiauto/build.xml rename to wlauto/workloads/skype/uiauto/build.xml index c2fdeb90..dd54c056 100644 --- a/wlauto/workloads/skypevideo/uiauto/build.xml +++ b/wlauto/workloads/skype/uiauto/build.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<project name="com.arm.wlauto.uiauto.skypevideo" default="help"> +<project name="com.arm.wlauto.uiauto.skype" default="help"> <!-- The local.properties file is created and updated by the 'android' tool. It contains the path to the SDK. It should *NOT* be checked into diff --git a/wlauto/workloads/skypevideo/uiauto/project.properties b/wlauto/workloads/skype/uiauto/project.properties similarity index 100% rename from wlauto/workloads/skypevideo/uiauto/project.properties rename to wlauto/workloads/skype/uiauto/project.properties diff --git a/wlauto/workloads/skype/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/skype/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java new file mode 100644 index 00000000..78163e42 --- /dev/null +++ b/wlauto/workloads/skype/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -0,0 +1,183 @@ +/* Copyright 2014-2016 ARM Limited + * + * 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.arm.wlauto.uiauto.skype; + +import android.os.Bundle; + +// Import the uiautomator libraries +import com.android.uiautomator.core.UiObject; +import com.android.uiautomator.core.UiObjectNotFoundException; +import com.android.uiautomator.core.UiSelector; +import com.android.uiautomator.core.UiWatcher; +import com.arm.wlauto.uiauto.UxPerfUiAutomation; + +import java.util.concurrent.TimeUnit; + +public class UiAutomation extends UxPerfUiAutomation { + + public static final String TEXT_VIEW = "android.widget.TextView"; + + public Bundle parameters; + public String packageName; + public String packageID; + + // Creates a watcher for when a pop up dialog appears with a dismiss button. + private UiWatcher createInfoPopUpWatcher() throws Exception { + UiWatcher infoPopUpWatcher = new UiWatcher() { + @Override + public boolean checkForCondition() { + UiObject dismissButton = + new UiObject(new UiSelector().resourceId(packageID + "dismiss_button")); + + if (dismissButton.exists()) { + try { + dismissButton.click(); + } catch (UiObjectNotFoundException e) { + e.printStackTrace(); + } + + Long viewTimeout = TimeUnit.SECONDS.toMillis(10); + boolean dismissed = dismissButton.waitUntilGone(viewTimeout); + + return dismissed; + } + return false; + } + }; + + return infoPopUpWatcher; + } + + public void runUiAutomation() throws Exception { + // Override superclass value + this.uiAutoTimeout = TimeUnit.SECONDS.toMillis(10); + + // Get Params + parameters = getParams(); + packageName = parameters.getString("package"); + packageID = packageName + ":id/"; + String loginName = parameters.getString("my_id"); + String loginPass = parameters.getString("my_pwd"); + String contactName = parameters.getString("name").replace("0space0", " "); + int callDuration = Integer.parseInt(parameters.getString("duration")); + String callType = parameters.getString("action"); + String resultsFile = parameters.getString("results_file"); + + setScreenOrientation(ScreenOrientation.NATURAL); + + UiWatcher infoPopUpWatcher = createInfoPopUpWatcher(); + registerWatcher("infoPopUpWatcher", infoPopUpWatcher); + runWatchers(); + + // Run tests + handleLoginScreen(loginName, loginPass); + searchForContact(contactName); + + if ("video".equalsIgnoreCase(callType)) { + videoCallTest(callDuration); + } else if ("voice".equalsIgnoreCase(callType)) { + voiceCallTest(callDuration); + } + + removeWatcher("infoPopUpWatcher"); + unsetScreenOrientation(); + } + + public void handleLoginScreen(String username, String password) throws Exception { + String useridResoureId = packageID + "sign_in_userid"; + String nextButtonResourceId = packageID + "sign_in_next_btn"; + UiObject useridField = new UiObject(new UiSelector().resourceId(useridResoureId)); + UiObject nextButton = new UiObject(new UiSelector().resourceId(nextButtonResourceId)); + useridField.setText(username); + nextButton.clickAndWaitForNewWindow(); + + String passwordResoureId = packageID + "signin_password"; + String signinButtonResourceId = packageID + "sign_in_btn"; + UiObject passwordField = new UiObject(new UiSelector().resourceId(passwordResoureId)); + UiObject signinButton = new UiObject(new UiSelector().resourceId(signinButtonResourceId)); + passwordField.setText(password); + signinButton.clickAndWaitForNewWindow(); + } + + public void searchForContact(String name) throws Exception { + UiObject menuSearch = new UiObject(new UiSelector().resourceId(packageID + "menu_search")); + boolean sharingResource = false; + + // If searching for a contact from Skype directly we need + // to click the menu search button to display the contact search box. + if (menuSearch.waitForExists(uiAutoTimeout)) { + menuSearch.click(); + + // If sharing a resource from another app the contact search box is shown + // by default. + } else { + sharingResource = true; + } + + UiObject search = getUiObjectByText("Search", "android.widget.EditText"); + search.setText(name); + + UiObject peopleItem = getUiObjectByText(name, "android.widget.TextView"); + + peopleItem.waitForExists(uiAutoTimeout); + peopleItem.click(); + + UiObject avatarPresence = + new UiObject(new UiSelector().resourceId(packageID + "skype_avatar_presence")); + + // On some devices two clicks are needed to select a contact. + if (!avatarPresence.waitUntilGone(uiAutoTimeout)) { + peopleItem.click(); + } + + // Before sharing a resource from another app we first need to + // confirm our selection. + if (sharingResource) { + UiObject confirm = + new UiObject(new UiSelector().resourceId(packageID + "fab")); + confirm.click(); + } + } + + private void voiceCallTest(int duration) throws Exception { + String testTag = "call_voice"; + ActionLogger logger = new ActionLogger(testTag, parameters); + + logger.start(); + makeCall(duration, false, testTag); + logger.stop(); + } + + private void videoCallTest(int duration) throws Exception { + String testTag = "call_video"; + ActionLogger logger = new ActionLogger(testTag, parameters); + + logger.start(); + makeCall(duration, true, testTag); + logger.stop(); + } + + private void makeCall(int duration, boolean video, String testTag) throws Exception { + String description = video ? "Video call" : "Call options"; + + UiObject callButton = new UiObject(new UiSelector().descriptionContains(description)); + callButton.clickAndWaitForNewWindow(); + + UiObject muteButton = new UiObject(new UiSelector().descriptionContains("Mute")); + muteButton.click(); + sleep(duration); + } +} diff --git a/wlauto/workloads/skypevideo/__init__.py b/wlauto/workloads/skypevideo/__init__.py deleted file mode 100644 index 58959e1f..00000000 --- a/wlauto/workloads/skypevideo/__init__.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2014-2015 ARM Limited -# -# 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. -# - -# pylint: disable=E1101,W0201,E0203 - -import time - -from wlauto import UiAutomatorWorkload, Parameter -from wlauto.utils.types import boolean - - -class SkypeVideo(UiAutomatorWorkload): - - name = 'skypevideo' - description = """ - Initiates Skype video call to a specified contact for a pre-determined duration. - (Note: requires Skype to be set up appropriately). - - This workload is intended for monitoring the behaviour of a device while a Skype - video call is in progress (a common use case). It does not produce any score or - metric and the intention is that some addition instrumentation is enabled while - running this workload. - - This workload, obviously, requires a network connection (ideally, wifi). - - This workload accepts the following parameters: - - - **Skype Setup** - - - You should install Skype client from Google Play Store on the device - (this was tested with client version 4.5.0.39600; other recent versions - should also work). - - You must have an account set up and logged into Skype on the device. - - The contact to be called must be added (and has accepted) to the - account. It's possible to have multiple contacts in the list, however - the contact to be called *must* be visible on initial navigation to the - list. - - The contact must be able to received the call. This means that there - must be a Skype client running (somewhere) with the contact logged in - and that client must have been configured to auto-accept calls from the - account on the device (how to set this varies between different versions - of Skype and between platforms -- please search online for specific - instructions). - https://support.skype.com/en/faq/FA3751/can-i-automatically-answer-all-my-calls-with-video-in-skype-for-windows-desktop - - """ - - package = 'com.skype.raider' - - parameters = [ - Parameter('duration', kind=int, default=300, - description='Duration of the video call in seconds.'), - Parameter('contact', mandatory=True, - description=""" - The name of the Skype contact to call. The contact must be already - added (see below). *If use_gui is set*, then this must be the skype - ID of the contact, *otherwise*, this must be the name of the - contact as it appears in Skype client's contacts list. In the latter case - it *must not* contain underscore characters (``_``); it may, however, contain - spaces. There is no default, you **must specify the name of the contact**. - - .. note:: You may alternatively specify the contact name as - ``skype_contact`` setting in your ``config.py``. If this is - specified, the ``contact`` parameter is optional, though - it may still be specified (in which case it will override - ``skype_contact`` setting). - """), - Parameter('use_gui', kind=boolean, default=False, - description=""" - Specifies whether the call should be placed directly through a - Skype URI, or by navigating the GUI. The URI is the recommended way - to place Skype calls on a device, but that does not seem to work - correctly on some devices (the URI seems to just start Skype, but not - place the call), so an alternative exists that will start the Skype app - and will then navigate the UI to place the call (incidentally, this method - does not seem to work on all devices either, as sometimes Skype starts - backgrounded...). Please note that the meaning of ``contact`` prameter - is different depending on whether this is set. Defaults to ``False``. - - .. note:: You may alternatively specify this as ``skype_use_gui`` setting - in your ``config.py``. - """), - - ] - - def __init__(self, device, **kwargs): - super(SkypeVideo, self).__init__(device, **kwargs) - if self.use_gui: - self.uiauto_params['name'] = self.contact.replace(' ', '_') - self.uiauto_params['duration'] = self.duration - self.run_timeout = self.duration + 30 - - def setup(self, context): - if self.use_gui: - super(SkypeVideo, self).setup(context) - self.device.execute('am force-stop {}'.format(self.package)) - self.device.execute('am start -W -a android.intent.action.VIEW -d skype:') - else: - self.device.execute('am force-stop {}'.format(self.package)) - - def run(self, context): - if self.use_gui: - super(SkypeVideo, self).run(context) - else: - command = "am start -W -a android.intent.action.VIEW -d \"skype:{}?call&video=true\"" - self.logger.debug(self.device.execute(command.format(self.contact))) - self.logger.debug('Call started; waiting for {} seconds...'.format(self.duration)) - time.sleep(self.duration) - self.device.execute('am force-stop com.skype.raider') - - def update_result(self, context): - pass - - def teardown(self, context): - if self.use_gui: - super(SkypeVideo, self).teardown(context) - self.device.execute('am force-stop {}'.format(self.package)) diff --git a/wlauto/workloads/skypevideo/com.arm.wlauto.uiauto.skypevideo.jar b/wlauto/workloads/skypevideo/com.arm.wlauto.uiauto.skypevideo.jar deleted file mode 100644 index dff2302a..00000000 Binary files a/wlauto/workloads/skypevideo/com.arm.wlauto.uiauto.skypevideo.jar and /dev/null differ diff --git a/wlauto/workloads/skypevideo/uiauto/build.sh b/wlauto/workloads/skypevideo/uiauto/build.sh deleted file mode 100755 index db6f8ff4..00000000 --- a/wlauto/workloads/skypevideo/uiauto/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Copyright 2014-2015 ARM Limited -# -# 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. -# - - - -class_dir=bin/classes/com/arm/wlauto/uiauto -base_class=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', 'BaseUiAutomation.class')"` -mkdir -p $class_dir -cp $base_class $class_dir - -ant build - -if [[ -f bin/com.arm.wlauto.uiauto.skypevideo.jar ]]; then - cp bin/com.arm.wlauto.uiauto.skypevideo.jar .. -fi diff --git a/wlauto/workloads/skypevideo/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/skypevideo/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java deleted file mode 100644 index 0743372e..00000000 --- a/wlauto/workloads/skypevideo/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2014-2015 ARM Limited - * - * 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.arm.wlauto.uiauto.skypevideo; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.view.KeyEvent; - -// Import the uiautomator libraries -import com.android.uiautomator.core.UiObject; -import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; -import com.android.uiautomator.core.UiSelector; -import com.android.uiautomator.testrunner.UiAutomatorTestCase; - -import com.arm.wlauto.uiauto.BaseUiAutomation; - -public class UiAutomation extends BaseUiAutomation { - - public static String TAG = "skypevideo"; - public static String videoCallButtonResourceId = "com.skype.raider:id/chat_menu_item_call_video"; - public static String noContactMessage = "Could not find contact \"%s\" in the contacts list."; - - public void runUiAutomation() throws Exception { - Bundle parameters = getParams(); - String contactName = parameters.getString("name").replace('_', ' '); - int duration = Integer.parseInt(parameters.getString("duration")); - - selectContact(contactName); - initiateCall(duration); - } - - public void selectContact(String name) throws Exception { - UiSelector selector = new UiSelector(); - UiObject peopleTab = new UiObject(selector.text("People")); - peopleTab.click(); - sleep(1); // tab transition - - // Note: this assumes that the contact is in view and does not attempt to scroll to find it. - // The expectation is that this automation will be used with a dedicated account that was set - // up for the purpose and so would only have the intended target plus one or two other contacts - // at most in the list. If that is not the case, then this needs to be re-written to scroll to - // find the contact if necessary. - UiObject contactCard = new UiObject(selector.text(name)); - if (!contactCard.exists()) { - throw new UiObjectNotFoundException(String.format(noContactMessage, name)); - } - contactCard.clickAndWaitForNewWindow(); - } - - public void initiateCall(int duration) throws Exception { - UiSelector selector = new UiSelector(); - UiObject videoCallButton = new UiObject(selector.resourceId(videoCallButtonResourceId)); - videoCallButton.click(); - sleep(duration); - } -}