mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-11-04 09:02:12 +00:00 
			
		
		
		
	Add Multiapp workload
A new workload for testing context switching between applications. Uses googlephotos as the producer workload and gmail and skype as the consumer workloads.
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -173,6 +173,10 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
 | 
			
		||||
        getUiDevice().getInstance().click(x, y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void pressBack() {
 | 
			
		||||
        getUiDevice().getInstance().pressBack();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void uiDeviceSwipeUp (int steps) {
 | 
			
		||||
        getUiDevice().getInstance().swipe(
 | 
			
		||||
            getDisplayCentreWidth(),
 | 
			
		||||
@@ -272,4 +276,10 @@ public class BaseUiAutomation extends UiAutomatorTestCase {
 | 
			
		||||
    public void unsetScreenOrientation() throws Exception {
 | 
			
		||||
        getUiDevice().unfreezeRotation();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   public void uiDevicePerformLongClick(UiObject view, int steps) throws Exception {
 | 
			
		||||
        Rect rect = view.getBounds();
 | 
			
		||||
        getUiDevice().getInstance().swipe(rect.centerX(), rect.centerY(),
 | 
			
		||||
                                          rect.centerX(), rect.centerY(), steps);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -44,7 +44,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        clearFirstRunDialogues();
 | 
			
		||||
 | 
			
		||||
        clickNewMail();
 | 
			
		||||
        setToField();
 | 
			
		||||
        setToField(parameters);
 | 
			
		||||
        setSubjectField();
 | 
			
		||||
        setComposeField();
 | 
			
		||||
        attachFiles();
 | 
			
		||||
@@ -53,7 +53,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        writeResultsToFile(timingResults, parameters.getString("output_file"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void clearFirstRunDialogues () throws Exception {
 | 
			
		||||
    public void clearFirstRunDialogues() throws Exception {
 | 
			
		||||
        // The first run dialogues vary on different devices so check if they are there and dismiss
 | 
			
		||||
        UiObject gotItBox = new UiObject(new UiSelector().resourceId("com.google.android.gm:id/welcome_tour_got_it")
 | 
			
		||||
                                                     .className("android.widget.TextView"));
 | 
			
		||||
@@ -65,15 +65,27 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        if (takeMeToBox.exists()) {
 | 
			
		||||
            clickUiObject(takeMeToBox, timeout);
 | 
			
		||||
        }
 | 
			
		||||
        UiObject converationView = new UiObject(new UiSelector()
 | 
			
		||||
                                            .resourceId("com.google.android.gm:id/conversation_list_view")
 | 
			
		||||
                                            .className("android.widget.ListView"));
 | 
			
		||||
        if (!converationView.waitForExists(networkTimeout)) {
 | 
			
		||||
            throw new UiObjectNotFoundException("Could not find \"converationView\".");
 | 
			
		||||
 | 
			
		||||
        UiObject syncNowButton = new UiObject(new UiSelector().textContains("Sync now")
 | 
			
		||||
                                                              .className("android.widget.Button"));
 | 
			
		||||
 | 
			
		||||
        // On some devices we need to wait for a sync to occur after clearing the data
 | 
			
		||||
        // We also need to sleep here since waiting for a new window is not enough
 | 
			
		||||
        if (syncNowButton.exists()) {
 | 
			
		||||
            syncNowButton.clickAndWaitForNewWindow(timeout);
 | 
			
		||||
            sleep(10);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void clickNewMail() throws Exception {
 | 
			
		||||
        UiObject conversationView = new UiObject(new UiSelector()
 | 
			
		||||
                                            .resourceId("com.google.android.gm:id/conversation_list_view")
 | 
			
		||||
                                            .className("android.widget.ListView"));
 | 
			
		||||
 | 
			
		||||
        if (!conversationView.waitForExists(networkTimeout)) {
 | 
			
		||||
            throw new UiObjectNotFoundException("Could not find \"conversationView\".");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        UiObject newMailButton = getUiObjectByDescription("Compose", "android.widget.ImageButton");
 | 
			
		||||
        result.start();
 | 
			
		||||
@@ -82,13 +94,18 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        timingResults.put("Create_newMail", result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setToField() throws Exception {
 | 
			
		||||
    public boolean hasComposeView() throws Exception {
 | 
			
		||||
        UiObject composeView = new UiObject(new UiSelector().resourceId("com.google.android.gm:id/compose"));
 | 
			
		||||
        return composeView.waitForExists(networkTimeout);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setToField(final Bundle parameters) throws Exception {
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        UiObject toField = getUiObjectByText("To", "android.widget.TextView");
 | 
			
		||||
        String recipient = parameters.getString("recipient").replace('_', ' ');
 | 
			
		||||
        result.start();
 | 
			
		||||
        toField.setText(recipient);
 | 
			
		||||
        getUiDevice().pressEnter();
 | 
			
		||||
        getUiDevice().getInstance().pressEnter();
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("Create_To", result);
 | 
			
		||||
    }
 | 
			
		||||
@@ -100,7 +117,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        // Click on the subject field is required on some platforms to exit the To box cleanly
 | 
			
		||||
        subjectField.click();
 | 
			
		||||
        subjectField.setText("This is a test message");
 | 
			
		||||
        getUiDevice().pressEnter();
 | 
			
		||||
        getUiDevice().getInstance().pressEnter();
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("Create_Subject", result);
 | 
			
		||||
    }
 | 
			
		||||
@@ -110,7 +127,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        UiObject composeField = getUiObjectByText("Compose email", "android.widget.EditText");
 | 
			
		||||
        result.start();
 | 
			
		||||
        composeField.setText("This is a test composition");
 | 
			
		||||
        getUiDevice().pressEnter();
 | 
			
		||||
        getUiDevice().getInstance().pressEnter();
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("Create_Compose", result);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -44,6 +44,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        parameters = getParams();
 | 
			
		||||
 | 
			
		||||
        setScreenOrientation(ScreenOrientation.NATURAL);
 | 
			
		||||
        pauseForSplashScreen();
 | 
			
		||||
        confirmAccess();
 | 
			
		||||
        dismissWelcomeView();
 | 
			
		||||
        gesturesTest();
 | 
			
		||||
@@ -55,14 +56,16 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        writeResultsToFile(timingResults, parameters.getString("output_file"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void dismissWelcomeView() throws Exception {
 | 
			
		||||
    public void pauseForSplashScreen() {
 | 
			
		||||
        sleep(5); // Pause while splash screen loads
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void dismissWelcomeView() throws Exception {
 | 
			
		||||
 | 
			
		||||
        // Click through the first two pages and make sure that we don't sign
 | 
			
		||||
        // in to our google account. This ensures the same set of photographs
 | 
			
		||||
        // are placed in the camera directory for each run.
 | 
			
		||||
 | 
			
		||||
        sleep(5); // Pause while splash screen loads
 | 
			
		||||
 | 
			
		||||
        UiObject getStartedButton =
 | 
			
		||||
            new UiObject(new UiSelector().textContains("Get started")
 | 
			
		||||
                                          .className("android.widget.Button"));
 | 
			
		||||
@@ -279,7 +282,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
            startDumpsysSurfaceFlinger(parameters, viewName);
 | 
			
		||||
 | 
			
		||||
            Timer result = new Timer();
 | 
			
		||||
            result = slideBarTest(straightenSlider , pos, steps);
 | 
			
		||||
            result = slideBarTest(straightenSlider, pos, steps);
 | 
			
		||||
 | 
			
		||||
            stopDumpsysSurfaceFlinger(parameters, viewName, surfFlingerlogName);
 | 
			
		||||
            stopDumpsysGfxInfo(parameters, gfxInfologName);
 | 
			
		||||
@@ -385,8 +388,8 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper to click on an individual photographs based on index in wa-working gallery.
 | 
			
		||||
    private void selectPhoto(final int index) throws Exception {
 | 
			
		||||
    // Helper to click on an individual photograph based on index in wa-working gallery.
 | 
			
		||||
    public void selectPhoto(final int index) throws Exception {
 | 
			
		||||
        UiObject workdir = getUiObjectByText("wa-working", "android.widget.TextView");
 | 
			
		||||
        workdir.clickAndWaitForNewWindow();
 | 
			
		||||
 | 
			
		||||
@@ -398,7 +401,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper that accepts, saves and navigates back to application home screen after an edit operation
 | 
			
		||||
    private void saveAndReturn() throws Exception {
 | 
			
		||||
    public void saveAndReturn() throws Exception {
 | 
			
		||||
 | 
			
		||||
        UiObject accept = getUiObjectByDescription("Accept", "android.widget.ImageView");
 | 
			
		||||
        accept.click();
 | 
			
		||||
@@ -413,4 +416,17 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        navigateUpButton.waitForExists(viewTimeout);
 | 
			
		||||
        navigateUpButton.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper to tag an individual photograph based on index in wa-working gallery.
 | 
			
		||||
    public void tagPhoto(final int index) throws Exception {
 | 
			
		||||
        UiObject workdir = getUiObjectByText("wa-working", "android.widget.TextView");
 | 
			
		||||
        workdir.clickAndWaitForNewWindow();
 | 
			
		||||
 | 
			
		||||
        UiObject photo =
 | 
			
		||||
            new UiObject(new UiSelector().resourceId("com.google.android.apps.photos:id/recycler_view")
 | 
			
		||||
                                         .childSelector(new UiSelector()
 | 
			
		||||
                                         .index(index)));
 | 
			
		||||
        photo.waitForExists(viewTimeout);
 | 
			
		||||
        uiDevicePerformLongClick(photo, 100);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										188
									
								
								wlauto/workloads/multiapp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								wlauto/workloads/multiapp/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from wlauto import AndroidUiAutoBenchmark, Parameter, File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Multiapp(AndroidUiAutoBenchmark):
 | 
			
		||||
 | 
			
		||||
    name = 'multiapp'
 | 
			
		||||
 | 
			
		||||
    googlephotos_package = 'com.google.android.apps.photos'
 | 
			
		||||
    gmail_package = 'com.google.android.gm'
 | 
			
		||||
    skype_package = 'com.skype.raider'
 | 
			
		||||
 | 
			
		||||
    # Set default package and activity
 | 
			
		||||
    package = googlephotos_package
 | 
			
		||||
    activity = 'com.google.android.apps.photos.home.HomeActivity'
 | 
			
		||||
 | 
			
		||||
    view = [googlephotos_package + '/com.google.android.apps.photos.home.HomeActivity',
 | 
			
		||||
            googlephotos_package + '/com.google.android.apps.photos.localmedia.ui.LocalPhotosActivity',
 | 
			
		||||
            googlephotos_package + '/com.google.android.apps.photos.onboarding.AccountPickerActivity',
 | 
			
		||||
            googlephotos_package + '/com.google.android.apps.photos.onboarding.IntroActivity',
 | 
			
		||||
            googlephotos_package + '/com.google.android.apps.photos/.share.ShareActivity',
 | 
			
		||||
 | 
			
		||||
            gmail_package + '/com.google.android.gm.ComposeActivityGmail',
 | 
			
		||||
 | 
			
		||||
            skype_package + '/com.skype.android.app.main.SplashActivity',
 | 
			
		||||
            skype_package + '/com.skype.android.app.signin.SignInActivity',
 | 
			
		||||
            skype_package + '/com.skype.android.app.signin.UnifiedLandingPageActivity',
 | 
			
		||||
            skype_package + '/com.skype.raider/com.skype.android.app.contacts.PickerActivity',
 | 
			
		||||
            skype_package + '/com.skype.android.app.chat.ChatActivity']
 | 
			
		||||
 | 
			
		||||
    instrumentation_log = '{}_instrumentation.log'.format(name)
 | 
			
		||||
 | 
			
		||||
    description = """
 | 
			
		||||
    Workload to test how responsive a device is when context switching between
 | 
			
		||||
    appplication tasks. It combines workflows from googlephotos, gmail and
 | 
			
		||||
    skype.
 | 
			
		||||
 | 
			
		||||
    Credentials for the user account used to log into the Skype app have to be provided
 | 
			
		||||
    in the agenda, as well as the display name of the contact to call.
 | 
			
		||||
 | 
			
		||||
    For reliable testing, this workload requires a good and stable internet connection,
 | 
			
		||||
    preferably on Wi-Fi.
 | 
			
		||||
 | 
			
		||||
    NOTE: This workload requires two jpeg files to be placed in the
 | 
			
		||||
    dependencies directory to run.
 | 
			
		||||
 | 
			
		||||
    WARNING: This workload timings are dependent on the time it takes to sync the Gmail.
 | 
			
		||||
 | 
			
		||||
    Although this workload attempts to be network independent it requires a
 | 
			
		||||
    network connection (ideally, wifi) to run. This is because the welcome
 | 
			
		||||
    screen UI is dependent on an existing connection.
 | 
			
		||||
 | 
			
		||||
    Test description:
 | 
			
		||||
     1. Two images are copied to the device
 | 
			
		||||
     2. Googlephotos is started in offline access mode
 | 
			
		||||
     3. The first image is selected and shared with Gmail
 | 
			
		||||
     4. Enter recipient details in the To: field
 | 
			
		||||
     5. Enter text in the Subject edit box
 | 
			
		||||
     6. Enter text in the Compose edit box
 | 
			
		||||
     7. Attach the shared image from Googlephotos to the email
 | 
			
		||||
     8. Click the Send mail button
 | 
			
		||||
     9. Return to Googlephotos and login to Skype via share action
 | 
			
		||||
    10. Return to Googlephotos and share the second image with Skype
 | 
			
		||||
    11. Select a recipient from the Contacts list
 | 
			
		||||
    12. The second image is posted to recipient
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    parameters = [
 | 
			
		||||
        Parameter('recipient', default='armuxperf@gmail.com', mandatory=False,
 | 
			
		||||
                  description=""""
 | 
			
		||||
                  The email address of the recipient.  Setting a void address
 | 
			
		||||
                  will stop any mesage failures clogging up your device inbox
 | 
			
		||||
                  """),
 | 
			
		||||
        Parameter('login_name', kind=str, mandatory=True,
 | 
			
		||||
                  description='''
 | 
			
		||||
                  Skype account to use when logging into the device
 | 
			
		||||
                  '''),
 | 
			
		||||
        Parameter('login_pass', kind=str, mandatory=True,
 | 
			
		||||
                  description='Skype password associated with the account to log into the device'),
 | 
			
		||||
        Parameter('contact_name', kind=str, mandatory=True,
 | 
			
		||||
                  description='This is the contact display name as it appears in the people list in Skype'),
 | 
			
		||||
        Parameter('dumpsys_enabled', kind=bool, default=True,
 | 
			
		||||
                  description='''
 | 
			
		||||
                  If ``True``, dumpsys captures will be carried out during the test run.
 | 
			
		||||
                  The output is piped to log files which are then pulled from the phone.
 | 
			
		||||
                  '''),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    file_prefix = 'wa_test_'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, device, **kwargs):
 | 
			
		||||
        super(Multiapp, self).__init__(device, **kwargs)
 | 
			
		||||
        self.output_file = os.path.join(self.device.working_directory, self.instrumentation_log)
 | 
			
		||||
 | 
			
		||||
    def validate(self):
 | 
			
		||||
        super(Multiapp, self).validate()
 | 
			
		||||
 | 
			
		||||
        self.uiauto_params['recipient'] = self.recipient
 | 
			
		||||
        self.uiauto_params['dumpsys_enabled'] = self.dumpsys_enabled
 | 
			
		||||
        self.uiauto_params['output_dir'] = self.device.working_directory
 | 
			
		||||
        self.uiauto_params['my_id'] = self.login_name
 | 
			
		||||
        self.uiauto_params['my_pwd'] = self.login_pass
 | 
			
		||||
        self.uiauto_params['name'] = self.contact_name.replace(' ', '_')
 | 
			
		||||
        self.uiauto_params['output_file'] = self.output_file
 | 
			
		||||
 | 
			
		||||
    def initialize(self, context):
 | 
			
		||||
 | 
			
		||||
        super(Multiapp, self).initialize(context)
 | 
			
		||||
 | 
			
		||||
        for entry in os.listdir(self.dependencies_directory):
 | 
			
		||||
            wa_file = ''.join([self.file_prefix, entry])
 | 
			
		||||
            if entry.endswith(".jpg"):
 | 
			
		||||
                self.device.push_file(os.path.join(self.dependencies_directory, entry),
 | 
			
		||||
                                      os.path.join(self.device.working_directory, wa_file),
 | 
			
		||||
                                      timeout=300)
 | 
			
		||||
 | 
			
		||||
        # Force a re-index of the mediaserver cache to pick up new files
 | 
			
		||||
        self.device.execute('am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard')
 | 
			
		||||
 | 
			
		||||
    def setup(self, context):
 | 
			
		||||
        super(Multiapp, self).setup(context)
 | 
			
		||||
 | 
			
		||||
        self.launch_main = False
 | 
			
		||||
 | 
			
		||||
        # Use superclass for setup of gmail dependency
 | 
			
		||||
        self.version = 'Gmail'
 | 
			
		||||
        self.package = self.gmail_package
 | 
			
		||||
        self.logger.info('Installing dependency Gmail')
 | 
			
		||||
        super(Multiapp, self).init_resources(context)
 | 
			
		||||
        super(Multiapp, self).setup(context)
 | 
			
		||||
 | 
			
		||||
        # Use superclass for setup of skype dependency
 | 
			
		||||
        self.version = 'Skype'
 | 
			
		||||
        self.package = self.skype_package
 | 
			
		||||
        self.logger.info('Installing dependency Skype')
 | 
			
		||||
        super(Multiapp, self).init_resources(context)
 | 
			
		||||
        super(Multiapp, self).setup(context)
 | 
			
		||||
 | 
			
		||||
        # Restore default settings
 | 
			
		||||
        self.package = self.googlephotos_package
 | 
			
		||||
        self.launch_main = True
 | 
			
		||||
        super(Multiapp, self).init_resources(context)
 | 
			
		||||
 | 
			
		||||
    def update_result(self, context):
 | 
			
		||||
        super(Multiapp, self).update_result(context)
 | 
			
		||||
 | 
			
		||||
        self.device.pull_file(self.output_file, context.output_directory)
 | 
			
		||||
        result_file = os.path.join(context.output_directory, self.instrumentation_log)
 | 
			
		||||
 | 
			
		||||
        with open(result_file, 'r') as wfh:
 | 
			
		||||
            regex = re.compile(r'(?P<key>\w+)\s+(?P<value1>\d+)\s+(?P<value2>\d+)\s+(?P<value3>\d+)')
 | 
			
		||||
            for line in wfh:
 | 
			
		||||
                match = regex.search(line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    context.result.add_metric((match.group('key') + "_start"), match.group('value1'))
 | 
			
		||||
                    context.result.add_metric((match.group('key') + "_finish"), match.group('value2'))
 | 
			
		||||
                    context.result.add_metric((match.group('key') + "_duration"), match.group('value3'))
 | 
			
		||||
 | 
			
		||||
    def teardown(self, context):
 | 
			
		||||
        super(Multiapp, self).teardown(context)
 | 
			
		||||
 | 
			
		||||
        # Use superclass for teardown of gmail dependency
 | 
			
		||||
        self.package = self.gmail_package
 | 
			
		||||
        super(Multiapp, self).teardown(context)
 | 
			
		||||
 | 
			
		||||
        # Use superclass for teardown of skype dependency
 | 
			
		||||
        self.package = self.skype_package
 | 
			
		||||
        super(Multiapp, self).teardown(context)
 | 
			
		||||
 | 
			
		||||
        # Restore default package
 | 
			
		||||
        self.package = self.googlephotos_package
 | 
			
		||||
 | 
			
		||||
        for file in self.device.listdir(self.device.working_directory):
 | 
			
		||||
            if file.endswith(".log"):
 | 
			
		||||
                self.device.pull_file(os.path.join(self.device.working_directory, file), context.output_directory)
 | 
			
		||||
                self.device.delete_file(os.path.join(self.device.working_directory, file))
 | 
			
		||||
 | 
			
		||||
    def finalize(self, context):
 | 
			
		||||
        super(Multiapp, self).finalize(context)
 | 
			
		||||
 | 
			
		||||
        for entry in self.device.listdir(self.device.working_directory):
 | 
			
		||||
            if entry.startswith(self.file_prefix) and entry.endswith(".jpg"):
 | 
			
		||||
                self.device.delete_file(os.path.join(self.device.working_directory, entry))
 | 
			
		||||
 | 
			
		||||
        # Force a re-index of the mediaserver cache to removed cached files
 | 
			
		||||
        self.device.execute('am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard')
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								wlauto/workloads/multiapp/com.arm.wlauto.uiauto.multiapp.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/workloads/multiapp/com.arm.wlauto.uiauto.multiapp.jar
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										49
									
								
								wlauto/workloads/multiapp/uiauto/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										49
									
								
								wlauto/workloads/multiapp/uiauto/build.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#!/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
 | 
			
		||||
 | 
			
		||||
# Add multiapp workload dependencies
 | 
			
		||||
apps=("googlephotos" "gmail" "skype")
 | 
			
		||||
 | 
			
		||||
for app in "${apps[@]}"; do
 | 
			
		||||
  module_path="wlauto.workloads.${app}"
 | 
			
		||||
  app_path="'uiauto/bin/classes/com/arm/wlauto/uiauto/${app}'"
 | 
			
		||||
  app_class_dir=`python -c "import os, ${module_path}; print os.path.join(os.path.dirname(${module_path}.__file__), ${app_path})"`
 | 
			
		||||
  cp -r $app_class_dir $class_dir
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
# 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.multiapp.jar
 | 
			
		||||
rm -f ../$package
 | 
			
		||||
if [[ -f bin/$package ]]; then
 | 
			
		||||
    cp bin/$package ..
 | 
			
		||||
else
 | 
			
		||||
    echo 'ERROR: UiAutomator JAR could not be found!'
 | 
			
		||||
    exit 9
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										92
									
								
								wlauto/workloads/multiapp/uiauto/build.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								wlauto/workloads/multiapp/uiauto/build.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project name="com.arm.wlauto.uiauto.multiapp" 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
 | 
			
		||||
         Version Control Systems. -->
 | 
			
		||||
    <property file="local.properties" />
 | 
			
		||||
 | 
			
		||||
    <!-- The ant.properties file can be created by you. It is only edited by the
 | 
			
		||||
         'android' tool to add properties to it.
 | 
			
		||||
         This is the place to change some Ant specific build properties.
 | 
			
		||||
         Here are some properties you may want to change/update:
 | 
			
		||||
 | 
			
		||||
         source.dir
 | 
			
		||||
             The name of the source directory. Default is 'src'.
 | 
			
		||||
         out.dir
 | 
			
		||||
             The name of the output directory. Default is 'bin'.
 | 
			
		||||
 | 
			
		||||
         For other overridable properties, look at the beginning of the rules
 | 
			
		||||
         files in the SDK, at tools/ant/build.xml
 | 
			
		||||
 | 
			
		||||
         Properties related to the SDK location or the project target should
 | 
			
		||||
         be updated using the 'android' tool with the 'update' action.
 | 
			
		||||
 | 
			
		||||
         This file is an integral part of the build system for your
 | 
			
		||||
         application and should be checked into Version Control Systems.
 | 
			
		||||
 | 
			
		||||
         -->
 | 
			
		||||
    <property file="ant.properties" />
 | 
			
		||||
 | 
			
		||||
    <!-- if sdk.dir was not set from one of the property file, then
 | 
			
		||||
         get it from the ANDROID_HOME env var.
 | 
			
		||||
         This must be done before we load project.properties since
 | 
			
		||||
         the proguard config can use sdk.dir -->
 | 
			
		||||
    <property environment="env" />
 | 
			
		||||
    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
 | 
			
		||||
        <isset property="env.ANDROID_HOME" />
 | 
			
		||||
    </condition>
 | 
			
		||||
 | 
			
		||||
    <!-- The project.properties file is created and updated by the 'android'
 | 
			
		||||
         tool, as well as ADT.
 | 
			
		||||
 | 
			
		||||
         This contains project specific properties such as project target, and library
 | 
			
		||||
         dependencies. Lower level build properties are stored in ant.properties
 | 
			
		||||
         (or in .classpath for Eclipse projects).
 | 
			
		||||
 | 
			
		||||
         This file is an integral part of the build system for your
 | 
			
		||||
         application and should be checked into Version Control Systems. -->
 | 
			
		||||
    <loadproperties srcFile="project.properties" />
 | 
			
		||||
 | 
			
		||||
    <!-- quick check on sdk.dir -->
 | 
			
		||||
    <fail
 | 
			
		||||
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
 | 
			
		||||
            unless="sdk.dir"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        Import per project custom build rules if present at the root of the project.
 | 
			
		||||
        This is the place to put custom intermediary targets such as:
 | 
			
		||||
            -pre-build
 | 
			
		||||
            -pre-compile
 | 
			
		||||
            -post-compile (This is typically used for code obfuscation.
 | 
			
		||||
                           Compiled code location: ${out.classes.absolute.dir}
 | 
			
		||||
                           If this is not done in place, override ${out.dex.input.absolute.dir})
 | 
			
		||||
            -post-package
 | 
			
		||||
            -post-build
 | 
			
		||||
            -pre-clean
 | 
			
		||||
    -->
 | 
			
		||||
    <import file="custom_rules.xml" optional="true" />
 | 
			
		||||
 | 
			
		||||
    <!-- Import the actual build file.
 | 
			
		||||
 | 
			
		||||
         To customize existing targets, there are two options:
 | 
			
		||||
         - Customize only one target:
 | 
			
		||||
             - copy/paste the target into this file, *before* the
 | 
			
		||||
               <import> task.
 | 
			
		||||
             - customize it to your needs.
 | 
			
		||||
         - Customize the whole content of build.xml
 | 
			
		||||
             - copy/paste the content of the rules files (minus the top node)
 | 
			
		||||
               into this file, replacing the <import> task.
 | 
			
		||||
             - customize to your needs.
 | 
			
		||||
 | 
			
		||||
         ***********************
 | 
			
		||||
         ****** IMPORTANT ******
 | 
			
		||||
         ***********************
 | 
			
		||||
         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
 | 
			
		||||
         in order to avoid having your file be overridden by tools such as "android update project"
 | 
			
		||||
    -->
 | 
			
		||||
    <!-- version-tag: VERSION_TAG -->
 | 
			
		||||
    <import file="${sdk.dir}/tools/ant/uibuild.xml" />
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										14
									
								
								wlauto/workloads/multiapp/uiauto/project.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								wlauto/workloads/multiapp/uiauto/project.properties
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
# This file is automatically generated by Android Tools.
 | 
			
		||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
 | 
			
		||||
#
 | 
			
		||||
# This file must be checked in Version Control Systems.
 | 
			
		||||
#
 | 
			
		||||
# To customize properties used by the Ant build system edit
 | 
			
		||||
# "ant.properties", and override values to adapt the script to your
 | 
			
		||||
# project structure.
 | 
			
		||||
#
 | 
			
		||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
 | 
			
		||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 | 
			
		||||
 | 
			
		||||
# Project target.
 | 
			
		||||
target=android-18
 | 
			
		||||
@@ -0,0 +1,167 @@
 | 
			
		||||
package com.arm.wlauto.uiauto.multiapp;
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
 | 
			
		||||
// Import the uiautomator libraries
 | 
			
		||||
import com.android.uiautomator.core.UiObject;
 | 
			
		||||
import com.android.uiautomator.core.UiScrollable;
 | 
			
		||||
import com.android.uiautomator.core.UiSelector;
 | 
			
		||||
 | 
			
		||||
import com.arm.wlauto.uiauto.UxPerfUiAutomation;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
 | 
			
		||||
public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
 | 
			
		||||
    public static String TAG = "uxperf_multiapp";
 | 
			
		||||
 | 
			
		||||
    public Bundle parameters;
 | 
			
		||||
    private String outputDir;
 | 
			
		||||
    private LinkedHashMap<String, Timer> timingResults = new LinkedHashMap<String, Timer>();
 | 
			
		||||
 | 
			
		||||
    public void runUiAutomation() throws Exception {
 | 
			
		||||
        parameters = getParams();
 | 
			
		||||
 | 
			
		||||
        com.arm.wlauto.uiauto.googlephotos.UiAutomation googlephotos =
 | 
			
		||||
            new com.arm.wlauto.uiauto.googlephotos.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        confirmAccess();
 | 
			
		||||
        googlephotos.dismissWelcomeView();
 | 
			
		||||
 | 
			
		||||
        // select the first photo
 | 
			
		||||
        googlephotos.tagPhoto(0);
 | 
			
		||||
        sendToGmail();
 | 
			
		||||
 | 
			
		||||
        // select the second photo
 | 
			
		||||
        googlephotos.tagPhoto(1);
 | 
			
		||||
        logIntoSkype();
 | 
			
		||||
 | 
			
		||||
        // Skype won't allow us to login and share on first visit so invoke
 | 
			
		||||
        // once more from googlephotos
 | 
			
		||||
        pressBack();
 | 
			
		||||
        pressBack();
 | 
			
		||||
        googlephotos.tagPhoto(1);
 | 
			
		||||
 | 
			
		||||
        sendToSkype();
 | 
			
		||||
 | 
			
		||||
        writeResultsToFile(timingResults, parameters.getString("output_file"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void sendToGmail() throws Exception {
 | 
			
		||||
 | 
			
		||||
        final String dumpsysTag = "sendToGmail";
 | 
			
		||||
        final String PACKAGE = "com.google.android.gm";
 | 
			
		||||
        String outputDir = parameters.getString("output_dir", "/sdcard/wa-working");
 | 
			
		||||
 | 
			
		||||
        com.arm.wlauto.uiauto.gmail.UiAutomation gmail =
 | 
			
		||||
            new com.arm.wlauto.uiauto.gmail.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        result.start();
 | 
			
		||||
 | 
			
		||||
        initDumpsysGfxInfo(PACKAGE);
 | 
			
		||||
        shareUsingApp("Gmail");
 | 
			
		||||
        gmail.clearFirstRunDialogues();
 | 
			
		||||
 | 
			
		||||
        if (!gmail.hasComposeView()) {
 | 
			
		||||
            waitForSync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        gmail.setToField(parameters);
 | 
			
		||||
        gmail.setSubjectField();
 | 
			
		||||
        gmail.setComposeField();
 | 
			
		||||
        gmail.clickSendButton();
 | 
			
		||||
        exitDumpsysGfxInfo(PACKAGE, new File(outputDir, dumpsysTag + "_gfxInfo.log"));
 | 
			
		||||
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("send_to_gmail_contact", result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void waitForSync() throws Exception {
 | 
			
		||||
 | 
			
		||||
        // After the initial share request on some devices Gmail returns back
 | 
			
		||||
        // to the launching app, so we need to share the photo onces more and
 | 
			
		||||
        // wait for Gmail to sync.
 | 
			
		||||
        com.arm.wlauto.uiauto.googlephotos.UiAutomation googlephotos =
 | 
			
		||||
            new com.arm.wlauto.uiauto.googlephotos.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        com.arm.wlauto.uiauto.gmail.UiAutomation gmail =
 | 
			
		||||
            new com.arm.wlauto.uiauto.gmail.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        googlephotos.tagPhoto(1);
 | 
			
		||||
        shareUsingApp("Gmail");
 | 
			
		||||
        gmail.clearFirstRunDialogues();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void logIntoSkype()  throws Exception {
 | 
			
		||||
 | 
			
		||||
        final String dumpsysTag = "logInToSkype";
 | 
			
		||||
        final String PACKAGE = "com.skype.raider";
 | 
			
		||||
        String outputDir = parameters.getString("output_dir", "/sdcard/wa-working");
 | 
			
		||||
 | 
			
		||||
        com.arm.wlauto.uiauto.skype.UiAutomation skype =
 | 
			
		||||
            new com.arm.wlauto.uiauto.skype.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        String loginName = parameters.getString("my_id");
 | 
			
		||||
        String loginPass = parameters.getString("my_pwd");
 | 
			
		||||
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        result.start();
 | 
			
		||||
 | 
			
		||||
        initDumpsysGfxInfo(PACKAGE);
 | 
			
		||||
        shareUsingApp("Skype");
 | 
			
		||||
        skype.handleLoginScreen(loginName, loginPass);
 | 
			
		||||
        confirmAccess();
 | 
			
		||||
        exitDumpsysGfxInfo(PACKAGE, new File(outputDir, dumpsysTag + "_gfxInfo.log"));
 | 
			
		||||
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("log_into_skype", result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void sendToSkype() throws Exception {
 | 
			
		||||
 | 
			
		||||
        final String dumpsysTag = "sendToSkype";
 | 
			
		||||
        final String PACKAGE = "com.skype.raider";
 | 
			
		||||
        String outputDir = parameters.getString("output_dir", "/sdcard/wa-working");
 | 
			
		||||
 | 
			
		||||
        com.arm.wlauto.uiauto.skype.UiAutomation skype =
 | 
			
		||||
            new com.arm.wlauto.uiauto.skype.UiAutomation();
 | 
			
		||||
 | 
			
		||||
        String contactName = parameters.getString("name").replace("_", " ");
 | 
			
		||||
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        result.start();
 | 
			
		||||
 | 
			
		||||
        initDumpsysGfxInfo(PACKAGE);
 | 
			
		||||
        shareUsingApp("Skype");
 | 
			
		||||
        skype.searchForContact(contactName);
 | 
			
		||||
        exitDumpsysGfxInfo(PACKAGE, new File(outputDir, dumpsysTag + "_gfxInfo.log"));
 | 
			
		||||
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put("send_to_skype_contact", result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void shareUsingApp(String appName) throws Exception {
 | 
			
		||||
        Timer result = new Timer();
 | 
			
		||||
        result.start();
 | 
			
		||||
        UiObject shareButton = getUiObjectByDescription("Share", "android.widget.TextView");
 | 
			
		||||
        shareButton.click();
 | 
			
		||||
 | 
			
		||||
        UiScrollable applicationGrid =
 | 
			
		||||
            new UiScrollable(new UiSelector().resourceId("com.google.android.apps.photos:id/application_grid"));
 | 
			
		||||
 | 
			
		||||
        UiObject openApp = new UiObject(new UiSelector().className("android.widget.TextView").text(appName));
 | 
			
		||||
 | 
			
		||||
        while (!openApp.exists()) {
 | 
			
		||||
            applicationGrid.scrollForward();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        openApp.clickAndWaitForNewWindow();
 | 
			
		||||
        String testTag = String.format("share_using_%s", appName);
 | 
			
		||||
 | 
			
		||||
        result.end();
 | 
			
		||||
        timingResults.put(testTag, result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -62,8 +62,6 @@ class Skype(AndroidUiAutoBenchmark):
 | 
			
		||||
                  '''),
 | 
			
		||||
        Parameter('login_pass', kind=str, mandatory=True,
 | 
			
		||||
                  description='Password associated with the account to log into the device'),
 | 
			
		||||
        Parameter('contact_skypeid', kind=str, mandatory=True,
 | 
			
		||||
                  description='This is the Skype ID of the contact to call from the device'),
 | 
			
		||||
        Parameter('contact_name', kind=str, mandatory=True,
 | 
			
		||||
                  description='This is the contact display name as it appears in the people list'),
 | 
			
		||||
        Parameter('duration', kind=int, default=60,
 | 
			
		||||
@@ -89,7 +87,6 @@ class Skype(AndroidUiAutoBenchmark):
 | 
			
		||||
        self.uiauto_params['output_dir'] = self.device.working_directory
 | 
			
		||||
        self.uiauto_params['my_id'] = self.login_name
 | 
			
		||||
        self.uiauto_params['my_pwd'] = self.login_pass
 | 
			
		||||
        self.uiauto_params['skypeid'] = self.contact_skypeid
 | 
			
		||||
        self.uiauto_params['name'] = self.contact_name.replace(' ', '_')
 | 
			
		||||
        self.uiauto_params['duration'] = self.duration
 | 
			
		||||
        self.uiauto_params['action'] = self.action
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -54,7 +54,6 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        Bundle parameters = getParams();
 | 
			
		||||
        String loginName = parameters.getString("my_id");
 | 
			
		||||
        String loginPass = parameters.getString("my_pwd");
 | 
			
		||||
        String contactSkypeId = parameters.getString("skypeid");
 | 
			
		||||
        String contactName = parameters.getString("name").replace("_", " ");
 | 
			
		||||
        int callDuration = Integer.parseInt(parameters.getString("duration"));
 | 
			
		||||
        String callType = parameters.getString("action");
 | 
			
		||||
@@ -67,7 +66,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        // Run tests
 | 
			
		||||
        handleLoginScreen(loginName, loginPass);
 | 
			
		||||
        confirmAccess();
 | 
			
		||||
        selectContact(contactName, contactSkypeId);
 | 
			
		||||
        selectContact(contactName);
 | 
			
		||||
        if ("video".equalsIgnoreCase(callType)) {
 | 
			
		||||
            videoCallTest(callDuration);
 | 
			
		||||
        } else if ("voice".equalsIgnoreCase(callType)) {
 | 
			
		||||
@@ -80,7 +79,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        writeResultsToFile(results, resultsFile);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void handleLoginScreen(String username, String password) throws Exception {
 | 
			
		||||
    public void handleLoginScreen(String username, String password) throws Exception {
 | 
			
		||||
        String useridResoureId = PACKAGE_ID + "sign_in_userid";
 | 
			
		||||
        String nextButtonResourceId = PACKAGE_ID + "sign_in_next_btn";
 | 
			
		||||
        UiObject useridField = new UiObject(new UiSelector().resourceId(useridResoureId));
 | 
			
		||||
@@ -96,7 +95,7 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        signinButton.clickAndWaitForNewWindow();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void selectContact(String name, String id) throws Exception {
 | 
			
		||||
    public void selectContact(String name) throws Exception {
 | 
			
		||||
        Timer timer = new Timer();
 | 
			
		||||
        timer.start();
 | 
			
		||||
        UiObject peopleTab;
 | 
			
		||||
@@ -123,6 +122,22 @@ public class UiAutomation extends UxPerfUiAutomation {
 | 
			
		||||
        results.put("select_contact", timer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void searchForContact(String name) throws Exception {
 | 
			
		||||
 | 
			
		||||
        UiObject search = getUiObjectByText("Search", "android.widget.EditText");
 | 
			
		||||
        search.setText(name);
 | 
			
		||||
 | 
			
		||||
        UiObject peopleItem =
 | 
			
		||||
            new UiObject(new UiSelector().resourceId("com.skype.raider:id/list")
 | 
			
		||||
                                         .childSelector(new UiSelector()
 | 
			
		||||
                                         .index(0)
 | 
			
		||||
                                         .clickable(true)));
 | 
			
		||||
        peopleItem.click();
 | 
			
		||||
        UiObject confirm =
 | 
			
		||||
            getUiObjectByResourceId("com.skype.raider:id/fab", "android.widget.ImageView");
 | 
			
		||||
        confirm.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void voiceCallTest(int duration) throws Exception {
 | 
			
		||||
        String testTag = "voice_call";
 | 
			
		||||
        Timer timer = new Timer();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user