mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-02-22 12:58:36 +00:00
Merge pull request #239 from jimboatarm/googleplaybooks_fix
Updates and fixes for googleplaybooks workload
This commit is contained in:
commit
ac0256e377
@ -13,18 +13,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import os
|
from wlauto import AndroidUxPerfWorkload, Parameter
|
||||||
import re
|
|
||||||
|
|
||||||
from wlauto import AndroidUiAutoBenchmark, Parameter
|
|
||||||
from wlauto.exceptions import DeviceError
|
from wlauto.exceptions import DeviceError
|
||||||
|
|
||||||
|
|
||||||
class Googleplaybooks(AndroidUiAutoBenchmark):
|
class Googleplaybooks(AndroidUxPerfWorkload):
|
||||||
|
|
||||||
name = 'googleplaybooks'
|
name = 'googleplaybooks'
|
||||||
package = 'com.google.android.apps.books'
|
package = 'com.google.android.apps.books'
|
||||||
version = '3.8.15' # apk version
|
min_apk_verson = '3.9.37'
|
||||||
activity = 'com.google.android.apps.books.app.BooksActivity'
|
activity = 'com.google.android.apps.books.app.BooksActivity'
|
||||||
view = [package + '/com.google.android.apps.books.app.HomeActivity',
|
view = [package + '/com.google.android.apps.books.app.HomeActivity',
|
||||||
package + '/com.android.vending/com.google.android.finsky.activities.MainActivity',
|
package + '/com.android.vending/com.google.android.finsky.activities.MainActivity',
|
||||||
@ -71,24 +68,12 @@ class Googleplaybooks(AndroidUiAutoBenchmark):
|
|||||||
The word to search for within a selected book.
|
The word to search for within a selected book.
|
||||||
Note: Accepts single words only.
|
Note: Accepts single words only.
|
||||||
"""),
|
"""),
|
||||||
Parameter('markers_enabled', kind=bool, default=True,
|
|
||||||
description="""
|
|
||||||
If ``True``, UX_PERF action markers will be emitted to logcat during
|
|
||||||
the test run.
|
|
||||||
"""),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
requires_network = True
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(Googleplaybooks, self).validate()
|
super(Googleplaybooks, self).validate()
|
||||||
self.uiauto_params['package'] = self.package
|
|
||||||
self.uiauto_params['markers_enabled'] = self.markers_enabled
|
|
||||||
self.uiauto_params['book_title'] = self.search_book_title.replace(" ", "_")
|
self.uiauto_params['book_title'] = self.search_book_title.replace(" ", "_")
|
||||||
self.uiauto_params['chapter_page_number'] = self.select_chapter_page_number
|
self.uiauto_params['chapter_page_number'] = self.select_chapter_page_number
|
||||||
self.uiauto_params['search_word'] = self.search_word
|
self.uiauto_params['search_word'] = self.search_word
|
||||||
|
|
||||||
def initialize(self, context):
|
|
||||||
super(Googleplaybooks, self).initialize(context)
|
|
||||||
|
|
||||||
# This workload relies on the internet so check that there is a working internet connection
|
|
||||||
if not self.device.is_network_connected():
|
|
||||||
raise DeviceError('Network is not connected for device {}'.format(self.device.name))
|
|
||||||
|
Binary file not shown.
@ -29,14 +29,16 @@ import com.arm.wlauto.uiauto.UxPerfUiAutomation;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import android.util.Log;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
public class UiAutomation extends UxPerfUiAutomation {
|
public class UiAutomation extends UxPerfUiAutomation {
|
||||||
|
|
||||||
public static String TAG = "uxperf_googleplaybooks";
|
protected Bundle parameters;
|
||||||
|
protected String packageName;
|
||||||
|
protected String packageID;
|
||||||
|
|
||||||
public Bundle parameters;
|
|
||||||
private int viewTimeoutSecs = 10;
|
private int viewTimeoutSecs = 10;
|
||||||
private long viewTimeout = TimeUnit.SECONDS.toMillis(viewTimeoutSecs);
|
private long viewTimeout = TimeUnit.SECONDS.toMillis(viewTimeoutSecs);
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
this.uiAutoTimeout = TimeUnit.SECONDS.toMillis(8);
|
this.uiAutoTimeout = TimeUnit.SECONDS.toMillis(8);
|
||||||
|
|
||||||
parameters = getParams();
|
parameters = getParams();
|
||||||
|
packageName = parameters.getString("package");
|
||||||
|
packageID = packageName + ":id/";
|
||||||
|
|
||||||
String bookTitle = parameters.getString("book_title").replace("_", " ");
|
String bookTitle = parameters.getString("book_title").replace("_", " ");
|
||||||
String chapterPageNumber = parameters.getString("chapter_page_number");
|
String chapterPageNumber = parameters.getString("chapter_page_number");
|
||||||
@ -120,7 +124,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
// pick a random sample book.
|
// pick a random sample book.
|
||||||
private void clearFirstRunDialogues() throws Exception {
|
private void clearFirstRunDialogues() throws Exception {
|
||||||
UiObject startButton =
|
UiObject startButton =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/start_button"));
|
new UiObject(new UiSelector().resourceId(packageID + "start_button"));
|
||||||
|
|
||||||
// First try and skip the sample book selection
|
// First try and skip the sample book selection
|
||||||
if (startButton.exists()) {
|
if (startButton.exists()) {
|
||||||
@ -128,7 +132,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UiObject endButton =
|
UiObject endButton =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/end_button"));
|
new UiObject(new UiSelector().resourceId(packageID + "end_button"));
|
||||||
|
|
||||||
// Click next button if it exists
|
// Click next button if it exists
|
||||||
if (endButton.exists()) {
|
if (endButton.exists()) {
|
||||||
@ -147,7 +151,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
// Searches for a "free" or "purchased" book title in Google play
|
// Searches for a "free" or "purchased" book title in Google play
|
||||||
private void searchForBook(final String bookTitle) throws Exception {
|
private void searchForBook(final String bookTitle) throws Exception {
|
||||||
UiObject search =
|
UiObject search =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
|
new UiObject(new UiSelector().resourceId(packageID + "menu_search"));
|
||||||
search.click();
|
search.click();
|
||||||
|
|
||||||
UiObject searchText = new UiObject(new UiSelector().textContains("Search")
|
UiObject searchText = new UiObject(new UiSelector().textContains("Search")
|
||||||
@ -168,36 +172,31 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
// book that contains a "free" or "purchased" book identifier
|
// book that contains a "free" or "purchased" book identifier
|
||||||
UiSelector bookSelector = new UiSelector().description(desc).className("android.widget.TextView");
|
UiSelector bookSelector = new UiSelector().description(desc).className("android.widget.TextView");
|
||||||
|
|
||||||
UiObject freeLabel =
|
UiObject label =
|
||||||
new UiObject(new UiSelector().fromParent(bookSelector)
|
new UiObject(new UiSelector().fromParent(bookSelector)
|
||||||
.resourceId("com.android.vending:id/li_label")
|
.resourceId("com.android.vending:id/li_label")
|
||||||
.description("Free"));
|
.descriptionMatches("^(Purchased|Free)$"));
|
||||||
|
|
||||||
UiObject purchasedLabel =
|
|
||||||
new UiObject(new UiSelector().fromParent(bookSelector)
|
|
||||||
.resourceId("com.android.vending:id/li_label")
|
|
||||||
.description("Purchased"));
|
|
||||||
|
|
||||||
UiScrollable searchResultsList =
|
UiScrollable searchResultsList =
|
||||||
new UiScrollable(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
|
new UiScrollable(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
|
||||||
|
|
||||||
int maxSwipes = 10;
|
final int maxSearchTime = 30;
|
||||||
while (!freeLabel.exists() && !purchasedLabel.exists()) {
|
int searchTime = maxSearchTime;
|
||||||
if (maxSwipes <= 0) {
|
|
||||||
throw new UiObjectNotFoundException("Could not find free or purchased book \"" + bookTitle + "\"");
|
while (!label.exists()) {
|
||||||
|
if (searchTime <= 0) {
|
||||||
|
throw new UiObjectNotFoundException(
|
||||||
|
"Exceeded maximum search time (" + maxSearchTime + " seconds) to find book \"" + bookTitle + "\"");
|
||||||
} else {
|
} else {
|
||||||
searchResultsList.swipeUp(10);
|
uiDeviceSwipeDown(100);
|
||||||
maxSwipes--;
|
sleep(1);
|
||||||
|
searchTime--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click on either the first "free" or "purchased" book found that
|
// Click on either the first "free" or "purchased" book found that
|
||||||
// matches the book title
|
// matches the book title
|
||||||
try {
|
label.click();
|
||||||
freeLabel.click();
|
|
||||||
} catch (UiObjectNotFoundException e) {
|
|
||||||
purchasedLabel.click();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToLibrary() throws Exception {
|
private void addToLibrary() throws Exception {
|
||||||
@ -244,7 +243,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
private void openBook(final String bookTitle) throws Exception {
|
private void openBook(final String bookTitle) throws Exception {
|
||||||
|
|
||||||
UiScrollable cardsGrid =
|
UiScrollable cardsGrid =
|
||||||
new UiScrollable(new UiSelector().resourceId("com.google.android.apps.books:id/cards_grid"));
|
new UiScrollable(new UiSelector().resourceId(packageID + "cards_grid"));
|
||||||
|
|
||||||
UiSelector bookSelector = new UiSelector().text(bookTitle).className("android.widget.TextView");
|
UiSelector bookSelector = new UiSelector().text(bookTitle).className("android.widget.TextView");
|
||||||
UiObject book = new UiObject(bookSelector);
|
UiObject book = new UiObject(bookSelector);
|
||||||
@ -253,7 +252,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
// can assume any newly downloaded books will be visible on the first
|
// can assume any newly downloaded books will be visible on the first
|
||||||
// screen.
|
// screen.
|
||||||
UiObject menuSort =
|
UiObject menuSort =
|
||||||
getUiObjectByResourceId("com.google.android.apps.books:id/menu_sort", "android.widget.TextView");
|
getUiObjectByResourceId(packageID + "menu_sort", "android.widget.TextView");
|
||||||
menuSort.click();
|
menuSort.click();
|
||||||
|
|
||||||
UiObject sortByRecent = getUiObjectByText("Recent", "android.widget.TextView");
|
UiObject sortByRecent = getUiObjectByText("Recent", "android.widget.TextView");
|
||||||
@ -265,16 +264,12 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
UiObject downloadComplete =
|
UiObject downloadComplete =
|
||||||
new UiObject(new UiSelector().fromParent(bookSelector).description("100% downloaded"));
|
new UiObject(new UiSelector().fromParent(bookSelector).description("100% downloaded"));
|
||||||
|
|
||||||
int maxDownloadTime = 120; // seconds
|
long maxWaitTimeSeconds = 120;
|
||||||
|
long maxWaitTime = TimeUnit.SECONDS.toMillis(maxWaitTimeSeconds);
|
||||||
|
|
||||||
while (!downloadComplete.exists()) {
|
if (!downloadComplete.waitForExists(maxWaitTime)) {
|
||||||
if (maxDownloadTime <= 0) {
|
|
||||||
throw new UiObjectNotFoundException(
|
throw new UiObjectNotFoundException(
|
||||||
"Exceeded maximum wait time (" + maxDownloadTime + " seconds) to download book \"" + bookTitle + "\"");
|
"Exceeded maximum wait time (" + maxWaitTimeSeconds + " seconds) to download book \"" + bookTitle + "\"");
|
||||||
} else {
|
|
||||||
sleep(1);
|
|
||||||
maxDownloadTime--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
book.click();
|
book.click();
|
||||||
@ -353,11 +348,10 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
private void selectChapter(final String chapterPageNumber) throws Exception {
|
private void selectChapter(final String chapterPageNumber) throws Exception {
|
||||||
getDropdownMenu();
|
getDropdownMenu();
|
||||||
|
|
||||||
UiObject contents = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_toc",
|
UiObject contents = getUiObjectByResourceId(packageID + "menu_reader_toc");
|
||||||
"android.widget.TextView");
|
|
||||||
contents.clickAndWaitForNewWindow(uiAutoTimeout);
|
contents.clickAndWaitForNewWindow(uiAutoTimeout);
|
||||||
|
|
||||||
UiObject toChapterView = getUiObjectByResourceId("com.google.android.apps.books:id/toc_list_view",
|
UiObject toChapterView = getUiObjectByResourceId(packageID + "toc_list_view",
|
||||||
"android.widget.ExpandableListView");
|
"android.widget.ExpandableListView");
|
||||||
|
|
||||||
// Navigate to top of chapter view
|
// Navigate to top of chapter view
|
||||||
@ -375,18 +369,20 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
ActionLogger logger = new ActionLogger(testTag, parameters);
|
ActionLogger logger = new ActionLogger(testTag, parameters);
|
||||||
logger.start();
|
logger.start();
|
||||||
|
|
||||||
|
hideDropDownMenu();
|
||||||
|
|
||||||
UiObject clickable = new UiObject(new UiSelector().longClickable(true));
|
UiObject clickable = new UiObject(new UiSelector().longClickable(true));
|
||||||
uiObjectPerformLongClick(clickable, 100);
|
uiObjectPerformLongClick(clickable, 100);
|
||||||
|
|
||||||
UiObject addNoteButton = new UiObject(
|
UiObject addNoteButton = new UiObject(
|
||||||
new UiSelector().resourceId("com.google.android.apps.books:id/add_note_button"));
|
new UiSelector().resourceId(packageID + "add_note_button"));
|
||||||
addNoteButton.click();
|
addNoteButton.click();
|
||||||
|
|
||||||
UiObject noteEditText = getUiObjectByResourceId("com.google.android.apps.books:id/note_edit_text",
|
UiObject noteEditText = getUiObjectByResourceId(packageID + "note_edit_text",
|
||||||
"android.widget.EditText");
|
"android.widget.EditText");
|
||||||
noteEditText.setText(text);
|
noteEditText.setText(text);
|
||||||
|
|
||||||
UiObject noteMenuButton = getUiObjectByResourceId("com.google.android.apps.books:id/note_menu_button",
|
UiObject noteMenuButton = getUiObjectByResourceId(packageID + "note_menu_button",
|
||||||
"android.widget.ImageButton");
|
"android.widget.ImageButton");
|
||||||
noteMenuButton.click();
|
noteMenuButton.click();
|
||||||
|
|
||||||
@ -407,7 +403,7 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
uiObjectPerformLongClick(clickable, 100);
|
uiObjectPerformLongClick(clickable, 100);
|
||||||
|
|
||||||
UiObject removeButton = new UiObject(
|
UiObject removeButton = new UiObject(
|
||||||
new UiSelector().resourceId("com.google.android.apps.books:id/remove_highlight_button"));
|
new UiSelector().resourceId(packageID + "remove_highlight_button"));
|
||||||
removeButton.click();
|
removeButton.click();
|
||||||
|
|
||||||
UiObject confirmRemove = getUiObjectByText("Remove", "android.widget.Button");
|
UiObject confirmRemove = getUiObjectByText("Remove", "android.widget.Button");
|
||||||
@ -425,16 +421,16 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
getDropdownMenu();
|
getDropdownMenu();
|
||||||
logger.start();
|
logger.start();
|
||||||
UiObject search = new UiObject(
|
UiObject search = new UiObject(
|
||||||
new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
|
new UiSelector().resourceId(packageID + "menu_search"));
|
||||||
search.click();
|
search.click();
|
||||||
|
|
||||||
UiObject searchText = new UiObject(
|
UiObject searchText = new UiObject(
|
||||||
new UiSelector().resourceId("com.google.android.apps.books:id/search_src_text"));
|
new UiSelector().resourceId(packageID + "search_src_text"));
|
||||||
searchText.setText(text);
|
searchText.setText(text);
|
||||||
pressEnter();
|
pressEnter();
|
||||||
|
|
||||||
UiObject resultList = new UiObject(
|
UiObject resultList = new UiObject(
|
||||||
new UiSelector().resourceId("com.google.android.apps.books:id/search_results_list"));
|
new UiSelector().resourceId(packageID + "search_results_list"));
|
||||||
|
|
||||||
// Allow extra time for search queries involing high freqency words
|
// Allow extra time for search queries involing high freqency words
|
||||||
final long searchTimeout = TimeUnit.SECONDS.toMillis(20);
|
final long searchTimeout = TimeUnit.SECONDS.toMillis(20);
|
||||||
@ -459,13 +455,13 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
String testTag = "switch_page_style";
|
String testTag = "switch_page_style";
|
||||||
|
|
||||||
getDropdownMenu();
|
getDropdownMenu();
|
||||||
UiObject readerSettings = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_settings",
|
UiObject readerSettings = getUiObjectByResourceId(packageID + "menu_reader_settings",
|
||||||
"android.widget.TextView");
|
"android.widget.TextView");
|
||||||
readerSettings.click();
|
readerSettings.click();
|
||||||
|
|
||||||
// Check for lighting option button on newer versions
|
// Check for lighting option button on newer versions
|
||||||
UiObject lightingOptionsButton =
|
UiObject lightingOptionsButton =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/lighting_options_button"));
|
new UiObject(new UiSelector().resourceId(packageID + "lighting_options_button"));
|
||||||
|
|
||||||
if (lightingOptionsButton.exists()) {
|
if (lightingOptionsButton.exists()) {
|
||||||
lightingOptionsButton.click();
|
lightingOptionsButton.click();
|
||||||
@ -474,15 +470,22 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
String[] styles = {"Night", "Sepia", "Day"};
|
String[] styles = {"Night", "Sepia", "Day"};
|
||||||
|
|
||||||
for (String style : styles) {
|
for (String style : styles) {
|
||||||
|
try {
|
||||||
ActionLogger logger = new ActionLogger(testTag + "_" + style, parameters);
|
ActionLogger logger = new ActionLogger(testTag + "_" + style, parameters);
|
||||||
logger.start();
|
logger.start();
|
||||||
UiObject pageStyle = new UiObject(new UiSelector().description(style));
|
UiObject pageStyle = new UiObject(new UiSelector().description(style));
|
||||||
pageStyle.clickAndWaitForNewWindow(viewTimeout);
|
pageStyle.clickAndWaitForNewWindow(viewTimeout);
|
||||||
logger.stop();
|
logger.stop();
|
||||||
|
} catch (UiObjectNotFoundException e) {
|
||||||
|
// On some devices the lighting options menu disappears
|
||||||
|
// between clicks. Searching for the menu again would affect
|
||||||
|
// the logger timings so log a message and continue
|
||||||
|
Log.e("GooglePlayBooks", "Could not find pageStyle \"" + style + "\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(2);
|
sleep(2);
|
||||||
tapDisplayCentre();
|
tapDisplayCentre(); // exit reader settings dialog
|
||||||
waitForPage();
|
waitForPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,28 +512,37 @@ public class UiAutomation extends UxPerfUiAutomation {
|
|||||||
|
|
||||||
// Helper for accessing the drop down menu
|
// Helper for accessing the drop down menu
|
||||||
private void getDropdownMenu() throws Exception {
|
private void getDropdownMenu() throws Exception {
|
||||||
sleep(1); // Allow previous views to settle
|
|
||||||
int height = getDisplayHeight();
|
|
||||||
int width = getDisplayCentreWidth();
|
|
||||||
getUiDevice().swipe(width, 20, width, height / 10, 50);
|
|
||||||
|
|
||||||
// selecting the drop down menu can be unreliable so check for its
|
|
||||||
// existence and if not present try for a second time using a different
|
|
||||||
// start point and step size
|
|
||||||
UiObject actionBar =
|
UiObject actionBar =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/action_bar"));
|
new UiObject(new UiSelector().resourceId(packageID + "action_bar"));
|
||||||
|
|
||||||
long actionBarTimeout = TimeUnit.SECONDS.toMillis(3);
|
if (!actionBar.exists()) {
|
||||||
|
tapDisplayCentre();
|
||||||
|
sleep(1); // Allow previous views to settle
|
||||||
|
}
|
||||||
|
|
||||||
if (!actionBar.waitForExists(actionBarTimeout)) {
|
if (!actionBar.exists()) {
|
||||||
getUiDevice().swipe(width, 5, width, height / 10, 20);
|
throw new UiObjectNotFoundException("Could not find \"action bar\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideDropDownMenu() throws Exception {
|
||||||
|
UiObject actionBar =
|
||||||
|
new UiObject(new UiSelector().resourceId(packageID + "action_bar"));
|
||||||
|
|
||||||
|
if (actionBar.exists()) {
|
||||||
|
tapDisplayCentre();
|
||||||
|
sleep(1); // Allow previous views to settle
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionBar.exists()) {
|
||||||
|
throw new UiObjectNotFoundException("Could not close \"action bar\".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for waiting on a page between actions
|
// Helper for waiting on a page between actions
|
||||||
private UiObject waitForPage() throws Exception {
|
private UiObject waitForPage() throws Exception {
|
||||||
UiObject activityReader =
|
UiObject activityReader =
|
||||||
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/activity_reader")
|
new UiObject(new UiSelector().resourceId(packageID + "activity_reader")
|
||||||
.childSelector(new UiSelector().focusable(true)));
|
.childSelector(new UiSelector().focusable(true)));
|
||||||
|
|
||||||
// On some devices the object in the view hierarchy is found before it
|
// On some devices the object in the view hierarchy is found before it
|
||||||
|
Loading…
x
Reference in New Issue
Block a user