├── .gitignore ├── Dataset └── README.md ├── README.md ├── RegDroid ├── App │ ├── AmazeFileManager-3.7.1.apk │ └── AmazeFileManager-3.7.2.apk ├── Document │ ├── Android_logo.jpg │ ├── Android_robot.png │ ├── Heartbeat.mp3 │ ├── intermission.mp3 │ ├── sample.3gp │ ├── sample_3GPP.3gp.zip │ ├── sample_iPod.m4v │ ├── sample_mpeg4.mp4 │ └── sample_sorenson.mov ├── app.py ├── checker.py ├── device.py ├── event.py ├── executor.py ├── injector.py ├── policy.py ├── regdroid.py ├── start.py ├── state.py ├── style.html ├── utils.py └── view.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | Output/* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ -------------------------------------------------------------------------------- /Dataset/README.md: -------------------------------------------------------------------------------- 1 | 399 functional bugs in our study 2 | | | | | | 3 | |-|-|-|-| 4 | |ID |App|Issue ID|App version| 5 | |1|Simplenote|[1429](https://github.com/Automattic/simplenote-android/issues/1429)|2.18| 6 | |2|Simplenote|[1413](https://github.com/Automattic/simplenote-android/issues/1413)|2.18| 7 | |3|Simplenote|[1296](https://github.com/Automattic/simplenote-android/issues/1296)|2.16-rc-2 | 8 | |4|Simplenote|[1307](https://github.com/Automattic/simplenote-android/issues/1307)|2.15| 9 | |5|Simplenote|[1289](https://github.com/Automattic/simplenote-android/issues/1289)|2.14| 10 | |6|Simplenote|[1294](https://github.com/Automattic/simplenote-android/issues/1294)|2.15| 11 | |7|Simplenote|[1293](https://github.com/Automattic/simplenote-android/issues/1293)|2.16-rc-2 | 12 | |8|Simplenote|[907](https://github.com/Automattic/simplenote-android/issues/907)|2.3-rc-2 | 13 | |9|Simplenote|[725](https://github.com/Automattic/simplenote-android/issues/725)|1.8.0 | 14 | |10|Simplenote|[1259](https://github.com/Automattic/simplenote-android/issues/1259)|2.13| 15 | |11|Simplenote|[917](https://github.com/Automattic/simplenote-android/issues/917)|2.9.0 | 16 | |12|Simplenote|[919](https://github.com/Automattic/simplenote-android/issues/919)|2.3| 17 | |13|Simplenote|[1190](https://github.com/Automattic/simplenote-android/issues/1190)|2.11| 18 | |14|Simplenote|[1221](https://github.com/Automattic/simplenote-android/issues/1221)|2.14-rc-1 | 19 | |15|Simplenote|[1111](https://github.com/Automattic/simplenote-android/issues/1111)|2.9-rc-1 | 20 | |16|Simplenote|[992](https://github.com/Automattic/simplenote-android/issues/992)| | 21 | |17|Simplenote|[1113](https://github.com/Automattic/simplenote-android/issues/1113)|2.9.0 | 22 | |18|Simplenote|[1070](https://github.com/Automattic/simplenote-android/issues/1070)|2.6.0 | 23 | |19|Simplenote|[1046](https://github.com/Automattic/simplenote-android/issues/1046)|2.6-rc-1 | 24 | |20|Simplenote|[1007](https://github.com/Automattic/simplenote-android/issues/1007)| | 25 | |21|Simplenote|[991](https://github.com/Automattic/simplenote-android/issues/991)|2.5-rc-1 | 26 | |22|Simplenote|[984](https://github.com/Automattic/simplenote-android/issues/984)|2.5-rc-1 | 27 | |23|Simplenote|[980](https://github.com/Automattic/simplenote-android/issues/980)|2.5-rc-1 | 28 | |24|Simplenote|[952](https://github.com/Automattic/simplenote-android/issues/952)|2.4-rc-2 | 29 | |25|Simplenote|[925](https://github.com/Automattic/simplenote-android/issues/925)| | 30 | |26|Simplenote|[911](https://github.com/Automattic/simplenote-android/issues/911)| | 31 | |27|Simplenote|[710](https://github.com/Automattic/simplenote-android/issues/710)| | 32 | |28|Simplenote|[702](https://github.com/Automattic/simplenote-android/issues/702)|1.7| 33 | |29|Simplenote|[709](https://github.com/Automattic/simplenote-android/issues/709)|1.7| 34 | |30|Simplenote|[739](https://github.com/Automattic/simplenote-android/issues/739)|1.8.1 | 35 | |31|Simplenote|[728](https://github.com/Automattic/simplenote-android/issues/728)| | 36 | |32|Simplenote|[598](https://github.com/Automattic/simplenote-android/issues/598)|1.5.7 | 37 | |33|Simplenote|[642](https://github.com/Automattic/simplenote-android/issues/642)|1.6.9 | 38 | |34|Simplenote|[623](https://github.com/Automattic/simplenote-android/issues/623)|1.6.8 | 39 | |35|Simplenote|[615](https://github.com/Automattic/simplenote-android/issues/615)|1.6.7 | 40 | |36|AnkiDroid|[8583](https://github.com/ankidroid/Anki-Android/issues/8583)|2.15alpha40| 41 | |37|AnkiDroid|[8229](https://github.com/ankidroid/Anki-Android/issues/8229)|2.14.6| 42 | |38|AnkiDroid|[7465](https://github.com/ankidroid/Anki-Android/issues/7465)|2.10alpha21| 43 | |39|AnkiDroid|[8235](https://github.com/ankidroid/Anki-Android/issues/8235)|2.15.0| 44 | |40|AnkiDroid|[7369](https://github.com/ankidroid/Anki-Android/issues/7369)| | 45 | |41|AnkiDroid|[7232](https://github.com/ankidroid/Anki-Android/issues/7232)|2.14alpha9| 46 | |42|AnkiDroid|[7690](https://github.com/ankidroid/Anki-Android/issues/7690)|2.13.5| 47 | |43|AnkiDroid|[7836](https://github.com/ankidroid/Anki-Android/issues/7836)|2.15alpha10| 48 | |44|AnkiDroid|[7801](https://github.com/ankidroid/Anki-Android/issues/7801)|2.13.0| 49 | |45|AnkiDroid|[7793](https://github.com/ankidroid/Anki-Android/issues/7793)|2.14.0| 50 | |46|AnkiDroid|[7344](https://github.com/ankidroid/Anki-Android/issues/7344)|2.13.4| 51 | |47|AnkiDroid|[7770](https://github.com/ankidroid/Anki-Android/issues/7770)|2.15alpha7| 52 | |48|AnkiDroid|[7768](https://github.com/ankidroid/Anki-Android/issues/7768)|2.14.1| 53 | |49|AnkiDroid|[7758](https://github.com/ankidroid/Anki-Android/issues/7758)|2.14.0| 54 | |50|AnkiDroid|[7730](https://github.com/ankidroid/Anki-Android/issues/7730)|2.14.0| 55 | |51|AnkiDroid|[7680](https://github.com/ankidroid/Anki-Android/issues/7680)|2.13.5| 56 | |52|AnkiDroid|[7695](https://github.com/ankidroid/Anki-Android/issues/7695)|2.13.5| 57 | |53|AnkiDroid|[7674](https://github.com/ankidroid/Anki-Android/issues/7674)|2.13.5| 58 | |54|AnkiDroid|[6288](https://github.com/ankidroid/Anki-Android/issues/6288)|2.11alpha7| 59 | |55|AnkiDroid|[7438](https://github.com/ankidroid/Anki-Android/issues/7438)| | 60 | |56|AnkiDroid|[7377](https://github.com/ankidroid/Anki-Android/issues/7377)|2.12.1| 61 | |57|AnkiDroid|[6857](https://github.com/ankidroid/Anki-Android/issues/6857)|2.12.1| 62 | |58|AnkiDroid|[7366](https://github.com/ankidroid/Anki-Android/issues/7366)|2.14alpha17| 63 | |59|AnkiDroid|[7286](https://github.com/ankidroid/Anki-Android/issues/7286)|2.13.0| 64 | |60|AnkiDroid|[7138](https://github.com/ankidroid/Anki-Android/issues/7138)|2.13.0| 65 | |61|AnkiDroid|[7076](https://github.com/ankidroid/Anki-Android/issues/7076)|2.14alpha1| 66 | |62|AnkiDroid|[7070](https://github.com/ankidroid/Anki-Android/issues/7070)|2.12.1| 67 | |63|AnkiDroid|[7027](https://github.com/ankidroid/Anki-Android/issues/7027)|2.13alpha26| 68 | |64|AnkiDroid|[5802](https://github.com/ankidroid/Anki-Android/issues/5802)|2.10alpha45| 69 | |65|AnkiDroid|[6853](https://github.com/ankidroid/Anki-Android/issues/6853)|2.11.1| 70 | |66|AnkiDroid|[6887](https://github.com/ankidroid/Anki-Android/issues/6887)|2.12.1| 71 | |67|AnkiDroid|[6894](https://github.com/ankidroid/Anki-Android/issues/6894)|2.12.1| 72 | |68|AnkiDroid|[5016](https://github.com/ankidroid/Anki-Android/issues/5016)|2.9alpha40| 73 | |69|AnkiDroid|[5143](https://github.com/ankidroid/Anki-Android/issues/5143)|2.8.4| 74 | |70|AnkiDroid|[5973](https://github.com/ankidroid/Anki-Android/issues/5973)|2.10alpha65| 75 | |71|AnkiDroid|[6684](https://github.com/ankidroid/Anki-Android/issues/6684)|2.12.0beta6| 76 | |72|AnkiDroid|[6587](https://github.com/ankidroid/Anki-Android/issues/6587)|2.12alpha18| 77 | |73|AnkiDroid|[6576](https://github.com/ankidroid/Anki-Android/issues/6576)|2.12alpha17| 78 | |74|AnkiDroid|[6513](https://github.com/ankidroid/Anki-Android/issues/6513)|2.9.3| 79 | |75|AnkiDroid|[5409](https://github.com/ankidroid/Anki-Android/issues/5409)| | 80 | |76|AnkiDroid|[6254](https://github.com/ankidroid/Anki-Android/issues/6254)|2.11alpha6| 81 | |77|AnkiDroid|[6228](https://github.com/ankidroid/Anki-Android/issues/6228)|2.10.2| 82 | |78|AnkiDroid|[6200](https://github.com/ankidroid/Anki-Android/issues/6200)|2.10.2| 83 | |79|AnkiDroid|[6167](https://github.com/ankidroid/Anki-Android/issues/6167)|2.10| 84 | |80|AnkiDroid|[6119](https://github.com/ankidroid/Anki-Android/issues/6119)|2.10beta3| 85 | |81|AnkiDroid|[5512](https://github.com/ankidroid/Anki-Android/issues/5512)|2.9| 86 | |82|AnkiDroid|[5187](https://github.com/ankidroid/Anki-Android/issues/5187)|2.9alpha55 2.9.3| 87 | |83|AnkiDroid|[5695](https://github.com/ankidroid/Anki-Android/issues/5695)|2.10alpha26| 88 | |84|AnkiDroid|[5352](https://github.com/ankidroid/Anki-Android/issues/5352)|2.9alpha75| 89 | |85|AnkiDroid|[5544](https://github.com/ankidroid/Anki-Android/issues/5544)|2.8.4| 90 | |86|AnkiDroid|[5688](https://github.com/ankidroid/Anki-Android/issues/5688)|2.9.2| 91 | |87|AnkiDroid|[5737](https://github.com/ankidroid/Anki-Android/issues/5737)|2.9.2| 92 | |88|AnkiDroid|[5415](https://github.com/ankidroid/Anki-Android/issues/5415)|2.9beta7| 93 | |89|AnkiDroid|[5713](https://github.com/ankidroid/Anki-Android/issues/5713)|2.9| 94 | |90|AnkiDroid|[5091](https://github.com/ankidroid/Anki-Android/issues/5091)|2.9.7| 95 | |91|AnkiDroid|[5166](https://github.com/ankidroid/Anki-Android/issues/5166)|2.9.2beta4| 96 | |92|AnkiDroid|[5450](https://github.com/ankidroid/Anki-Android/issues/5450)|2.9beta12| 97 | |93|AnkiDroid|[5184](https://github.com/ankidroid/Anki-Android/issues/5184)|2.9alpha55 2.8.4| 98 | |94|AnkiDroid|[5334](https://github.com/ankidroid/Anki-Android/issues/5334)|2.2alpha59| 99 | |95|AnkiDroid|[5262](https://github.com/ankidroid/Anki-Android/issues/5262)|2.8.4| 100 | |96|AnkiDroid|[5245](https://github.com/ankidroid/Anki-Android/issues/5245)| | 101 | |97|AnkiDroid|[5156](https://github.com/ankidroid/Anki-Android/issues/5156)|2.9alpha54| 102 | |98|AnkiDroid|[5216](https://github.com/ankidroid/Anki-Android/issues/5216)|2.9alpha58| 103 | |99|AnkiDroid|[5167](https://github.com/ankidroid/Anki-Android/issues/5167)|2.9alpha54| 104 | |100|AnkiDroid|[5105](https://github.com/ankidroid/Anki-Android/issues/5105)|2.9alpha50| 105 | |101|AnkiDroid|[4999](https://github.com/ankidroid/Anki-Android/issues/4999)|2.8.4| 106 | |102|AnkiDroid|[4951](https://github.com/ankidroid/Anki-Android/issues/4951)|2.9alpha29| 107 | |103|AnkiDroid|[4935](https://github.com/ankidroid/Anki-Android/issues/4935)|2.9alpha29| 108 | |104|AnkiDroid|[8072](https://github.com/ankidroid/Anki-Android/issues/8072)| | 109 | |105|AnkiDroid|[7992](https://github.com/ankidroid/Anki-Android/issues/7992)|2.15alpha20| 110 | |106|AnkiDroid|[9005](https://github.com/ankidroid/Anki-Android/issues/9005)|2.15.1| 111 | |107|AnkiDroid|[8975](https://github.com/ankidroid/Anki-Android/issues/8975)|2.15.2| 112 | |108|AnkiDroid|[8466](https://github.com/ankidroid/Anki-Android/issues/8466)| | 113 | |109|AnkiDroid|[8417](https://github.com/ankidroid/Anki-Android/issues/8417)|2.15alpha34| 114 | |110|AnkiDroid|[8527](https://github.com/ankidroid/Anki-Android/issues/8527)|2.15alpha39| 115 | |111|AnkiDroid|[8547](https://github.com/ankidroid/Anki-Android/issues/8547)|2.15alpha38| 116 | |112|AnkiDroid|[8379](https://github.com/ankidroid/Anki-Android/issues/8379)|2.15alpha34| 117 | |113|AnkiDroid|[8354](https://github.com/ankidroid/Anki-Android/issues/8354)|2.15alpha32| 118 | |114|AnkiDroid|[7934](https://github.com/ankidroid/Anki-Android/issues/7934)|2.14.2| 119 | |115|AnkiDroid|[8008](https://github.com/ankidroid/Anki-Android/issues/8008)| | 120 | |116|AnkiDroid|[7896](https://github.com/ankidroid/Anki-Android/issues/7896)|2.14.2| 121 | |117|AnkiDroid|[7023](https://github.com/ankidroid/Anki-Android/issues/7023)|2.13alpha25| 122 | |118|Amaze|[2595](https://github.com/TeamAmaze/AmazeFileManager/issues/2595)|3.5.3| 123 | |119|Amaze|[2526](https://github.com/TeamAmaze/AmazeFileManager/issues/2526)|3.5.3 | 124 | |120|Amaze|[2518](https://github.com/TeamAmaze/AmazeFileManager/issues/2518)|9f3f1dc| 125 | |121|Amaze|[2498](https://github.com/TeamAmaze/AmazeFileManager/issues/2498)|9c8048a| 126 | |122|Amaze|[2477](https://github.com/TeamAmaze/AmazeFileManager/issues/2477)|b7c9c81 | 127 | |123|Amaze|[2476](https://github.com/TeamAmaze/AmazeFileManager/issues/2476)|06028eda | 128 | |124|Amaze|[2360](https://github.com/TeamAmaze/AmazeFileManager/issues/2360)|3.5.0| 129 | |125|Amaze|[2131](https://github.com/TeamAmaze/AmazeFileManager/issues/2131)|3.5.0 | 130 | |126|Amaze|[2113](https://github.com/TeamAmaze/AmazeFileManager/issues/2113)|3.5.1 | 131 | |127|Amaze|[2079](https://github.com/TeamAmaze/AmazeFileManager/issues/2079)|3.5.0 | 132 | |128|Amaze|[2010](https://github.com/TeamAmaze/AmazeFileManager/issues/2010)|3.5.0beta3 | 133 | |129|Amaze|[1982](https://github.com/TeamAmaze/AmazeFileManager/issues/1982)|3.4.3 | 134 | |130|Amaze|[1978](https://github.com/TeamAmaze/AmazeFileManager/issues/1978)|3.4.3 | 135 | |131|Amaze|[1951](https://github.com/TeamAmaze/AmazeFileManager/issues/1951)|3.4.3| 136 | |132|Amaze|[1933](https://github.com/TeamAmaze/AmazeFileManager/issues/1933)|3.4.3 | 137 | |133|Amaze|[1920](https://github.com/TeamAmaze/AmazeFileManager/issues/1920)|3.4.3 .1.0| 138 | |134|Amaze|[1919](https://github.com/TeamAmaze/AmazeFileManager/issues/1919)|3.4.3 .1.0| 139 | |135|Amaze|[1916](https://github.com/TeamAmaze/AmazeFileManager/issues/1916)|3.4.3 | 140 | |136|Amaze|[1885](https://github.com/TeamAmaze/AmazeFileManager/issues/1885)|3.4.3 | 141 | |137|Amaze|[1872](https://github.com/TeamAmaze/AmazeFileManager/issues/1872)|3.4.3 | 142 | |138|Amaze|[1866](https://github.com/TeamAmaze/AmazeFileManager/issues/1866)|3.4.3 | 143 | |139|Amaze|[1834](https://github.com/TeamAmaze/AmazeFileManager/issues/1834)|3.4.2 | 144 | |140|Amaze|[1797](https://github.com/TeamAmaze/AmazeFileManager/issues/1797)|3.5.0| 145 | |141|Amaze|[1712](https://github.com/TeamAmaze/AmazeFileManager/issues/1712)|3.4.3 | 146 | |142|Amaze|[1687](https://github.com/TeamAmaze/AmazeFileManager/issues/1687)|3.3.4 | 147 | |143|Amaze|[1628](https://github.com/TeamAmaze/AmazeFileManager/issues/1628)|3.3.2 | 148 | |144|Amaze|[1499](https://github.com/TeamAmaze/AmazeFileManager/issues/1499)|3.30RC10| 149 | |145|Amaze|[1497](https://github.com/TeamAmaze/AmazeFileManager/issues/1497)| | 150 | |146|Amaze|[1475](https://github.com/TeamAmaze/AmazeFileManager/issues/1475)|3.3.0RC8 | 151 | |147|Amaze|[1467](https://github.com/TeamAmaze/AmazeFileManager/issues/1467)|3.6.0 | 152 | |148|K-9 Mail|[5292](https://github.com/k9mail/k-9/issues/5292)|5.734-SNAPSHOT| 153 | |149|K-9 Mail|[5206](https://github.com/k9mail/k-9/issues/5206)|5.732| 154 | |150|K-9 Mail|[5198](https://github.com/k9mail/k-9/issues/5198)|5.732| 155 | |151|K-9 Mail|[5139](https://github.com/k9mail/k-9/issues/5139)|5.728| 156 | |152|K-9 Mail|[5088](https://github.com/k9mail/k-9/issues/5088)|5.726| 157 | |153|K-9 Mail|[5044](https://github.com/k9mail/k-9/issues/5044)|5.725| 158 | |154|K-9 Mail|[5026](https://github.com/k9mail/k-9/issues/5026)|5.722| 159 | |155|K-9 Mail|[5004](https://github.com/k9mail/k-9/issues/5004)|5.721| 160 | |156|K-9 Mail|[4984](https://github.com/k9mail/k-9/issues/4984)|5.719| 161 | |157|K-9 Mail|[4951](https://github.com/k9mail/k-9/issues/4951)|5.6| 162 | |158|K-9 Mail|[4936](https://github.com/k9mail/k-9/issues/4936)|5.6| 163 | |159|K-9 Mail|[4935](https://github.com/k9mail/k-9/issues/4935)|5.718| 164 | |160|K-9 Mail|[4872](https://github.com/k9mail/k-9/issues/4872)|5.717| 165 | |161|K-9 Mail|[4804](https://github.com/k9mail/k-9/issues/4804)|5.715| 166 | |162|K-9 Mail|[4742](https://github.com/k9mail/k-9/issues/4742)|5.711| 167 | |163|K-9 Mail|[4685](https://github.com/k9mail/k-9/issues/4685)|5.709| 168 | |164|K-9 Mail|[4678](https://github.com/k9mail/k-9/issues/4678)|5.708| 169 | |165|K-9 Mail|[4610](https://github.com/k9mail/k-9/issues/4610)|5.705 | 170 | |166|K-9 Mail|[4517](https://github.com/k9mail/k-9/issues/4517)|de1612f| 171 | |167|K-9 Mail|[4472](https://github.com/k9mail/k-9/issues/4472)|5.7| 172 | |168|K-9 Mail|[4453](https://github.com/k9mail/k-9/issues/4453)|5.703 5.719 | 173 | |169|K-9 Mail|[4452](https://github.com/k9mail/k-9/issues/4452)|5.703| 174 | |170|K-9 Mail|[4435](https://github.com/k9mail/k-9/issues/4435)|5.6| 175 | |171|K-9 Mail|[4407](https://github.com/k9mail/k-9/issues/4407)|5.703| 176 | |172|K-9 Mail|[4393](https://github.com/k9mail/k-9/issues/4393)|5.701 | 177 | |173|K-9 Mail|[4379](https://github.com/k9mail/k-9/issues/4379)|5.701 | 178 | |174|K-9 Mail|[4374](https://github.com/k9mail/k-9/issues/4374)|5.700 | 179 | |175|K-9 Mail|[4360](https://github.com/k9mail/k-9/issues/4360)|5.701| 180 | |176|K-9 Mail|[4342](https://github.com/k9mail/k-9/issues/4342)|5.701| 181 | |177|K-9 Mail|[4333](https://github.com/k9mail/k-9/issues/4333)|5.701 | 182 | |178|K-9 Mail|[4304](https://github.com/k9mail/k-9/issues/4304)|5.700 | 183 | |179|K-9 Mail|[3957](https://github.com/k9mail/k-9/issues/3957)|3e1a8da| 184 | |180|K-9 Mail|[3866](https://github.com/k9mail/k-9/issues/3866)|5.800 | 185 | |181|K-9 Mail|[3832](https://github.com/k9mail/k-9/issues/3832)| | 186 | |182|K-9 Mail|[3811](https://github.com/k9mail/k-9/issues/3811)|5.6| 187 | |183|K-9 Mail|[3685](https://github.com/k9mail/k-9/issues/3685)|5.600 | 188 | |184|NewPIpe|[6298](https://github.com/TeamNewPipe/NewPipe/issues/6298)|f80b1fb2feb3076b612f6fb86e6b904b58ae1929| 189 | |185|NewPIpe|[4256](https://github.com/TeamNewPipe/NewPipe/issues/4256)| | 190 | |186|NewPIpe|[6667](https://github.com/TeamNewPipe/NewPipe/issues/6667)|0.21.6| 191 | |187|NewPIpe|[3647](https://github.com/TeamNewPipe/NewPipe/issues/3647)| | 192 | |188|NewPIpe|[3888](https://github.com/TeamNewPipe/NewPipe/issues/3888)|0.19.6| 193 | |189|NewPIpe|[6419](https://github.com/TeamNewPipe/NewPipe/issues/6419)| | 194 | |190|NewPIpe|[4040](https://github.com/TeamNewPipe/NewPipe/issues/4040)| | 195 | |191|NewPIpe|[6664](https://github.com/TeamNewPipe/NewPipe/issues/6664)| | 196 | |192|NewPIpe|[5959](https://github.com/TeamNewPipe/NewPipe/issues/5959)| | 197 | |193|NewPIpe|[5036](https://github.com/TeamNewPipe/NewPipe/issues/5036)| | 198 | |194|NewPIpe|[3673](https://github.com/TeamNewPipe/NewPipe/issues/3673)| | 199 | |195|NewPIpe|[3149](https://github.com/TeamNewPipe/NewPipe/issues/3149)| | 200 | |196|NewPIpe|[2272](https://github.com/TeamNewPipe/NewPipe/issues/2272)| | 201 | |197|NewPIpe|[2848](https://github.com/TeamNewPipe/NewPipe/issues/2848)| | 202 | |198|NewPIpe|[6323](https://github.com/TeamNewPipe/NewPipe/issues/6323)| | 203 | |199|NewPIpe|[5805](https://github.com/TeamNewPipe/NewPipe/issues/5805)| | 204 | |200|NewPIpe|[5613](https://github.com/TeamNewPipe/NewPipe/issues/5613)| | 205 | |201|NewPIpe|[3238](https://github.com/TeamNewPipe/NewPipe/issues/3238)| | 206 | |202|NewPIpe|[6138](https://github.com/TeamNewPipe/NewPipe/issues/6138)| | 207 | |203|NewPIpe|[3973](https://github.com/TeamNewPipe/NewPipe/issues/3973)| | 208 | |204|NewPIpe|[3851](https://github.com/TeamNewPipe/NewPipe/issues/3851)| | 209 | |205|NewPIpe|[5895](https://github.com/TeamNewPipe/NewPipe/issues/5895)| | 210 | |206|NewPIpe|[5550](https://github.com/TeamNewPipe/NewPipe/issues/5550)| | 211 | |207|NewPIpe|[2632](https://github.com/TeamNewPipe/NewPipe/issues/2632)| | 212 | |208|NewPIpe|[3145](https://github.com/TeamNewPipe/NewPipe/issues/3145)| | 213 | |209|NewPIpe|[4277](https://github.com/TeamNewPipe/NewPipe/issues/4277)| | 214 | |210|NewPIpe|[5363](https://github.com/TeamNewPipe/NewPipe/issues/5363)| | 215 | |211|NewPIpe|[4940](https://github.com/TeamNewPipe/NewPipe/issues/4940)|0.21.1| 216 | |212|NewPIpe|[6409](https://github.com/TeamNewPipe/NewPipe/issues/6409)| | 217 | |213|NewPIpe|[6256](https://github.com/TeamNewPipe/NewPipe/issues/6256)| | 218 | |214|NewPIpe|[2111](https://github.com/TeamNewPipe/NewPipe/issues/2111)| | 219 | |215|NewPIpe|[4245](https://github.com/TeamNewPipe/NewPipe/issues/4245)| | 220 | |216|NewPIpe|[6468](https://github.com/TeamNewPipe/NewPipe/issues/6468)| | 221 | |217|NewPIpe|[2810](https://github.com/TeamNewPipe/NewPipe/issues/2810)| | 222 | |218|NewPIpe|[4121](https://github.com/TeamNewPipe/NewPipe/issues/4121)| | 223 | |219|NewPIpe|[3937](https://github.com/TeamNewPipe/NewPipe/issues/3937)| | 224 | |220|NewPIpe|[5453](https://github.com/TeamNewPipe/NewPipe/issues/5453)| | 225 | |221|NewPIpe|[2626](https://github.com/TeamNewPipe/NewPipe/issues/2626)| | 226 | |222|NewPIpe|[6371](https://github.com/TeamNewPipe/NewPipe/issues/6371)|0.21.2| 227 | |223|NewPIpe|[2504](https://github.com/TeamNewPipe/NewPipe/issues/2504)| | 228 | |224|NewPIpe|[3381](https://github.com/TeamNewPipe/NewPipe/issues/3381)| | 229 | |225|NewPIpe|[2924](https://github.com/TeamNewPipe/NewPipe/issues/2924)| | 230 | |226|NewPIpe|[2894](https://github.com/TeamNewPipe/NewPipe/issues/2894)| | 231 | |227|NewPIpe|[5912](https://github.com/TeamNewPipe/NewPipe/issues/5912)| | 232 | |228|NewPIpe|[2922](https://github.com/TeamNewPipe/NewPipe/issues/2922)| | 233 | |229|NewPIpe|[4113](https://github.com/TeamNewPipe/NewPipe/issues/4113)| | 234 | |230|NewPIpe|[4143](https://github.com/TeamNewPipe/NewPipe/issues/4143)| | 235 | |231|NewPIpe|[4294](https://github.com/TeamNewPipe/NewPipe/issues/4294)| | 236 | |232|NewPIpe|[6615](https://github.com/TeamNewPipe/NewPipe/issues/6615)| | 237 | |233|NewPIpe|[1588](https://github.com/TeamNewPipe/NewPipe/issues/1588)| | 238 | |234|NewPIpe|[6335](https://github.com/TeamNewPipe/NewPipe/issues/6335)|0.21.2| 239 | |235|NewPIpe|[5475](https://github.com/TeamNewPipe/NewPipe/issues/5475)| | 240 | |236|NewPIpe|[6397](https://github.com/TeamNewPipe/NewPipe/issues/6397)|0.21.3| 241 | |237|NewPIpe|[1919](https://github.com/TeamNewPipe/NewPipe/issues/1919)| | 242 | |238|NewPIpe|[2586](https://github.com/TeamNewPipe/NewPipe/issues/2586)| | 243 | |239|NewPIpe|[3684](https://github.com/TeamNewPipe/NewPipe/issues/3684)| | 244 | |240|NewPIpe|[4712](https://github.com/TeamNewPipe/NewPipe/issues/4712)| | 245 | |241|NewPIpe|[3714](https://github.com/TeamNewPipe/NewPipe/issues/3714)| | 246 | |242|NewPIpe|[4047](https://github.com/TeamNewPipe/NewPipe/issues/4047)| | 247 | |243|NewPIpe|[2615](https://github.com/TeamNewPipe/NewPipe/issues/2615)| | 248 | |244|NewPIpe|[2644](https://github.com/TeamNewPipe/NewPipe/issues/2644)| | 249 | |245|NewPIpe|[3681](https://github.com/TeamNewPipe/NewPipe/issues/3681)| | 250 | |246|NewPIpe|[2007](https://github.com/TeamNewPipe/NewPipe/issues/2007)| | 251 | |247|NewPIpe|[5660](https://github.com/TeamNewPipe/NewPipe/issues/5660)| | 252 | |248|NewPIpe|[4890](https://github.com/TeamNewPipe/NewPipe/issues/4890)|0.20.2| 253 | |249|AntennaPod|[5003](https://github.com/AntennaPod/AntennaPod/issues/5003)|2.2.0-beta1| 254 | |250|AntennaPod|[3977](https://github.com/AntennaPod/AntennaPod/issues/3977)|1.8.1| 255 | |251|AntennaPod|[4010](https://github.com/AntennaPod/AntennaPod/issues/4010)|2.0.0-alpha1| 256 | |252|AntennaPod|[4669](https://github.com/AntennaPod/AntennaPod/issues/4669)|2.1.0-RC2| 257 | |253|AntennaPod|[4715](https://github.com/AntennaPod/AntennaPod/issues/4715)| | 258 | |254|AntennaPod|[3364](https://github.com/AntennaPod/AntennaPod/issues/3364)|1.7.3-RC3| 259 | |255|AntennaPod|[4028](https://github.com/AntennaPod/AntennaPod/issues/4028)|2.0.0-alpha1| 260 | |256|AntennaPod|[4202](https://github.com/AntennaPod/AntennaPod/issues/4202)|2.0.0-RC3| 261 | |257|AntennaPod|[3209](https://github.com/AntennaPod/AntennaPod/issues/3209)|1.7.2b| 262 | |258|AntennaPod|[5275](https://github.com/AntennaPod/AntennaPod/issues/5275)|2.3.0| 263 | |259|AntennaPod|[4488](https://github.com/AntennaPod/AntennaPod/issues/4488)|2.0.0| 264 | |260|AntennaPod|[4853](https://github.com/AntennaPod/AntennaPod/issues/4853)| | 265 | |261|AntennaPod|[5091](https://github.com/AntennaPod/AntennaPod/issues/5091)| | 266 | |262|AntennaPod|[3092](https://github.com/AntennaPod/AntennaPod/issues/3092)|1.7.1| 267 | |263|AntennaPod|[4550](https://github.com/AntennaPod/AntennaPod/issues/4550)|885362e5| 268 | |264|AntennaPod|[3275](https://github.com/AntennaPod/AntennaPod/issues/3275)| | 269 | |265|AntennaPod|[4126](https://github.com/AntennaPod/AntennaPod/issues/4126)| | 270 | |266|AntennaPod|[4640](https://github.com/AntennaPod/AntennaPod/issues/4640)|1.7.1| 271 | |267|AntennaPod|[4656](https://github.com/AntennaPod/AntennaPod/issues/4656)|2.1.0-RC1| 272 | |268|AntennaPod|[4879](https://github.com/AntennaPod/AntennaPod/issues/4879)| | 273 | |269|AntennaPod|[4327](https://github.com/AntennaPod/AntennaPod/issues/4327)| | 274 | |270|AntennaPod|[3573](https://github.com/AntennaPod/AntennaPod/issues/3573)| | 275 | |271|AntennaPod|[4776](https://github.com/AntennaPod/AntennaPod/issues/4776)|2.1.1| 276 | |272|AntennaPod|[3786](https://github.com/AntennaPod/AntennaPod/issues/3786)|1.8.0| 277 | |273|AntennaPod|[2992](https://github.com/AntennaPod/AntennaPod/issues/2992)|1.7.1| 278 | |274|AntennaPod|[4548](https://github.com/AntennaPod/AntennaPod/issues/4548)|2.0.1| 279 | |275|AntennaPod|[4473](https://github.com/AntennaPod/AntennaPod/issues/4473)| | 280 | |276|AntennaPod|[2953](https://github.com/AntennaPod/AntennaPod/issues/2953)| | 281 | |277|AntennaPod|[3986](https://github.com/AntennaPod/AntennaPod/issues/3986)| | 282 | |278|AntennaPod|[5090](https://github.com/AntennaPod/AntennaPod/issues/5090)| | 283 | |279|AntennaPod|[4123](https://github.com/AntennaPod/AntennaPod/issues/4123)|2.0.0-alpha2| 284 | |280|AntennaPod|[5132](https://github.com/AntennaPod/AntennaPod/issues/5132)|2.2.0| 285 | |281|AntennaPod|[4026](https://github.com/AntennaPod/AntennaPod/issues/4026)|2.0.0-alpha1| 286 | |282|AntennaPod|[5218](https://github.com/AntennaPod/AntennaPod/issues/5218)|2.2.1| 287 | |283|AntennaPod|[5255](https://github.com/AntennaPod/AntennaPod/issues/5255)|2.3.0-beta3| 288 | |284|AntennaPod|[4275](https://github.com/AntennaPod/AntennaPod/issues/4275)|2.0.0-RC5| 289 | |285|AntennaPod|[5252](https://github.com/AntennaPod/AntennaPod/issues/5252)| | 290 | |286|AntennaPod|[4636](https://github.com/AntennaPod/AntennaPod/issues/4636)| | 291 | |287|AntennaPod|[4734](https://github.com/AntennaPod/AntennaPod/issues/4734)|2.1.0| 292 | |288|AntennaPod|[4146](https://github.com/AntennaPod/AntennaPod/issues/4146)| | 293 | |289|AntennaPod|[4524](https://github.com/AntennaPod/AntennaPod/issues/4524)|2.0.1| 294 | |290|WordPress|[15026](https://github.com/wordpress-mobile/WordPress-Android/issues/15026)|17.8-rc-1| 295 | |291|WordPress|[14787](https://github.com/wordpress-mobile/WordPress-Android/issues/14787)|17.3| 296 | |292|WordPress|[14430](https://github.com/wordpress-mobile/WordPress-Android/issues/14430)| | 297 | |293|WordPress|[14234](https://github.com/wordpress-mobile/WordPress-Android/issues/14234)|16.8| 298 | |294|WordPress|[14216](https://github.com/wordpress-mobile/WordPress-Android/issues/14216)|16.8-rc-2| 299 | |295|WordPress|[13853](https://github.com/wordpress-mobile/WordPress-Android/issues/13853)| | 300 | |296|WordPress|[13671](https://github.com/wordpress-mobile/WordPress-Android/issues/13671)|16.3-rc-2| 301 | |297|WordPress|[13428](https://github.com/wordpress-mobile/WordPress-Android/issues/13428)|16.2-rc-1| 302 | |298|WordPress|[13299](https://github.com/wordpress-mobile/WordPress-Android/issues/13299)| | 303 | |299|WordPress|[13121](https://github.com/wordpress-mobile/WordPress-Android/issues/13121)|15.9-rc-1| 304 | |300|WordPress|[13067](https://github.com/wordpress-mobile/WordPress-Android/issues/13067)|15.9-rc-1| 305 | |301|WordPress|[13037](https://github.com/wordpress-mobile/WordPress-Android/issues/13037)|15.7| 306 | |302|WordPress|[12975](https://github.com/wordpress-mobile/WordPress-Android/issues/12975)|alpha-244| 307 | |303|WordPress|[12915](https://github.com/wordpress-mobile/WordPress-Android/issues/12915)|alpha-241| 308 | |304|WordPress|[12858](https://github.com/wordpress-mobile/WordPress-Android/issues/12858)|13.9-rc-1| 309 | |305|WordPress|[12767](https://github.com/wordpress-mobile/WordPress-Android/issues/12767)|15.4.1| 310 | |306|WordPress|[12578](https://github.com/wordpress-mobile/WordPress-Android/issues/12578)|15.4-rc-1| 311 | |307|WordPress|[12556](https://github.com/wordpress-mobile/WordPress-Android/issues/12556)|15.4-rc-1| 312 | |308|WordPress|[12544](https://github.com/wordpress-mobile/WordPress-Android/issues/12544)|15.4-rc-1| 313 | |309|WordPress|[12376](https://github.com/wordpress-mobile/WordPress-Android/issues/12376)| | 314 | |310|WordPress|[12308](https://github.com/wordpress-mobile/WordPress-Android/issues/12308)|11.5| 315 | |311|WordPress|[12224](https://github.com/wordpress-mobile/WordPress-Android/issues/12224)|15.1-rc-1| 316 | |312|WordPress|[12040](https://github.com/wordpress-mobile/WordPress-Android/issues/12040)|14.9-rc-2| 317 | |313|WordPress|[11950](https://github.com/wordpress-mobile/WordPress-Android/issues/11950)| | 318 | |314|WordPress|[11679](https://github.com/wordpress-mobile/WordPress-Android/issues/11679)|14.6-rc-2| 319 | |315|WordPress|[11307](https://github.com/wordpress-mobile/WordPress-Android/issues/11307)|14.2| 320 | |316|WordPress|[11019](https://github.com/wordpress-mobile/WordPress-Android/issues/11019)|13.9-rc-1| 321 | |317|WordPress|[11018](https://github.com/wordpress-mobile/WordPress-Android/issues/11018)|alpha-200| 322 | |318|WordPress|[11010](https://github.com/wordpress-mobile/WordPress-Android/issues/11010)|alpha-200| 323 | |319|WordPress|[10952](https://github.com/wordpress-mobile/WordPress-Android/issues/10952)|alpha-200| 324 | |320|WordPress|[10951](https://github.com/wordpress-mobile/WordPress-Android/issues/10951)|13.4| 325 | |321|WordPress|[10836](https://github.com/wordpress-mobile/WordPress-Android/issues/10836)|6.4| 326 | |322|WordPress|[10835](https://github.com/wordpress-mobile/WordPress-Android/issues/10835)|13.5| 327 | |323|WordPress|[10707](https://github.com/wordpress-mobile/WordPress-Android/issues/10707)|13.5| 328 | |324|WordPress|[10410](https://github.com/wordpress-mobile/WordPress-Android/issues/10410)| | 329 | |325|WordPress|[10354](https://github.com/wordpress-mobile/WordPress-Android/issues/10354)| | 330 | |326|WordPress|[10288](https://github.com/wordpress-mobile/WordPress-Android/issues/10288)| | 331 | |327|WordPress|[10203](https://github.com/wordpress-mobile/WordPress-Android/issues/10203)|11.1| 332 | |328|WordPress|[10145](https://github.com/wordpress-mobile/WordPress-Android/issues/10145)|12.8-rc-1| 333 | |329|WordPress|[9966](https://github.com/wordpress-mobile/WordPress-Android/issues/9966)|12.4| 334 | |330|WordPress|[9935](https://github.com/wordpress-mobile/WordPress-Android/issues/9935)|12.4| 335 | |331|WordPress|[9899](https://github.com/wordpress-mobile/WordPress-Android/issues/9899)|12.3.2| 336 | |332|WordPress|[9897](https://github.com/wordpress-mobile/WordPress-Android/issues/9897)|11.8| 337 | |333|WordPress|[9864](https://github.com/wordpress-mobile/WordPress-Android/issues/9864)|11.2| 338 | |334|WordPress|[9717](https://github.com/wordpress-mobile/WordPress-Android/issues/9717)|12.4| 339 | |335|WordPress|[9658](https://github.com/wordpress-mobile/WordPress-Android/issues/9658)|12.1.2| 340 | |336|WordPress|[9604](https://github.com/wordpress-mobile/WordPress-Android/issues/9604)| | 341 | |337|WordPress|[9603](https://github.com/wordpress-mobile/WordPress-Android/issues/9603)| | 342 | |338|WordPress|[9599](https://github.com/wordpress-mobile/WordPress-Android/issues/9599)|12.1| 343 | |339|WordPress|[9595](https://github.com/wordpress-mobile/WordPress-Android/issues/9595)| | 344 | |340|WordPress|[9575](https://github.com/wordpress-mobile/WordPress-Android/issues/9575)| | 345 | |341|WordPress|[9573](https://github.com/wordpress-mobile/WordPress-Android/issues/9573)|12.1| 346 | |342|WordPress|[9567](https://github.com/wordpress-mobile/WordPress-Android/issues/9567)| | 347 | |343|WordPress|[9088](https://github.com/wordpress-mobile/WordPress-Android/issues/9088)|11.5| 348 | |344|WordPress|[8994](https://github.com/wordpress-mobile/WordPress-Android/issues/8994)| | 349 | |345|WordPress|[8964](https://github.com/wordpress-mobile/WordPress-Android/issues/8964)|alpha-145| 350 | |346|WordPress|[8821](https://github.com/wordpress-mobile/WordPress-Android/issues/8821)|11.6| 351 | |347|WordPress|[8755](https://github.com/wordpress-mobile/WordPress-Android/issues/8755)|11.3| 352 | |348|WordPress|[8754](https://github.com/wordpress-mobile/WordPress-Android/issues/8754)| | 353 | |349|WordPress|[8582](https://github.com/wordpress-mobile/WordPress-Android/issues/8582)|11.2| 354 | |350|WordPress|[8570](https://github.com/wordpress-mobile/WordPress-Android/issues/8570)|11| 355 | |351|WordPress|[8521](https://github.com/wordpress-mobile/WordPress-Android/issues/8521)|10.9-rc-1| 356 | |352|WordPress|[8520](https://github.com/wordpress-mobile/WordPress-Android/issues/8520)|11.2| 357 | |353|WordPress|[8370](https://github.com/wordpress-mobile/WordPress-Android/issues/8370)|10.9| 358 | |354|WordPress|[8283](https://github.com/wordpress-mobile/WordPress-Android/issues/8283)|9.5-rc-1| 359 | |355|WordPress|[8222](https://github.com/wordpress-mobile/WordPress-Android/issues/8222)|10.5.2| 360 | |356|WordPress|[8220](https://github.com/wordpress-mobile/WordPress-Android/issues/8220)|10.6| 361 | |357|Firefox|[4583](https://github.com/mozilla-mobile/focus-android/issues/4583)|8.5.0| 362 | |358|Firefox|[4617](https://github.com/mozilla-mobile/focus-android/issues/4617)|8.7.2-arm| 363 | |359|Firefox|[3394](https://github.com/mozilla-mobile/focus-android/issues/3394)| | 364 | |360|Firefox|[3617](https://github.com/mozilla-mobile/focus-android/issues/3617)|8| 365 | |361|Firefox|[3081](https://github.com/mozilla-mobile/focus-android/issues/3081)|6.3| 366 | |362|Firefox|[3939](https://github.com/mozilla-mobile/focus-android/issues/3939)|8.0-arm| 367 | |363|Firefox|[3713](https://github.com/mozilla-mobile/focus-android/issues/3713)| | 368 | |364|Firefox|[3152](https://github.com/mozilla-mobile/focus-android/issues/3152)|7.0beta2| 369 | |365|Firefox|[3790](https://github.com/mozilla-mobile/focus-android/issues/3790)| | 370 | |366|Firefox|[3829](https://github.com/mozilla-mobile/focus-android/issues/3829)| | 371 | |367|Firefox|[4858](https://github.com/mozilla-mobile/focus-android/issues/4858)|Debug8.13.1-Build#1190.a1-20210525093431-arm| 372 | |368|Firefox|[3563](https://github.com/mozilla-mobile/focus-android/issues/3563)|7.0RC10-arm| 373 | |369|Firefox|[3633](https://github.com/mozilla-mobile/focus-android/issues/3633)| | 374 | |370|Firefox|[3258](https://github.com/mozilla-mobile/focus-android/issues/3258)| | 375 | |371|Firefox|[4880](https://github.com/mozilla-mobile/focus-android/issues/4880)|8.18.0| 376 | |372|Firefox|[3291](https://github.com/mozilla-mobile/focus-android/issues/3291)|7.0beta2| 377 | |373|Firefox|[3892](https://github.com/mozilla-mobile/focus-android/issues/3892)|7.0.13| 378 | |374|Firefox|[4041](https://github.com/mozilla-mobile/focus-android/issues/4041)|8.11.1| 379 | |375|Firefox|[4068](https://github.com/mozilla-mobile/focus-android/issues/4068)|8.0.8| 380 | |376|Firefox|[3128](https://github.com/mozilla-mobile/focus-android/issues/3128)|7.0beta1| 381 | |377|Firefox|[4097](https://github.com/mozilla-mobile/focus-android/issues/4097)|8.14.0| 382 | |378|Firefox|[3069](https://github.com/mozilla-mobile/focus-android/issues/3069)| | 383 | |379|Firefox|[3146](https://github.com/mozilla-mobile/focus-android/issues/3146)|7.0beta1| 384 | |380|Firefox|[3937](https://github.com/mozilla-mobile/focus-android/issues/3937)|7.0.13-arm| 385 | |381|Firefox|[3085](https://github.com/mozilla-mobile/focus-android/issues/3085)| | 386 | |382|Firefox|[4740](https://github.com/mozilla-mobile/focus-android/issues/4740)|8.0.6-arm| 387 | |383|Firefox|[3124](https://github.com/mozilla-mobile/focus-android/issues/3124)| | 388 | |384|Firefox|[4341](https://github.com/mozilla-mobile/focus-android/issues/4341)|8.7.2-arm| 389 | |385|Firefox|[3254](https://github.com/mozilla-mobile/focus-android/issues/3254)|7.0-RC1| 390 | |386|Firefox|[4065](https://github.com/mozilla-mobile/focus-android/issues/4065)|7.0.13-arm| 391 | |387|Firefox|[4168](https://github.com/mozilla-mobile/focus-android/issues/4168)| | 392 | |388|Firefox|[4877](https://github.com/mozilla-mobile/focus-android/issues/4877)| | 393 | |389|Firefox|[3287](https://github.com/mozilla-mobile/focus-android/issues/3287)|7.0beta2| 394 | |390|Firefox|[3675](https://github.com/mozilla-mobile/focus-android/issues/3675)| | 395 | |391|Firefox|[4739](https://github.com/mozilla-mobile/focus-android/issues/4739)|8.13.2-arm| 396 | |392|Firefox|[3787](https://github.com/mozilla-mobile/focus-android/issues/3787)| | 397 | |393|Firefox|[3121](https://github.com/mozilla-mobile/focus-android/issues/3121)|7.0beta1| 398 | |394|Firefox|[3547](https://github.com/mozilla-mobile/focus-android/issues/3547)|7.0beta1| 399 | |395|Firefox|[4876](https://github.com/mozilla-mobile/focus-android/issues/4876)|6.1.1| 400 | |396|Firefox|[3664](https://github.com/mozilla-mobile/focus-android/issues/3664)| | 401 | |397|Firefox|[3233](https://github.com/mozilla-mobile/focus-android/issues/3233)|7.0beta2| 402 | |398|Firefox|[3304](https://github.com/mozilla-mobile/focus-android/issues/3304)|7.0beta2| 403 | |399|Firefox|[3297](https://github.com/mozilla-mobile/focus-android/issues/3297)|7.0beta1| 404 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Publication 2 | 3 | [1] "An Empirical Study of Functional Bugs in Android Apps" by Yiheng Xiong, Mengqian Xu, Ting Su, Jingling Sun, Jue Wang, He Wen, Geguang Pu, Jifeng He and Zhendong Su. In Proceedings of the 32nd ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA 2023). 4 | ``` 5 | @inproceedings{functionalbugs2023, 6 | title={An Empirical Study of Functional Bugs in Android Apps}, 7 | author={Xiong, Yiheng and Xu, Mengqian and Su, Ting and Sun, Jingling and Wang, Jue and Wen, He and Pu, Geguang and He, Jifeng and Su, Zhendong}, 8 | booktitle={Proceedings of the 32nd ACM SIGSOFT International Symposium on Software Testing and Analysis}, 9 | pages={1319--1331}, 10 | year={2023}, 11 | doi = {10.1145/3597926.3598138}, 12 | series = {ISSTA 2023} 13 | } 14 | ``` 15 | 16 | *You can find more about our work on testing/analyzing Android apps at this [website](https://tingsu.github.io/files/mobile-app-analysis.html)*. 17 | 18 | # Replication Package 19 | This repository contains all the artifacts (including the dataset and the tool Regdroid) in our study. 20 | 21 | ## Directory Structure 22 | 23 | home 24 | | 25 | | --- Dataset: The bug list of 399 bug reports 26 | | --- RegDroid: The source code of RegDroid 27 | | 28 | | --- start.py: The entry of RegDroid, which accepts the parameters 29 | | --- regdroid.py The main module of RegDroid 30 | | --- executor.py The execution module of RegDroid 31 | ## Dataset 32 | 33 | view the bug list in [Dataset](Dataset) 34 | or Download all the data from [this link](https://1drv.ms/u/s!AqF-Z1v5QCuxgir6NaCpCtUC7ouX?e=PD1jVs) 35 | 36 | 37 | ## RegDroid 38 | 39 | ### Getting Started 40 | 41 | #### Download 42 | 43 | ``` 44 | git clone https://github.com/Android-Functional-bugs-study/home.git 45 | ``` 46 | 47 | #### Requirements 48 | 49 | - Android SDK: API26+ 50 | - python 3.8 51 | - We use some libraries (e.g., uiautomator2, androguard, cv2) provided by python, you can add them as prompted, for example: 52 | 53 | ``` 54 | pips install uiautomator2 55 | ``` 56 | 57 | #### Setting up 58 | 59 | You can create an emulator before running RegDroid. See [this link](https://stackoverflow.com/questions/43275238/how-to-set-system-images-path-when-creating-an-android-avd) for how to create avd using [avdmanager](https://developer.android.com/studio/command-line/avdmanager). 60 | The following sample command will help you create an emulator, which will help you to start using RegDroid quickly: 61 | 62 | ``` 63 | sdkmanager "system-images;android-26;google_apis;x86" 64 | avdmanager create avd --force --name Android8.0 --package 'system-images;android-26;google_apis;x86' --abi google_apis/x86 --sdcard 512M --device "pixel_xl" 65 | ``` 66 | 67 | Next, you can start two identical emulators and assign their port numbers with the following commands: 68 | 69 | ``` 70 | emulator -avd Android8.0 -read-only -port 5554 71 | emulator -avd Android8.0 -read-only -port 5556 72 | ``` 73 | 74 | #### Run 75 | 76 | If you have downloaded our project and configured the environment, you only need to enter ``download_path/tool/RegDroid`` to execute our sample app with the following command: 77 | 78 | ``` 79 | python3 start.py -app_path ./App/AmazeFileManager-3.7.1.apk -emulator_path path_to_emulator -app_path ./App/AmazeFileManager-3.7.2.apk -append_device emulator-5554 -append_device emulator-5556 -output 3.7.1-3.7.2 -testcase_count 1 -event_num 20 80 | ``` 81 | 82 | Here, 83 | 84 | ``-app_path``: the file path of APK 85 | 86 | ``-emulator_path ``: the path to the emulator 87 | 88 | ``-append_device``: the serial number of devices used in the test, which can be obtained by executing "adb devices" in the terminal. 89 | 90 | ``-output``: the output directory under /Tool/Output/app_package_name/ 91 | 92 | ``-testcase_count`` The number of rounds that you want to test. 93 | 94 | ``-event_num`` The number of events in per round of test. 95 | 96 | #### Detailed Description 97 | 98 | ##### Description of Output Files 99 | 100 | * The output path of the tool is in ``/Too/Output/``. 101 | * The result files of each app are classified and stored in ``/Too/Output/``. 102 | * Open the folder of an app, and you will see the result files of each strategy for this app are stored by category. 103 | * Open the folder corresponding regression test result, and you will see an ``error_realtime.txt`` file, a ``wrong_realtime.txt`` file, and many numbered folders corresponding to each round of test results. 104 | * Open a numbered folder, and you can see a ``read_trace.txt`` file, a ``trace.txt`` file, an ``i_trace.html`` file, and a folder named ``screen``. 105 | * Open the ``screen`` folder, and you can see the screenshot of each step and the corresponding interface layout information file. 106 | * Next, I will introduce the content and use of each file. 107 | 108 | ###### error_realtime.txt 109 | 110 | This file records the sequences that trigger the defects, which start with ``Start::x::run_count::y`` (x means the x-th error and Y means the error was captured during the y-th round of execution), and end with ``End::`` 111 | 112 | ###### wrong_realtime.txt 113 | 114 | This file records the sequences that trigger the suspected defects. 115 | 116 | ###### read_trace.txt 117 | 118 | This file records the execution sequence of RegDroid, which is easy for RegDroid users to read. 119 | 120 | ###### trace.txt 121 | 122 | This file records the execution sequence of RegDroid, which can be read and replayed by RegDroid. 123 | 124 | ###### i_trace.html 125 | 126 | This file records the sequence of screenshots after each step, which is arranged horizontally. The events executed at each step are marked on the screenshot. After opening the file in the browser, there is a drag bar at the bottom, which can drag horizontally to view the whole sequence. When the error is captured, the screenshot is marked with a red frame. When the two interfaces are different, the screen capture is marked with a yellow frame. 127 | -------------------------------------------------------------------------------- /RegDroid/App/AmazeFileManager-3.7.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/App/AmazeFileManager-3.7.1.apk -------------------------------------------------------------------------------- /RegDroid/App/AmazeFileManager-3.7.2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/App/AmazeFileManager-3.7.2.apk -------------------------------------------------------------------------------- /RegDroid/Document/Android_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/Android_logo.jpg -------------------------------------------------------------------------------- /RegDroid/Document/Android_robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/Android_robot.png -------------------------------------------------------------------------------- /RegDroid/Document/Heartbeat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/Heartbeat.mp3 -------------------------------------------------------------------------------- /RegDroid/Document/intermission.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/intermission.mp3 -------------------------------------------------------------------------------- /RegDroid/Document/sample.3gp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/sample.3gp -------------------------------------------------------------------------------- /RegDroid/Document/sample_3GPP.3gp.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/sample_3GPP.3gp.zip -------------------------------------------------------------------------------- /RegDroid/Document/sample_iPod.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/sample_iPod.m4v -------------------------------------------------------------------------------- /RegDroid/Document/sample_mpeg4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/sample_mpeg4.mp4 -------------------------------------------------------------------------------- /RegDroid/Document/sample_sorenson.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-Functional-bugs-study/home/6cd5142ce2743c70bd68a5fccb879cf8ec3788a3/RegDroid/Document/sample_sorenson.mov -------------------------------------------------------------------------------- /RegDroid/app.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | import os 4 | 5 | from androguard.core.bytecodes.apk import APK 6 | 7 | 8 | class App(object): 9 | 10 | def __init__(self, app_path, root_path, app_name): 11 | print("Root path:", root_path) 12 | assert app_path is not None 13 | self.logger = logging.getLogger(self.__class__.__name__) 14 | self.app_path = app_path 15 | 16 | self.apk = APK(self.app_path) 17 | self.package_name = self.apk.get_package() 18 | self.main_activity = self.apk.get_main_activity() 19 | self.permissions = self.apk.get_permissions() 20 | self.activities = self.apk.get_activities() 21 | if app_name is not None: 22 | self.app_name = app_name 23 | else: 24 | self.app_name = self.apk.get_app_name() 25 | print("Main activity:", self.main_activity) 26 | print("Package name:", self.package_name) 27 | self.output_path = os.path.join(root_path, self.package_name) 28 | 29 | def get_package_name(self): 30 | return self.package_name 31 | -------------------------------------------------------------------------------- /RegDroid/checker.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import os 4 | import re 5 | 6 | from injector import Injector 7 | from utils import Utils 8 | 9 | 10 | class Checker(object): 11 | 12 | def __init__(self,devices,app,strategy_list,emulator_path,android_system,root_path,resource_path,testcase_count,event_num,timeout,setting_random_denominator,rest_interval,choice): 13 | 14 | self.timeout = timeout 15 | self.app = app 16 | self.devices = devices 17 | self.guest_devices = self.devices[1:] 18 | self.emulator_path = emulator_path 19 | self.android_system = android_system 20 | self.root_path = root_path 21 | self.resource_path = resource_path 22 | self.strategy_list = strategy_list 23 | self.testcase_count = testcase_count 24 | self.event_num = event_num 25 | self.setting_random_denominator = setting_random_denominator 26 | self.rest_interval = rest_interval 27 | self.injector = Injector(devices=devices, 28 | app=app, 29 | strategy_list=strategy_list, 30 | emulator_path=emulator_path, 31 | android_system=android_system, 32 | root_path=root_path, 33 | resource_path=resource_path, 34 | testcase_count=testcase_count, 35 | event_num=event_num, 36 | timeout=timeout, 37 | setting_random_denominator=setting_random_denominator, 38 | rest_interval=rest_interval, 39 | choice=choice) 40 | self.utils = Utils(devices=devices) 41 | 42 | def check_time(self,path): 43 | with open(f"{path}/time_bug.txt", 'w', encoding='utf-8') as fw: 44 | time_bugs=[] 45 | for root, dirs, files in os.walk(path, topdown=False): 46 | for name in files: 47 | if not ("5556" in name and "xml" in name): 48 | continue 49 | 50 | xml_path = os.path.join(root, name) 51 | with open(xml_path,'r',encoding='utf-8') as f: 52 | lines = f.readlines() 53 | 54 | for line in lines: 55 | if not("text=\"" in line and self.app.package_name in line): 56 | continue 57 | 58 | text = line[line.find("text=\"")+6:] 59 | text = text[:text.find("\"")] 60 | pattern = re.search("^(1[0-2]|0?[1-9]|0):([0-5]?[0-9])$", text) 61 | if pattern is not None and text not in time_bugs: 62 | time_bugs.append(text) 63 | fw.write(xml_path+'\n') 64 | fw.write(text+'\n\n') 65 | fw.flush() 66 | 67 | def check_language(self,path): 68 | with open(f"{path}/language_bug.txt", 'w', encoding='utf-8') as fw: 69 | language_bugs = [] 70 | for root, dirs, files in os.walk(path, topdown=False): 71 | for name in files: 72 | if not ("5556" in name and "xml" in name): 73 | continue 74 | 75 | xml_path = os.path.join(root, name) 76 | with open(xml_path,'r',encoding='utf-8') as f: 77 | lines = f.readlines() 78 | 79 | for line in lines: 80 | if not ("text=\"" in line and self.app.package_name in line): 81 | continue 82 | 83 | text = line[line.find("text=\"")+6:] 84 | text = text[:text.find("\"")] 85 | import langid 86 | language_classify = langid.classify(text) 87 | if "en" in language_classify[0] and text != "" and text not in language_bugs: 88 | print(text) 89 | language_bugs.append(text) 90 | fw.write(xml_path+'\n') 91 | fw.write(text+'\n\n') 92 | fw.flush() 93 | 94 | def check_keyboard(self): 95 | for device in self.devices: 96 | # device.use.set_clipboard('text', 'label') 97 | lines = device.state.lines 98 | if "com.sohu.inputmethod.sogou:id/imeview_keyboard" in lines or "com.baidu.input_huawei" in lines or "com.google.android.inputmethod.latin" in lines: 99 | print("close_keyboard") 100 | device.close_keyboard() 101 | 102 | def check_samestate(self): 103 | if self.devices[0].state.same_but_not_language(self.devices[1].state): 104 | return True 105 | return False 106 | 107 | def check_foreground(self): 108 | packagelist=[self.app.package_name,"com.google.android.permissioncontroller","com.android.packageinstaller","com.android.permissioncontroller"] 109 | lines = self.devices[0].use.dump_hierarchy() 110 | return any(package in lines for package in packagelist) 111 | 112 | def check_start(self,times,strategy): 113 | try: 114 | if self.app.package_name == "fr.free.nrw.commons" and times==0: 115 | print("Feimao start 0") 116 | time.sleep(self.rest_interval*20) 117 | for device in self.devices: 118 | device.use(scrollable=True).scroll.horiz.toEnd(max_swipes=50) 119 | device.use(text="YES!").wait(timeout=10.0) 120 | device.use(text="YES!",instance=0).click() 121 | device.use(text="Skip").wait(timeout=10.0) 122 | device.use(text="Skip",instance=0).click() 123 | device.use(text="YES").wait(timeout=10.0) 124 | device.use(text="YES",instance=0).click() 125 | print("Feimao start end 0") 126 | else: 127 | print("Other start") 128 | time.sleep(self.rest_interval*2) 129 | except Exception: 130 | print("") 131 | 132 | def check_setting_request(self): 133 | flag=False 134 | for device in self.guest_devices: 135 | if device.strategy == "permssion_lazy_1": 136 | if self.check_permission_request(device): 137 | self.devices[1].permission=True 138 | return True 139 | elif device.strategy == "network_lazy_1": 140 | if self.check_network_request(device): 141 | return True 142 | elif device.strategy == "location_lazy_1": 143 | if self.check_location_request(device): 144 | return True 145 | return flag 146 | 147 | def check_location_request(self,device): 148 | Flag = False 149 | gpslist = ["location","gps","位置","加载","connection"] 150 | requestlist = ["unavailable","try again","开启","检查","失败","重新","重试","not available","not enabled"] 151 | lines2 = device.use.dump_hierarchy() 152 | if (self.containsAny(lines2.lower(), gpslist) and self.containsAny(lines2.lower(), requestlist)): 153 | print("Allow location") 154 | if device.use(text="SETTINGS").count > 0: 155 | device.use(text="SETTINGS").click() 156 | device.use(text="OFF").wait() 157 | device.use(text="OFF").click() 158 | device.use.press("back") 159 | self.devices[1].gps_state = True 160 | else: 161 | self.injector.location_lazy_1() 162 | Flag = True 163 | return Flag 164 | 165 | def check_notification_request(self,device): 166 | Flag = False 167 | notificationlist = ["通知"] 168 | requestlist = ["unavailable","try again","开启"] 169 | lines2 = device.use.dump_hierarchy() 170 | if (self.containsAny(lines2.lower(), notificationlist) and self.containsAny(lines2.lower(), requestlist)): 171 | print("Allow notification") 172 | if device.use(text="忽略").count > 0: 173 | device.use(text="忽略").click() 174 | Flag = True 175 | return Flag 176 | 177 | def check_network_request(self,device): 178 | Flag = False 179 | wifilist = ["wifi","network","网络","加载","connection"] 180 | requestlist = ["unavailable","try again","开启","检查","失败","重新","重试","not available"] 181 | lines2 = device.use.dump_hierarchy() 182 | if (self.containsAny(lines2.lower(), wifilist) and self.containsAny(lines2.lower(), requestlist)): 183 | print("Allow network") 184 | self.injector.network_lazy_1() 185 | for device in self.devices: 186 | # if device.use(scrollable=True,packageName=self.app.package_name).count>0: 187 | # device.use(scrollable=True,packageName=self.app.package_name).scroll.vert.backward(steps=5) 188 | # device.use(scrollable=True,packageName=self.app.package_name).scroll.vert.forward(steps=5) 189 | if device.use(textContains="重试").count>0: 190 | device.use(textContains="重试").click() 191 | time.sleep(self.rest_interval*1) 192 | Flag = True 193 | return Flag 194 | 195 | def check_permission_request(self,device): 196 | Flag = False 197 | while device.use(className="android.widget.Button",packageName="com.google.android.permissioncontroller").count > 0: 198 | print("Allow permission:permissioncontroller") 199 | device.use(className="android.widget.Button",packageName="com.google.android.permissioncontroller").click() 200 | time.sleep(self.rest_interval*1) 201 | Flag = True 202 | while device.use(className="android.widget.Button",packageName="com.android.packageinstaller").count > 0: 203 | print("Allow permission:packageinstaller") 204 | device.use(className="android.widget.Button",packageName="com.android.packageinstaller",instance=1).click() 205 | time.sleep(self.rest_interval*1) 206 | Flag = True 207 | while device.use(className="android.widget.Button",packageName="com.google.android.packageinstaller").count > 0: 208 | print("Allow permission:packageinstaller") 209 | device.use(className="android.widget.Button",packageName="com.google.android.packageinstaller",instance=1).click() 210 | time.sleep(self.rest_interval*1) 211 | Flag = True 212 | 213 | permissionlist = ["权限","permission","authoriz"] 214 | requestlist = ["需要","开启","allow","允许","request","ensure","require","检查"] 215 | speciallist = ["migrate"] 216 | lines2 = device.use.dump_hierarchy() 217 | if device.use(packageName="com.android.settings",text="Permissions").count>0: 218 | device.use(packageName="com.android.settings",text="Permissions").click() 219 | time.sleep(self.rest_interval*1) 220 | while device.use(text="OFF",className="android.widget.Switch").count>0: 221 | device.use(text="OFF",className="android.widget.Switch").click() 222 | while device.use(text="我知道了",resourceId="com.ss.android.ugc.aweme:id/e9y").count>0: 223 | device.use(text="我知道了",resourceId="com.ss.android.ugc.aweme:id/e9y").click() 224 | while device.use(packageName=self.app.package_name).count<1: 225 | device.use.press("back") 226 | time.sleep(self.rest_interval*1) 227 | elif device.use(packageName="com.android.settings",text="APPS").count>0: 228 | device.use(scrollable=True,instance = 0).scroll.to(text=self.app.app_name) 229 | device.use(text=self.app.app_name).click() 230 | device.use(text="Battery").wait(timeout=3.0) 231 | device.use(scrollable=True,instance = 0).scroll.to(text="Permissions") 232 | self.check_permission_request(device) 233 | elif (self.containsAny(lines2.lower(), permissionlist) and self.containsAny(lines2.lower(), requestlist)) or self.containsAny(lines2.lower(), speciallist): 234 | print("Allow permission") 235 | again_flag=1 236 | if device.use(text="允许").count > 0: 237 | device.use(text="允许").click() 238 | again_flag=0 239 | elif device.use(text="设置").count > 0: 240 | device.use(text="设置").click() 241 | again_flag=0 242 | elif device.use(text="去设置").count > 0: 243 | device.use(text="去设置").click() 244 | again_flag=0 245 | elif device.use(text="确定").count > 0: 246 | device.use(text="确定").click() 247 | again_flag=0 248 | elif device.use(text="Settings").count > 0: 249 | device.use(text="Settings").click() 250 | again_flag=0 251 | elif device.use(text="Allow storage permissions in order to fully enjoy WeChat features.").count > 0: 252 | device.use(textContains="Allow").click() 253 | again_flag=0 254 | elif device.use(className="android.widget.Button").count > 0: 255 | device.use(className="android.widget.Button").click() 256 | again_flag=0 257 | if again_flag==0: 258 | time.sleep(self.rest_interval*3) 259 | self.check_permission_request(device) 260 | Flag = True 261 | return Flag 262 | 263 | def containsAny(self, seq, aset): 264 | return any((i in seq for i in aset)) 265 | 266 | def check_loading(self): 267 | wait_time = 0 268 | for device in self.devices: 269 | if (device.use(className="android.widget.ProgressBar").count > 0 and 270 | wait_time < self.rest_interval * 5): 271 | time.sleep(self.rest_interval * 5) 272 | wait_time = wait_time + self.rest_interval * 5 273 | print("wait load") 274 | elif wait_time > self.rest_interval * 20 or wait_time == self.rest_interval * 20: 275 | print("so long wait") 276 | return wait_time 277 | 278 | def check_crash(self): 279 | for device in self.devices: 280 | device.last_crash_logcat = device.crash_logcat 281 | f_crash = open( 282 | f"{self.root_path}/{device.device_serial}_logcat.txt", 283 | 'r', 284 | encoding='utf-8', 285 | ) 286 | device.crash_logcat = f_crash.read() 287 | if device.use(text="Close app").count>0: 288 | device.use(text="Close app").click() 289 | if device.crash_logcat != device.last_crash_logcat: 290 | return device.crash_logcat[len(device.last_crash_logcat):-1] + '\n' 291 | return None 292 | -------------------------------------------------------------------------------- /RegDroid/device.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | from threading import Thread 5 | 6 | import uiautomator2 as u2 7 | 8 | 9 | class MyThread(Thread): 10 | def __init__(self, func, args): 11 | super(MyThread, self).__init__() 12 | self.func = func 13 | self.args = args 14 | 15 | def run(self): 16 | self.result = self.func(*self.args) 17 | 18 | def get_result(self): 19 | try: 20 | return self.result 21 | except Exception: 22 | return None 23 | 24 | 25 | class Device(object): 26 | """ 27 | Record the information of the device 28 | """ 29 | 30 | def __init__( 31 | self, device_num=None, device_serial=None, is_emulator=True, rest_interval=None 32 | ): 33 | self.device_num = device_num 34 | self.device_serial = device_serial 35 | self.is_emulator = is_emulator 36 | self.use = None 37 | self.state = None 38 | self.last_state = None 39 | self.strategy = "screen" 40 | self.crash_logcat = "" 41 | self.last_crash_logcat = "" 42 | self.language = "en" 43 | self.rest_interval = rest_interval 44 | self.wifi_state = True 45 | self.gps_state = True 46 | self.sound_state = True 47 | self.battery_state = False 48 | self.game_mode = True 49 | self.blue_light = False 50 | self.notification = True 51 | self.permission = True 52 | self.hourformat = "12h" 53 | 54 | def set_strategy(self, strategy): 55 | self.strategy = strategy 56 | self.error_num = 0 57 | self.wrong_num = 0 58 | 59 | def set_thread(self, execute_event, args): 60 | if execute_event is not None: 61 | self.thread = MyThread(execute_event, args) 62 | else: 63 | self.thread = None 64 | 65 | def restart(self, emulator_path, emulator_name): 66 | port = self.device_serial[self.device_serial.find("-") + 1 :] 67 | print(f"----{port}") 68 | subprocess.run( 69 | ["adb", "-s", self.device_serial, "emu", "kill"], stdout=subprocess.PIPE 70 | ) 71 | time.sleep(self.rest_interval * 20) 72 | os.popen(f"{emulator_path} -avd {emulator_name} -read-only -port {port}") 73 | print("wait-for-device") 74 | subprocess.run( 75 | ["adb", "-s", self.device_serial, "wait-for-device"], stdout=subprocess.PIPE 76 | ) 77 | print("wait-for-device end") 78 | time.sleep(self.rest_interval * 10) 79 | 80 | def make_strategy(self, root_path): 81 | if not os.path.isdir(f"{root_path}strategy_{self.strategy}/"): 82 | os.makedirs(f"{root_path}strategy_{self.strategy}/") 83 | self.f_error = open( 84 | f"{root_path}strategy_{self.strategy}/error_realtime.txt", 85 | 'w', 86 | encoding='utf-8', 87 | ) 88 | self.f_wrong = open( 89 | f"{root_path}strategy_{self.strategy}/wrong_realtime.txt", 90 | 'w', 91 | encoding='utf-8', 92 | ) 93 | 94 | def make_strategy_runcount(self, run_count, root_path): 95 | self.path = f"{root_path}strategy_{self.strategy}/{str(run_count)}/" 96 | if not os.path.isdir(self.path): 97 | os.makedirs(self.path) 98 | if not os.path.isdir(f"{self.path}screen/"): 99 | os.makedirs(f"{self.path}screen/") 100 | self.f_read_trace = open(f'{self.path}/read_trace.txt', 'w', encoding='utf-8') 101 | self.f_trace = open(f'{self.path}/trace.txt', 'w', encoding='utf-8') 102 | 103 | self.error_event_lists = [] 104 | self.wrong_event_lists = [] 105 | self.wrong_flag = True 106 | 107 | def connect(self): 108 | self.use = u2.connect_usb(self.device_serial) 109 | self.use.implicitly_wait(5.0) 110 | 111 | def install_app(self, app): 112 | print(app) 113 | subprocess.run( 114 | ["adb", "-s", self.device_serial, "install", app], stdout=subprocess.PIPE 115 | ) 116 | 117 | def initialization(self): 118 | self.use.set_orientation("n") 119 | 120 | def initial_setting(self): 121 | print("initial setting") 122 | 123 | def screenshot_and_getstate(self, path, event_count): 124 | self.screenshot_path = ( 125 | path + str(event_count) + '_' + self.device_serial + '.png' 126 | ) 127 | self.use.screenshot(path + str(event_count) + '_' + self.device_serial + '.png') 128 | xml = self.use.dump_hierarchy() 129 | f = open( 130 | path + str(event_count) + '_' + self.device_serial + '.xml', 131 | 'w', 132 | encoding='utf-8', 133 | ) 134 | f.write(xml) 135 | with open( 136 | path + str(event_count) + '_' + self.device_serial + '.xml', 137 | 'r', 138 | encoding='utf-8', 139 | ) as f: 140 | lines = f.readlines() 141 | return lines 142 | 143 | def update_state(self, state): 144 | self.last_state = self.state 145 | self.state = state 146 | 147 | def stop_app(self, app): 148 | self.use.app_stop(app.package_name) 149 | 150 | def clear_app(self, app, is_login_app): 151 | if is_login_app == 0: 152 | self.use.app_stop(app.package_name) 153 | else: 154 | self.use.app_clear(app.package_name) 155 | 156 | def start_app(self, app): 157 | self.use.app_start(app.package_name) 158 | subprocess.run( 159 | [ 160 | "adb", 161 | "-s", 162 | self.device_serial, 163 | "shell", 164 | "am", 165 | "start", 166 | "-n", 167 | f"{app.package_name}/{app.main_activity}", 168 | ], 169 | stdout=subprocess.PIPE, 170 | ) 171 | # self.use.app_wait(app.package_name, front=True, timeout=2.0) 172 | return True 173 | 174 | def click(self, view, strategy_list): 175 | try: 176 | if self.strategy != "language": 177 | if view.description != "": 178 | self.use( 179 | description=view.description, packageName=view.package 180 | ).click() 181 | return "description" 182 | elif view.text != "": 183 | self.use(text=view.text, packageName=view.package).click() 184 | return "text" 185 | elif view.instance == 0: 186 | self.use( 187 | className=view.className, 188 | resourceId=view.resourceId, 189 | packageName=view.package, 190 | ).click() 191 | return "classNameresourceId" 192 | else: 193 | self.use.click(view.x, view.y) 194 | return "xy" 195 | elif view.instance == 0: 196 | self.use( 197 | className=view.className, 198 | resourceId=view.resourceId, 199 | packageName=view.package, 200 | ).click() 201 | return "classNameresourceId" 202 | else: 203 | self.use.click(view.x, view.y) 204 | return "xy" 205 | except: 206 | self.use.click(view.x, view.y) 207 | return "xy" 208 | 209 | def _click(self, view, text): 210 | try: 211 | if text is not None and view.text == text: 212 | self.use(text=view.text, packageName=view.package).click() 213 | return "text" 214 | if view.description != "": 215 | self.use(description=view.description, packageName=view.package).click() 216 | return "description" 217 | elif view.instance == 0: 218 | self.use( 219 | className=view.className, 220 | resourceId=view.resourceId, 221 | packageName=view.package, 222 | ).click() 223 | return "classNameresourceId" 224 | else: 225 | self.use.click(view.x, view.y) 226 | return "xy" 227 | except: 228 | self.use.click(view.x, view.y) 229 | return "xy" 230 | 231 | def longclick(self, view, strategy_list): 232 | try: 233 | if self.strategy != "language": 234 | if view.description != "": 235 | self.use( 236 | description=view.description, packageName=view.package 237 | ).long_click(duration=1.0) 238 | return 239 | elif view.text != "": 240 | self.use(text=view.text, packageName=view.package).long_click( 241 | duration=1.0 242 | ) 243 | return 244 | elif view.instance == 0: 245 | self.use( 246 | className=view.className, 247 | resourceId=view.resourceId, 248 | packageName=view.package, 249 | ).long_click(duration=1.0) 250 | else: 251 | self.use.long_click(view.x, view.y, duration=1.0) 252 | elif view.instance == 0: 253 | self.use( 254 | className=view.className, 255 | resourceId=view.resourceId, 256 | packageName=view.package, 257 | ).long_click(duration=1.0) 258 | else: 259 | self.use.long_click(view.x, view.y, duration=1.0) 260 | except: 261 | self.use.long_click(view.x, view.y, duration=1.0) 262 | # print("x:"+str(view.x)+",y:"+str(view.y)) 263 | return 264 | 265 | def edit(self, view, strategy_list, text): 266 | if "language" not in strategy_list: 267 | self.use( 268 | className=view.className, 269 | resourceId=view.resourceId, 270 | packageName=view.package, 271 | ).set_text(text) 272 | else: 273 | self.use( 274 | className=view.className, 275 | resourceId=view.resourceId, 276 | packageName=view.package, 277 | ).set_text(text) 278 | 279 | def scroll(self, view, strategy_list): 280 | if view.action == "scroll_backward": 281 | self.use( 282 | className=view.className, 283 | resourceId=view.resourceId, 284 | packageName=view.package, 285 | ).scroll.vert.backward(steps=100) 286 | elif view.action == "scroll_forward": 287 | self.use( 288 | className=view.className, 289 | resourceId=view.resourceId, 290 | packageName=view.package, 291 | ).scroll.vert.forward(steps=100) 292 | elif view.action == "scroll_right": 293 | self.use( 294 | className=view.className, 295 | resourceId=view.resourceId, 296 | packageName=view.package, 297 | ).scroll.horiz.toEnd(max_swipes=10) 298 | elif view.action == "scroll_left": 299 | self.use( 300 | className=view.className, 301 | resourceId=view.resourceId, 302 | packageName=view.package, 303 | ).scroll.horiz.toBeginning(max_swipes=10) 304 | 305 | def close_keyboard(self): 306 | subprocess.run( 307 | ["adb", "-s", self.device_serial, "shell", "input", "keyevent", "111"], 308 | stdout=subprocess.PIPE, 309 | ) 310 | 311 | def add_file(self, resource_path, resource, path): 312 | subprocess.run( 313 | ["adb", "-s", self.device_serial, "logcat", "-c"], stdout=subprocess.PIPE 314 | ) 315 | subprocess.run( 316 | [ 317 | "adb", 318 | "-s", 319 | self.device_serial, 320 | "push", 321 | f"{resource_path}/{resource}", 322 | path, 323 | ], 324 | stdout=subprocess.PIPE, 325 | ) 326 | 327 | def log_crash(self, path): 328 | os.popen(f"adb -s {self.device_serial} logcat -b crash >{path}") 329 | 330 | def mkdir(self, path): 331 | subprocess.run( 332 | ["adb", "-s", self.device_serial, "shell", "mkdir", path], 333 | stdout=subprocess.PIPE, 334 | ) 335 | 336 | def disable_keyboard(self): 337 | self.use.set_fastinput_ime(True) 338 | -------------------------------------------------------------------------------- /RegDroid/event.py: -------------------------------------------------------------------------------- 1 | 2 | import traceback 3 | 4 | 5 | class Event(object): 6 | 7 | def __init__(self, view, action, device, event_count): 8 | self.view = view 9 | self.action = action 10 | self.device = device 11 | self.text = "None" 12 | self.count = 0 13 | self.event_count = event_count 14 | 15 | def set_device(self, device): 16 | self.device = device 17 | 18 | def set_text(self, text): 19 | self.text = text 20 | 21 | def set_count(self, count): 22 | self.count = count 23 | 24 | def print_event(self): 25 | try: 26 | print("Event start=============================") 27 | print(f"Event_count:{str(self.event_count)}") 28 | if self.view is not None: 29 | print(f"View_text:{self.view.line}") 30 | print(f"Action:{self.action}") 31 | print(f"Device:{self.device.device_serial}") 32 | if self.text is not None: 33 | print(f"Text:{self.text}") 34 | print("Event end=============================") 35 | except Exception: 36 | traceback.print_exc() 37 | -------------------------------------------------------------------------------- /RegDroid/executor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from injector import Injector 5 | from policy import RandomPolicy 6 | from state import State 7 | from checker import Checker 8 | from event import Event 9 | from view import View 10 | from utils import Utils 11 | 12 | 13 | class Executor(object): 14 | def __init__( 15 | self, 16 | devices, 17 | app, 18 | app_path, 19 | strategy_list, 20 | pro_click, 21 | pro_longclick, 22 | pro_scroll, 23 | pro_home, 24 | pro_edit, 25 | pro_naturalscreen, 26 | pro_leftscreen, 27 | pro_back, 28 | pro_splitscreen, 29 | emulator_path, 30 | android_system, 31 | root_path, 32 | resource_path, 33 | testcase_count, 34 | start_testcase_count, 35 | event_num, 36 | timeout, 37 | policy_name, 38 | setting_random_denominator, 39 | serial_or_parallel, 40 | emulator_name, 41 | is_login_app, 42 | rest_interval, 43 | trace_path, 44 | choice, 45 | ): 46 | self.policy_name = policy_name 47 | self.timeout = timeout 48 | self.pro_click = pro_click 49 | self.pro_longclick = pro_longclick 50 | self.pro_scroll = pro_scroll 51 | self.pro_home = pro_home 52 | self.pro_edit = pro_edit 53 | self.pro_naturalscreen = pro_naturalscreen 54 | self.pro_leftscreen = pro_leftscreen 55 | self.pro_back = pro_back 56 | self.pro_splitscreen = pro_splitscreen 57 | self.app = app 58 | self.app_path = app_path 59 | self.devices = devices 60 | self.emulator_path = emulator_path 61 | self.android_system = android_system 62 | self.resource_path = resource_path 63 | self.strategy_list = strategy_list 64 | self.testcase_count = testcase_count 65 | self.start_testcase_count = start_testcase_count 66 | self.event_num = event_num 67 | self.setting_random_denominator = setting_random_denominator 68 | self.root_path = root_path 69 | self.policy = self.get_policy() 70 | self.serial_or_parallel = serial_or_parallel 71 | self.emulator_name = emulator_name 72 | self.is_login_app = is_login_app 73 | self.rest_interval = rest_interval 74 | self.guest_devices = self.devices[1:] 75 | self.trace_path = trace_path 76 | self.choice = choice 77 | self.deduplicate_list1 = [] 78 | self.deduplicate_list2 = [] 79 | self.injector = Injector( 80 | devices=devices, 81 | app=app, 82 | strategy_list=strategy_list, 83 | emulator_path=emulator_path, 84 | android_system=android_system, 85 | root_path=root_path, 86 | resource_path=resource_path, 87 | testcase_count=testcase_count, 88 | event_num=event_num, 89 | timeout=timeout, 90 | setting_random_denominator=setting_random_denominator, 91 | rest_interval=rest_interval, 92 | choice=choice, 93 | ) 94 | 95 | self.checker = Checker( 96 | devices=devices, 97 | app=app, 98 | strategy_list=strategy_list, 99 | emulator_path=emulator_path, 100 | android_system=android_system, 101 | root_path=root_path, 102 | resource_path=resource_path, 103 | testcase_count=testcase_count, 104 | event_num=event_num, 105 | timeout=timeout, 106 | setting_random_denominator=setting_random_denominator, 107 | rest_interval=rest_interval, 108 | choice=self.choice, 109 | ) 110 | 111 | self.utils = Utils(devices=devices) 112 | 113 | def get_policy(self): 114 | if self.policy_name == "random": 115 | print("Policy: Random") 116 | policy = RandomPolicy( 117 | self.devices, 118 | self.app, 119 | self.emulator_path, 120 | self.android_system, 121 | self.root_path, 122 | self.pro_click, 123 | self.pro_longclick, 124 | self.pro_scroll, 125 | self.pro_edit, 126 | self.pro_naturalscreen, 127 | self.pro_leftscreen, 128 | self.pro_back, 129 | self.pro_splitscreen, 130 | self.pro_home, 131 | ) 132 | else: 133 | print("No valid input policy specified. Using policy \"none\".") 134 | policy = None 135 | return policy 136 | 137 | def execute_event(self, device, event, num): 138 | try: 139 | have_view_action = ["click", "longclick", "edit", "scroll"] 140 | feature = "" 141 | if event.action in have_view_action and event.view is not None: 142 | if ( 143 | device.use(resourceId=event.view.resourceId).count == 0 144 | or device.use(className=event.view.className).count == 0 145 | ): 146 | lines = device.use.dump_hierarchy() 147 | if ( 148 | event.view.resourceId != "" 149 | and event.view.resourceId not in lines 150 | ) or ( 151 | event.view.className != "" and event.view.className not in lines 152 | ): 153 | return False 154 | 155 | if event.action.startswith("setting_"): 156 | self.injector.replay_setting(event, self.strategy_list) 157 | elif event.action == "check_setting_request": 158 | self.checker.check_setting_request() 159 | elif event.action == "click": 160 | feature = device.click(event.view, self.strategy_list) 161 | elif event.action == "longclick": 162 | device.longclick(event.view, self.strategy_list) 163 | elif event.action == "edit": 164 | device.edit(event.view, self.strategy_list, event.text) 165 | elif event.action == "scroll": 166 | device.scroll(event.view, self.strategy_list) 167 | elif event.action == "back": 168 | device.use.press("back") 169 | elif event.action == "home": 170 | device.use.press("home") 171 | elif event.action == "naturalscreen": 172 | device.use.set_orientation("n") 173 | elif event.action == "leftscreen": 174 | device.use.set_orientation("l") 175 | elif event.action == "start": 176 | device.start_app(self.app) 177 | elif event.action == "stop": 178 | device.stop_app(self.app) 179 | elif event.action == "clear": 180 | device.clear_app(self.app, self.is_login_app) 181 | elif event.action == "restart": 182 | device.restart(self.emulator_path, self.emulator_name) 183 | else: 184 | self.injector.replay_setting(event, self.strategy_list) 185 | 186 | print(device.device_serial + ":" + feature + ":end execute\n") 187 | time.sleep(self.rest_interval * 1) 188 | return True 189 | except Exception as ex: 190 | if num == 0: 191 | print(ex) 192 | return self.execute_event(device, event, 1) 193 | else: 194 | print(ex) 195 | return False 196 | 197 | def replay(self, strategy): 198 | # init 199 | self.injector.init_setting() 200 | action_list = ["click", "long_click", "edit"] 201 | self.devices[1].set_strategy(strategy) 202 | path = os.path.join(self.root_path, f"strategy_{strategy}") 203 | self.error_path = os.path.join(path, "error_replay") 204 | self.utils.create_dir(self.error_path) 205 | self.f_replay_record = open( 206 | os.path.join(path, "error_replay.txt"), 'w', encoding='utf-8' 207 | ) 208 | self.error_event_lists = [] 209 | if not os.path.exists(os.path.join(path, "error_realtime.txt")): 210 | print("You should run first before replaying!") 211 | return 212 | self.f_replay = open( 213 | os.path.join(path, "error_realtime.txt"), 'r', encoding='utf-8' 214 | ) 215 | lines = self.f_replay.readlines() 216 | 217 | for line in lines: 218 | self.error_event_lists.append(line) 219 | if "Start::" in line: 220 | # init dir for each error 221 | print("Start") 222 | record_flag = False 223 | linelist = line.split("::") 224 | self.utils.create_dir(os.path.join(self.error_path, linelist[1])) 225 | self.screen_path = os.path.join(self.error_path, linelist[1], "screen/") 226 | self.utils.create_dir(self.screen_path) 227 | f_read_trace = open( 228 | os.path.join(self.error_path, linelist[1], "read_trace.txt"), 229 | 'w', 230 | encoding='utf-8', 231 | ) 232 | print(self.screen_path) 233 | elif "End::" in line: 234 | # end 235 | if record_flag is True: 236 | for theline in self.error_event_lists: 237 | self.f_replay_record.write(theline) 238 | self.f_replay_record.flush() 239 | self.error_event_lists = [] 240 | record_flag = False 241 | f_read_trace.close() 242 | self.utils.generate_html( 243 | os.path.join(self.error_path, linelist[1]), 244 | os.path.join(self.error_path, linelist[1]), 245 | linelist[1], 246 | ) 247 | elif line.strip() != "": 248 | print("-----------------------" + '\n' + line) 249 | f_read_trace.write(line) 250 | f_read_trace.flush() 251 | # replay each event 252 | crash_info = self.checker.check_crash() 253 | if crash_info is not None: 254 | self.f_replay_record.write(crash_info) 255 | self.f_replay_record.flush() 256 | elementlist = line.split("::") 257 | if len(elementlist) < 5: 258 | continue 259 | event = self.get_replay_event(elementlist, line) 260 | event.print_event() 261 | if elementlist[1] == "save_state": 262 | self.save_state( 263 | event.device.device_num, 264 | self.screen_path, 265 | elementlist[0], 266 | self.f_replay_record, 267 | ) 268 | else: 269 | if event.action in action_list: 270 | self.utils.draw_event(event) 271 | args = (event.device, event, 0) 272 | self.devices[event.device.device_num].set_thread( 273 | self.execute_event, args 274 | ) 275 | if event.device.device_num == 1: 276 | self.utils.start_thread() 277 | for device in self.devices: 278 | if device.thread is not None: 279 | success_flag = device.thread.get_result() 280 | if not success_flag and not self.checkduplicate(): 281 | print("write error") 282 | record_flag = True 283 | self.utils.draw_event(event) 284 | self.utils.draw_error_frame() 285 | device.set_thread(None, None) 286 | time.sleep(self.rest_interval * 1) 287 | if ( 288 | "device1" in line 289 | and "save_state" in line 290 | and self.devices[0].state is not None 291 | and self.devices[1].state is not None 292 | and not self.devices[0].state.same(self.devices[1].state) 293 | ): 294 | print("different!") 295 | event = Event(None, "wrong", self.devices[1], elementlist[0]) 296 | self.utils.draw_event(event) 297 | if "::start::" in line: 298 | self.checker.check_start(0, strategy) 299 | 300 | def get_replay_event(self, elementlist, line): 301 | view = None 302 | if elementlist[4].strip() != "None": 303 | view = View(elementlist[4], None, []) 304 | if elementlist[2] == "device0": 305 | event = Event(view, elementlist[1], self.devices[0], elementlist[0]) 306 | elif elementlist[2] == "device1": 307 | event = Event(view, elementlist[1], self.devices[1], elementlist[0]) 308 | else: 309 | print(f"{line} error") 310 | return event 311 | 312 | def start_app(self, event_count): 313 | for device in self.devices: 314 | args = (self.app,) 315 | device.set_thread(device.start_app, args) 316 | self.utils.start_thread() 317 | 318 | for device in self.guest_devices: 319 | self.utils.write_read_event( 320 | "::start::all devices::None::None" + '\n', 321 | event_count, 322 | None, 323 | "all devices", 324 | device.device_num, 325 | ) 326 | event = Event(None, "start", device, event_count) 327 | self.utils.write_event(event, device.device_num, device.f_trace) 328 | self.utils.draw_event(event) 329 | 330 | def clear_app(self, event_count): 331 | for device in self.devices: 332 | device.clear_app(self.app, self.is_login_app) 333 | device.use.set_orientation("n") 334 | 335 | for device in self.guest_devices: 336 | device.error_event_lists.clear() 337 | device.wrong_event_lists.clear() 338 | device.wrong_flag = True 339 | self.utils.write_read_event( 340 | "::clear::all devices::None::None" + '\n', 341 | event_count, 342 | None, 343 | "all devices", 344 | device.device_num, 345 | ) 346 | event = Event(None, "clear", device, event_count) 347 | self.utils.write_event(event, device.device_num, device.f_trace) 348 | self.utils.draw_event(event) 349 | 350 | def clear_and_restart_app(self, event_count, strategy): 351 | for device in self.devices: 352 | device.clear_app(self.app, self.is_login_app) 353 | device.use.set_orientation("n") 354 | for device in self.guest_devices: 355 | device.error_event_lists.clear() 356 | device.wrong_event_lists.clear() 357 | device.wrong_flag = True 358 | event = Event(None, "naturalscreen", device, event_count) 359 | self.utils.write_event(event, device.device_num, device.f_trace) 360 | event = Event(None, "clear", device, event_count) 361 | event_count = self.write_draw_and_save_all(device, event, event_count) 362 | 363 | # if event_count>3: 364 | # event=self.injector.change_setting_after_run(event_count,strategy) 365 | # if event is not None: 366 | # event_count = self.write_draw_and_save_one(event,event_count) 367 | 368 | # check keyboard 369 | self.checker.check_keyboard() 370 | 371 | for device in self.devices: 372 | args = (self.app,) 373 | device.set_thread(device.start_app, args) 374 | self.utils.start_thread() 375 | self.checker.check_start(0, strategy) 376 | 377 | for device in self.guest_devices: 378 | event = Event(None, "start", device, event_count) 379 | event_count = self.write_draw_and_save_all(device, event, event_count) 380 | 381 | event = self.injector.change_setting_before_run(event_count, strategy) 382 | 383 | if event is not None: 384 | event_count = self.write_draw_and_save_one(event, event_count) 385 | return event_count 386 | 387 | def back_to_app(self, event_count, strategy): 388 | for device in self.devices: 389 | device.use.press("back") 390 | print("Back") 391 | time.sleep(self.rest_interval * 1) 392 | if not self.checker.check_foreground(): 393 | for device in self.devices: 394 | device.stop_app(self.app) 395 | args = (self.app,) 396 | device.set_thread(device.start_app, args) 397 | self.utils.start_thread() 398 | self.checker.check_start(1, strategy) 399 | 400 | for device in self.guest_devices: 401 | self.utils.write_read_event( 402 | "::restart::all devices::None::None" + '\n', 403 | event_count, 404 | None, 405 | "all devices", 406 | device.device_num, 407 | ) 408 | event = Event(None, "back", device, event_count) 409 | self.utils.write_event(event, device.device_num, device.f_trace) 410 | event = Event(None, "home", device, event_count) 411 | self.utils.write_event(event, device.device_num, device.f_trace) 412 | event = Event(None, "start", device, event_count) 413 | self.utils.write_event(event, device.device_num, device.f_trace) 414 | self.utils.draw_event(event) 415 | else: 416 | for device in self.guest_devices: 417 | self.utils.write_read_event( 418 | "::back::all devices::None::None" + '\n', 419 | event_count, 420 | None, 421 | "all devices", 422 | device.device_num, 423 | ) 424 | event = Event(None, "back", device, event_count) 425 | self.utils.write_event(event, device.device_num, device.f_trace) 426 | self.utils.draw_event(event) 427 | 428 | def save_all_state(self, event_count): 429 | time.sleep(self.rest_interval * 1) 430 | for device in self.guest_devices: 431 | self.save_state(0, f"{device.path}screen/", event_count, device.f_trace) 432 | self.save_state( 433 | device.device_num, device.path + "screen/", event_count, device.f_trace 434 | ) 435 | event = Event(None, "save_state", device, event_count) 436 | event.set_count(device.device_num) 437 | self.utils.write_event(event, device.device_num, device.f_trace) 438 | return event_count + 1 439 | 440 | def update_all_state(self, event_count): 441 | time.sleep(self.rest_interval * 1) 442 | event_count = event_count - 1 443 | for device in self.guest_devices: 444 | self.update_state(0, f"{device.path}screen/", event_count, device.f_trace) 445 | self.update_state( 446 | device.device_num, 447 | f"{device.path}screen/", 448 | event_count, 449 | device.f_trace, 450 | ) 451 | event_count = event_count + 1 452 | 453 | def save_state(self, device_count, path, event_count, f_trace): 454 | # get and save state of all devices 455 | lines = self.devices[device_count].screenshot_and_getstate(path, event_count) 456 | state = State(lines) 457 | self.devices[device_count].update_state(state) 458 | 459 | def update_state(self, device_count, path, event_count, f_trace): 460 | lines = self.devices[device_count].use.dump_hierarchy().splitlines() 461 | state = State(lines) 462 | self.devices[device_count].update_state(state) 463 | if self.devices[device_count].last_state != self.devices[device_count].state: 464 | self.save_state(device_count, path, event_count, f_trace) 465 | 466 | def restart_devices(self, event_count): 467 | print("restart_devices") 468 | for device in self.devices: 469 | if device.is_emulator == 0: 470 | device.restart(self.emulator_path, self.emulator_name) 471 | # for device in self.guest_devices: 472 | # device.connect() 473 | # device.error_event_lists.clear() 474 | # device.wrong_event_lists.clear() 475 | # device.wrong_flag=True 476 | # self.utils.write_read_event("::restart::all devices::None::None"+'\n',event_count,None,"all devices",device.device_num) 477 | # event = Event(None, "restart", device, event_count) 478 | # self.utils.write_event(event,device.device_num,device.f_trace) 479 | # self.utils.draw_event(event) 480 | 481 | def wait_load(self, event_count): 482 | try: 483 | wait_time = self.checker.check_loading() 484 | if wait_time > 0: 485 | event_count = event_count - 1 486 | event_count = self.save_all_state(event_count) 487 | except Exception: 488 | import traceback 489 | 490 | traceback.print_exc() 491 | self.restart_devices() 492 | 493 | def write_draw_and_save_one(self, event, event_count): 494 | self.utils.write_read_event( 495 | None, event_count, event, "all device", event.device.device_num 496 | ) 497 | self.utils.write_one_device_event( 498 | event, event.device.device_num, event.device.f_trace 499 | ) 500 | self.utils.draw_event(event) 501 | event_count = self.save_all_state(event_count) 502 | return event_count 503 | 504 | def write_draw_and_save_all(self, device, event, event_count): 505 | self.utils.write_read_event( 506 | None, event_count, event, "all device", event.device.device_num 507 | ) 508 | self.utils.write_event(event, device.device_num, device.f_trace) 509 | self.utils.draw_event(event) 510 | event_count = self.save_all_state(event_count) 511 | return event_count 512 | 513 | def read_event(self, line, event_count): 514 | eventlist = line.split("::") 515 | action = eventlist[1] 516 | if eventlist[4] != "None\n": 517 | view = View(eventlist[4], None, []) 518 | event = Event(view, action, self.devices[0], event_count) 519 | else: 520 | event = Event(None, action, self.devices[0], event_count) 521 | return event 522 | 523 | def test(self): 524 | for device in self.guest_devices: 525 | self.checker.check_language(self.root_path + "/strategy_language/") 526 | 527 | def checkduplicate(self): 528 | for screen in self.deduplicate_list1: 529 | if self.devices[0].state.same(screen): 530 | return True 531 | for screen in self.deduplicate_list2: 532 | if self.devices[1].state.same(screen): 533 | return True 534 | self.deduplicate_list1.append(self.devices[0].state) 535 | self.deduplicate_list2.append(self.devices[1].state) 536 | return False 537 | 538 | def restart_devices_and_install_app_and_data(self): 539 | self.restart_devices(0) 540 | # connect device and install app 541 | if self.is_login_app != 0: 542 | self.devices[0].connect() 543 | self.devices[0].install_app(self.app_path[0]) 544 | self.devices[1].connect() 545 | self.devices[1].install_app(self.app_path[1]) 546 | else: 547 | for device in self.devices: 548 | device.restart(self.emulator_path, self.emulator_name) 549 | device.connect() 550 | 551 | # add some files to the devices 552 | resourcelist = os.listdir(self.resource_path) 553 | for device in self.devices: 554 | device.disable_keyboard() 555 | device.log_crash(f"{self.root_path}/{device.device_serial}_logcat.txt") 556 | for resource in resourcelist: 557 | device.add_file(self.resource_path, resource, "/sdcard") 558 | # if "anki" in self.app.package_name: 559 | # device.mkdir("/storage/emulated/0/AnkiDroid/") 560 | # print("add collection.anki2 for"+str(device.device_serial)) 561 | # device.add_file(self.resource_path,"collection.anki2","/storage/emulated/0/AnkiDroid/") 562 | 563 | def start(self, strategy): 564 | # if execute serial, init the strategy of device1, otherwise, init all the guest devices' strategies 565 | if self.serial_or_parallel == 0: 566 | self.devices[0].use.press("home") 567 | self.devices[1].use.press("home") 568 | self.devices[0].set_strategy(strategy) 569 | self.devices[1].set_strategy(strategy) 570 | self.devices[1].make_strategy(self.root_path) 571 | else: 572 | for device in self.guest_devices: 573 | device.set_strategy(self.strategy_list[device.device_num - 1]) 574 | device.make_strategy(self.root_path) 575 | 576 | run_count = self.start_testcase_count 577 | while run_count < self.testcase_count: 578 | if run_count > 0: 579 | self.restart_devices_and_install_app_and_data() 580 | # create folder of new run 581 | run_count = run_count + 1 582 | for device in self.guest_devices: 583 | device.use.press("back") 584 | device.use.press("back") 585 | device.make_strategy_runcount(run_count, self.root_path) 586 | 587 | # init setting 588 | event_count = 1.0 589 | event_count = self.save_all_state(event_count) 590 | # self.injector.init_setting() 591 | 592 | # clear and start app 593 | event_count = self.clear_and_restart_app(event_count, strategy) 594 | 595 | while event_count < self.event_num: 596 | # if the state of any device is different from last state, stoat need to judge (loading, foreground, same) again 597 | change_flag = False 598 | self.update_all_state(event_count) 599 | for device in self.devices: 600 | if device.last_state != device.state: 601 | change_flag = True 602 | break 603 | 604 | if self.devices[0].last_state is not None and change_flag: 605 | # wait loading 606 | self.wait_load(event_count) 607 | 608 | # check whether app is foreground 609 | if not self.checker.check_foreground(): 610 | print("Not foreground") 611 | self.back_to_app(event_count, strategy) 612 | self.checker.check_loading() 613 | event_count = self.save_all_state(event_count) 614 | 615 | self.checker.check_crash() 616 | 617 | # check keyboard 618 | self.checker.check_keyboard() 619 | 620 | # judge whether all devices are same 621 | if not self.devices[0].state.same(self.devices[1].state): 622 | # judge whether the guest devices requests settings 623 | # if self.checker.check_setting_request(): 624 | # event = Event(None, "check_setting_request", self.devices[1], event_count) 625 | # event_count = self.write_draw_and_save_one(event,event_count) 626 | # request_flag=1 627 | # #write wrong 628 | # elif self.devices[1].wrong_flag==True: 629 | print("Write wrong!") 630 | self.utils.write_error( 631 | 1, 632 | run_count, 633 | self.devices[1].wrong_event_lists, 634 | self.devices[1].f_wrong, 635 | self.devices[1].wrong_num, 636 | ) 637 | self.devices[1].wrong_num = self.devices[1].wrong_num + 1 638 | event = Event(None, "wrong", self.devices[1], event_count) 639 | self.utils.draw_event(event) 640 | 641 | # check keyboard 642 | self.checker.check_keyboard() 643 | 644 | self.wait_load(event_count) 645 | 646 | self.update_all_state(event_count) 647 | 648 | # choice a new event to execute 649 | event = self.policy.choose_event(self.devices[0], event_count) 650 | self.utils.draw_event(event) 651 | event.print_event() 652 | 653 | # execute event 654 | for device in self.devices: 655 | # success = self.execute_event(event,0) 656 | args = (device, event, 0) 657 | device.set_thread(self.execute_event, args) 658 | self.utils.start_thread() 659 | for device in self.devices: 660 | success = device.thread.get_result() 661 | if not success: 662 | fail_device = device.device_num 663 | break 664 | 665 | # device0 can not execute choice event, choice another event, skip this loop 666 | if not success and fail_device == 0: 667 | self.utils.print_dividing_line(False, event_count) 668 | continue 669 | # other devices cannot execute choice event due to setting, record fail, skip this loop 670 | elif not success: 671 | self.utils.print_dividing_line(False, event_count) 672 | self.utils.write_read_event( 673 | None, event_count, event, "different", fail_device 674 | ) 675 | self.utils.write_event( 676 | event, fail_device, self.devices[fail_device].f_trace 677 | ) 678 | self.utils.draw_event(event) 679 | if not self.checkduplicate(): 680 | print("write error") 681 | self.utils.draw_error_frame() 682 | self.utils.write_error( 683 | fail_device, 684 | run_count, 685 | self.devices[fail_device].error_event_lists, 686 | self.devices[fail_device].f_error, 687 | self.devices[fail_device].error_num, 688 | ) 689 | self.devices[fail_device].error_num = ( 690 | self.devices[fail_device].error_num + 1 691 | ) 692 | event_count = self.save_all_state(event_count) 693 | event_count = self.clear_and_restart_app(event_count, strategy) 694 | continue 695 | # all devices execute success, record event and update state 696 | else: 697 | self.utils.print_dividing_line(True, event_count) 698 | self.utils.write_read_event( 699 | None, event_count, event, "all device", device.device_num 700 | ) 701 | self.utils.write_event(event, device.device_num, device.f_trace) 702 | event_count = self.save_all_state(event_count) 703 | 704 | self.checker.check_keyboard() 705 | 706 | # injecte a setting change 707 | # event=self.injector.inject_setting_during_run(event_count,strategy,request_flag) 708 | # if event is not None: 709 | # event_count = self.write_draw_and_save_one(event,event_count) 710 | 711 | # event=self.injector.change_setting_after_run(event_count,strategy) 712 | # if event is not None: 713 | # event_count = self.write_draw_and_save_one(event,event_count) 714 | 715 | if strategy == "language": 716 | for device in self.guest_devices: 717 | self.checker.check_language(self.root_path) 718 | elif strategy == "time": 719 | for device in self.guest_devices: 720 | self.checker.check_time(self.root_path) 721 | 722 | # at the end of each run, generate a html file 723 | for device in self.guest_devices: 724 | self.utils.generate_html(device.path, device.path, run_count) 725 | -------------------------------------------------------------------------------- /RegDroid/injector.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import random 4 | 5 | from event import Event 6 | from utils import Utils 7 | 8 | 9 | class Injector(object): 10 | """ 11 | The strategy of changing setting 12 | """ 13 | 14 | def __init__(self, devices, app, strategy_list, emulator_path, android_system, root_path, resource_path, testcase_count, event_num, timeout, setting_random_denominator, rest_interval, choice): 15 | 16 | self.timeout = timeout 17 | self.app = app 18 | self.devices = devices 19 | self.emulator_path = emulator_path 20 | self.android_system = android_system 21 | self.root_path = root_path 22 | self.resource_path = resource_path 23 | self.strategy_list = strategy_list 24 | self.testcase_count = testcase_count 25 | self.event_num = event_num 26 | self.setting_random_denominator = setting_random_denominator 27 | self.rest_interval = rest_interval 28 | self.utils = Utils(devices=devices) 29 | self.choice = choice 30 | 31 | def change_setting_before_run(self, event_count, strategy): 32 | print("Change setting before run") 33 | if strategy == "network_immediate_1": 34 | self.network_immediate_1() 35 | event = Event(None, "network_immediate_1", 36 | self.devices[1], event_count) 37 | elif strategy == "network_lazy_1": 38 | self.network_lazy_1() 39 | event = Event(None, "network_lazy_1", self.devices[1], event_count) 40 | elif strategy == "network_lazy_2": 41 | self.network_lazy_2() 42 | event = Event(None, "network_lazy_2", self.devices[1], event_count) 43 | elif strategy == "location_lazy_1": 44 | self.location_lazy_1() 45 | event = Event(None, "location_lazy_1", 46 | self.devices[1], event_count) 47 | elif strategy == "location_lazy_2": 48 | self.location_lazy_2() 49 | event = Event(None, "location_lazy_2", 50 | self.devices[1], event_count) 51 | elif strategy == "sound_lazy_1": 52 | self.sound_lazy_1() 53 | event = Event(None, "sound_lazy_1", self.devices[1], event_count) 54 | elif strategy == "battery_lazy_1": 55 | self.battery_lazy_1() 56 | event = Event(None, "battery_lazy_1", self.devices[1], event_count) 57 | elif strategy == "battery_immediate_1": 58 | self.battery_immediate_1() 59 | event = Event(None, "battery_immediate_1", 60 | self.devices[1], event_count) 61 | elif strategy == "permssion_lazy_1": 62 | self.permssion_lazy_1() 63 | event = Event(None, "permssion_lazy_1", 64 | self.devices[1], event_count) 65 | elif strategy == "developer_lazy_1": 66 | self.developer_lazy_1() 67 | event = Event(None, "developer_lazy_1", 68 | self.devices[1], event_count) 69 | elif strategy == "language": 70 | self.language() 71 | event = Event(None, "language", self.devices[1], event_count) 72 | elif strategy == "time": 73 | self.time() 74 | event = Event(None, "time", self.devices[1], event_count) 75 | else: 76 | event = None 77 | return event 78 | 79 | def inject_setting_during_run(self, event_count, strategy, request_flag): 80 | event = None 81 | if strategy == "network_immediate_1": 82 | setting_or_not = random.randint( 83 | 0, self.setting_random_denominator/10) 84 | if setting_or_not == 0: 85 | print("network_immediate_1") 86 | self.network_immediate_1() 87 | event = Event(None, "network_immediate_1", 88 | self.devices[1], event_count) 89 | elif strategy == "network_lazy_1": 90 | if self.devices[1].wifi_state is True: 91 | setting_or_not = random.randint( 92 | 0, self.setting_random_denominator/10) 93 | if setting_or_not == 0: 94 | print("network_lazy_1") 95 | self.network_lazy_1() 96 | event = Event(None, "network_lazy_1", 97 | self.devices[1], event_count) 98 | elif strategy == "network_lazy_2": 99 | if self.devices[1].wifi_state is True: 100 | setting_or_not = random.randint( 101 | 0, self.setting_random_denominator/10) 102 | if setting_or_not == 0: 103 | print("network_lazy_2") 104 | self.network_lazy_2() 105 | event = Event(None, "network_lazy_2", 106 | self.devices[1], event_count) 107 | elif strategy == "sound_lazy_1": 108 | if self.devices[1].sound_state is True: 109 | setting_or_not = random.randint( 110 | 0, self.setting_random_denominator/10) 111 | if setting_or_not == 0: 112 | print("sound_lazy_1") 113 | self.sound_lazy_1() 114 | event = Event(None, "sound_lazy_1", 115 | self.devices[1], event_count) 116 | elif strategy == "location_lazy_1": 117 | if self.devices[1].gps_state is True: 118 | setting_or_not = random.randint( 119 | 0, self.setting_random_denominator/10) 120 | if setting_or_not == 0: 121 | print("") 122 | self.location_lazy_1() 123 | event = Event(None, "location_lazy_1", 124 | self.devices[1], event_count) 125 | elif strategy == "location_lazy_2": 126 | if self.devices[1].gps_state is True: 127 | setting_or_not = random.randint( 128 | 0, self.setting_random_denominator/10) 129 | if setting_or_not == 0: 130 | print("location_lazy_2") 131 | self.location_lazy_2() 132 | event = Event(None, "location_lazy_2", 133 | self.devices[1], event_count) 134 | elif strategy == "display_immediate_1": 135 | setting_or_not = random.randint( 136 | 0, self.setting_random_denominator/10) 137 | if setting_or_not == 0: 138 | print("display_immediate_1") 139 | self.display_immediate_1() 140 | event = Event(None, "display_immediate_1", 141 | self.devices[1], event_count) 142 | elif strategy == "display_immediate_2": 143 | setting_or_not = random.randint( 144 | 0, self.setting_random_denominator/10) 145 | if setting_or_not == 0: 146 | print("display_immediate_2") 147 | self.display_immediate_2() 148 | event = Event(None, "display_immediate_2", 149 | self.devices[1], event_count) 150 | elif strategy == "permssion_lazy_1": 151 | if request_flag == 1: 152 | setting_or_not = 0 153 | else: 154 | setting_or_not = random.randint( 155 | 0, self.setting_random_denominator) 156 | if setting_or_not == 0 and self.devices[1].permission is True: 157 | print("permssion_lazy_1") 158 | self.permssion_lazy_1() 159 | event = Event(None, "permssion_lazy_1", 160 | self.devices[1], event_count) 161 | # elif strategy == "language" : 162 | # if self.devices[1].language == "ch": 163 | # setting_or_not = random.randint(0,self.setting_random_denominator) 164 | # else: 165 | # setting_or_not = random.randint(0,self.setting_random_denominator/10) 166 | # print() 167 | # if setting_or_not == 0: 168 | # print("language") 169 | # self.language() 170 | # event = Event(None,"language",self.devices[1],event_count) 171 | else: 172 | event = None 173 | return event 174 | 175 | def change_setting_after_run(self, event_count, strategy): 176 | print("Change setting after run") 177 | if strategy == "network_immediate_1": 178 | self.network_immediate_1() 179 | event = Event(None, "network_immediate_1", 180 | self.devices[1], event_count) 181 | elif strategy == "network_lazy_1": 182 | self.network_lazy_1() 183 | event = Event(None, "network_lazy_1", self.devices[1], event_count) 184 | elif strategy == "network_lazy_2": 185 | self.network_lazy_2() 186 | event = Event(None, "network_lazy_2", self.devices[1], event_count) 187 | elif strategy == "location_lazy_1": 188 | self.location_lazy_1() 189 | event = Event(None, "location_lazy_1", 190 | self.devices[1], event_count) 191 | elif strategy == "location_lazy_2": 192 | self.location_lazy_2() 193 | event = Event(None, "location_lazy_2", 194 | self.devices[1], event_count) 195 | elif strategy == "sound_lazy_1": 196 | self.sound_lazy_1() 197 | event = Event(None, "sound_lazy_1", self.devices[1], event_count) 198 | elif strategy == "battery_immediate_1": 199 | self.battery_immediate_1() 200 | event = Event(None, "battery_immediate_1", 201 | self.devices[1], event_count) 202 | elif strategy == "battery_lazy_1": 203 | self.battery_lazy_1() 204 | event = Event(None, "battery_lazy_1", self.devices[1], event_count) 205 | elif strategy == "permssion_lazy_1": 206 | self.permssion_lazy_1() 207 | event = Event(None, "permssion_lazy_1", 208 | self.devices[1], event_count) 209 | elif strategy == "developer_lazy_1": 210 | self.developer_lazy_1() 211 | event = Event(None, "developer_lazy_1", 212 | self.devices[1], event_count) 213 | elif strategy == "language" and self.devices[1].language == "ch": 214 | self.language() 215 | event = Event(None, "language", self.devices[1], event_count) 216 | elif strategy == "time" and self.devices[1].hourformat == "24h": 217 | self.time() 218 | event = Event(None, "time", self.devices[1], event_count) 219 | else: 220 | event = None 221 | return event 222 | 223 | def replay_setting(self, event, strategy_list): 224 | print("Replay setting") 225 | if event.action == "network_immediate_1": 226 | self.network_immediate_1() 227 | elif event.action == "network_lazy_1": 228 | self.network_lazy_1() 229 | elif event.action == "network_lazy_2": 230 | self.network_lazy_2() 231 | elif event.action == "location_lazy_1": 232 | self.location_lazy_1() 233 | elif event.action == "location_lazy_2": 234 | self.location_lazy_2() 235 | elif event.action == "sound_lazy_1": 236 | self.sound_lazy_1() 237 | elif event.action == "battery_immediate_1": 238 | self.battery_immediate_1() 239 | elif event.action == "battery_lazy_1": 240 | self.battery_lazy_1() 241 | elif event.action == "display_immediate_1": 242 | self.display_immediate_1() 243 | elif event.action == "display_immediate_2": 244 | self.display_immediate_2() 245 | elif event.action == "permssion_lazy_1": 246 | self.permssion_lazy_1() 247 | elif event.action == "developer_lazy_1": 248 | self.developer_lazy_1() 249 | elif event.action == "language": 250 | self.language() 251 | elif event.action == "time": 252 | self.time() 253 | else: 254 | event = None 255 | return event 256 | 257 | def developer_lazy_1(self): 258 | device0 = self.devices[0].use 259 | device1 = self.devices[1].use 260 | self.clear_and_start_setting(device0, device1) 261 | device0.set_orientation("n") 262 | device1.set_orientation("n") 263 | while device0(text="Apps & notifications").count < 0 or device1(text="Apps & notifications").count < 0: 264 | time.sleep(self.rest_interval*1) 265 | print("System") 266 | device1(scrollable=True, instance=0).scroll.to(text="System") 267 | device1(text="System").wait(timeout=3.0) 268 | device1(text="System").click() 269 | device1(text="About emulated device").wait(timeout=3.0) 270 | device1(text="About emulated device").click() 271 | for _ in range(7): 272 | time.sleep(self.rest_interval*1) 273 | device1(text="Build number").click() 274 | device1.press("back") 275 | device1(text="Developer options").wait(timeout=3.0) 276 | device1(text="Developer options").click() 277 | device1(scrollable=True, instance=0).scroll.to(text="Don’t keep activities") 278 | device1(text="Don’t keep activities").wait(timeout=3.0) 279 | device1(text="Don’t keep activities").click() 280 | device0.press("back") 281 | time.sleep(self.rest_interval*1) 282 | for _ in range(3): 283 | device1.press("back") 284 | time.sleep(self.rest_interval*1) 285 | 286 | def network_immediate_1(self): 287 | device = self.devices[1] 288 | device.use.open_quick_settings() 289 | self.devices[0].use.open_quick_settings() 290 | device.use(description="Airplane mode").wait() 291 | device.use(description="Airplane mode").click() 292 | time.sleep(self.rest_interval*1) 293 | device.use(description="Airplane mode").wait() 294 | device.use(description="Airplane mode").click() 295 | device.use.press("back") 296 | self.devices[0].use.press("back") 297 | time.sleep(self.rest_interval*1) 298 | device.use.press("back") 299 | self.devices[0].use.press("back") 300 | self.devices[1].wifi_state = True 301 | time.sleep(self.rest_interval*1) 302 | 303 | def network_lazy_1(self): 304 | device = self.devices[1] 305 | device.use.open_quick_settings() 306 | self.devices[0].use.open_quick_settings() 307 | if self.devices[1].wifi_state is True: 308 | device.use(description="Airplane mode").wait() 309 | device.use(description="Airplane mode").click() 310 | self.devices[1].wifi_state = False 311 | elif self.devices[1].wifi_state is False: 312 | device.use(description="Airplane mode").wait() 313 | device.use(description="Airplane mode").click() 314 | self.devices[1].wifi_state = True 315 | device.use.press("back") 316 | self.devices[0].use.press("back") 317 | time.sleep(self.rest_interval*1) 318 | device.use.press("back") 319 | self.devices[0].use.press("back") 320 | time.sleep(self.rest_interval*1) 321 | 322 | def network_lazy_2(self): 323 | device = self.devices[1] 324 | device.use.open_quick_settings() 325 | self.devices[0].use.open_quick_settings() 326 | if self.devices[1].wifi_state is True: 327 | device.use( 328 | description="Wi-Fi,Wifi signal full.,No internet.,AndroidWifi").wait() 329 | device.use( 330 | description="Wi-Fi,Wifi signal full.,No internet.,AndroidWifi").click() 331 | device.use(text="ON").wait() 332 | device.use(text="ON").click() 333 | device.use(text="DONE").wait() 334 | device.use(text="DONE").click() 335 | self.devices[1].wifi_state = False 336 | elif self.devices[1].wifi_state is False: 337 | device.use(description="Wi-Fi,").wait() 338 | device.use(description="Wi-Fi,").click() 339 | device.use(text="OFF").wait() 340 | device.use(text="OFF").click() 341 | device.use(text="DONE").wait() 342 | device.use(text="DONE").click() 343 | self.devices[1].wifi_state = True 344 | device.use.press("back") 345 | self.devices[0].use.press("back") 346 | time.sleep(self.rest_interval*1) 347 | device.use.press("back") 348 | self.devices[0].use.press("back") 349 | time.sleep(self.rest_interval*1) 350 | 351 | def location_lazy_1(self): 352 | device0 = self.devices[0].use 353 | device1 = self.devices[1].use 354 | orientation1 = device0.orientation 355 | orientation2 = device1.orientation 356 | self.clear_and_start_setting(device0, device1) 357 | device0.set_orientation("n") 358 | device1.set_orientation("n") 359 | while device0(text="Apps & notifications").count < 0 or device1(text="Apps & notifications").count < 0: 360 | time.sleep(self.rest_interval*1) 361 | print("Security & Location") 362 | device1(scrollable=True, instance=0).scroll.to( 363 | text="Security & Location") 364 | device1(text="Security & Location").click() 365 | device1(text="Location").wait(timeout=3.0) 366 | device1(text="Location").click() 367 | time.sleep(self.rest_interval*1) 368 | if self.devices[1].gps_state is True and device1(text="ON").count > 0: 369 | device1(text="ON").click() 370 | self.devices[1].gps_state = False 371 | elif self.devices[1].gps_state is False and device1(text="OFF").count > 0: 372 | device1(text="OFF").click() 373 | self.devices[1].gps_state = True 374 | print("End location change") 375 | device0.set_orientation(orientation1) 376 | device1.set_orientation(orientation2) 377 | backtime = 0 378 | device1.press("back") 379 | time.sleep(self.rest_interval*1) 380 | device1.press("back") 381 | time.sleep(self.rest_interval*1) 382 | while backtime < 2: 383 | backtime = backtime+1 384 | device0.press("back") 385 | device1.press("back") 386 | time.sleep(self.rest_interval*1) 387 | 388 | def location_lazy_2(self): 389 | device0 = self.devices[0].use 390 | device1 = self.devices[1].use 391 | orientation1 = device0.orientation 392 | orientation2 = device1.orientation 393 | self.clear_and_start_setting(device0, device1) 394 | device0.set_orientation("n") 395 | device1.set_orientation("n") 396 | while device0(text="Apps & notifications").count < 0 or device1(text="Apps & notifications").count < 0: 397 | time.sleep(self.rest_interval*1) 398 | print("Security & Location") 399 | device0(scrollable=True, instance=0).scroll.to( 400 | text="Security & Location") 401 | device0(text="Security & Location").click() 402 | device0(text="Location").wait(timeout=3.0) 403 | device0(text="Location").click() 404 | device1(scrollable=True, instance=0).scroll.to( 405 | text="Security & Location") 406 | device1(text="Security & Location").click() 407 | device1(text="Location").wait(timeout=3.0) 408 | device1(text="Location").click() 409 | device0(text="Mode").wait(timeout=3.0) 410 | device0(text="Mode").click() 411 | device1(text="Mode").wait(timeout=3.0) 412 | device1(text="Mode").click() 413 | if self.devices[1].gps_state is True: 414 | device0(text="High accuracy").wait(timeout=3.0) 415 | device0(text="High accuracy").click() 416 | device0(text="AGREE").wait(timeout=3.0) 417 | if device0(text="AGREE").count > 0: 418 | device0(text="AGREE").click() 419 | device1(text="Device only").wait(timeout=3.0) 420 | device1(text="Device only").click() 421 | self.devices[1].gps_state = False 422 | elif self.devices[1].gps_state is False: 423 | device1(text="High accuracy").wait(timeout=3.0) 424 | device1(text="High accuracy").click() 425 | device1(text="AGREE").wait(timeout=3.0) 426 | if device1(text="AGREE").count > 0: 427 | device1(text="AGREE").click() 428 | self.devices[1].gps_state = True 429 | time.sleep(self.rest_interval*1) 430 | print("End location change") 431 | device0.set_orientation(orientation1) 432 | device1.set_orientation(orientation2) 433 | for _ in range(4): 434 | device0.press("back") 435 | device1.press("back") 436 | time.sleep(self.rest_interval*1) 437 | 438 | def sound_lazy_1(self): 439 | device = self.devices[1] 440 | device.use.open_quick_settings() 441 | self.devices[0].use.open_quick_settings() 442 | if self.devices[1].sound_state is True: 443 | device.use(description="Do not disturb.").wait() 444 | device.use(description="Do not disturb.").click() 445 | device.use(text="OFF").wait() 446 | device.use(text="OFF").click() 447 | device.use(text="DONE").wait() 448 | device.use(text="DONE").click() 449 | self.devices[1].sound_state = False 450 | elif self.devices[1].sound_state is False: 451 | device.use(text="Alarms only").wait() 452 | device.use(text="Alarms only").click() 453 | device.use(text="ON").wait() 454 | device.use(text="ON").click() 455 | device.use(text="DONE").wait() 456 | device.use(text="DONE").click() 457 | self.devices[1].sound_state = True 458 | device.use.press("back") 459 | self.devices[0].use.press("back") 460 | time.sleep(self.rest_interval*1) 461 | device.use.press("back") 462 | self.devices[0].use.press("back") 463 | time.sleep(self.rest_interval*1) 464 | 465 | def battery_immediate_1(self): 466 | device = self.devices[1] 467 | device.use.open_quick_settings() 468 | self.devices[0].use.open_quick_settings() 469 | if self.devices[1].battery_state is False: 470 | device.use(description="Battery Saver").wait() 471 | device.use(description="Battery Saver").click() 472 | device.use(description="Battery Saver").wait() 473 | device.use(description="Battery Saver").long_click() 474 | device.use(description="More options").wait() 475 | device.use(description="More options").click() 476 | device.use(className="android.widget.RelativeLayout", 477 | instance=0).wait() 478 | device.use(className="android.widget.RelativeLayout", 479 | instance=0).click() 480 | device.use(text="Not optimized").wait() 481 | device.use(text="Not optimized").click() 482 | device.use(text="All apps").wait() 483 | device.use(text="All apps").click() 484 | device.use(scrollable=True, instance=1).scroll.to( 485 | text=self.app.app_name) 486 | device.use(scrollable=True, instance=1).scroll.to( 487 | text=self.app.app_name) 488 | device.use(text=self.app.app_name).click() 489 | device.use(text=self.app.app_name).click() 490 | device.use(text="Don’t optimize").wait() 491 | device.use(text="Don’t optimize").click() 492 | device.use(text="DONE").wait() 493 | device.use(text="DONE").click() 494 | self.devices[1].battery_state = True 495 | elif self.devices[1].battery_state is True: 496 | device.use(description="Battery Saver").wait() 497 | device.use(description="Battery Saver").click() 498 | device.use(description="Battery Saver").wait() 499 | device.use(description="Battery Saver").long_click() 500 | device.use(description="More options").wait() 501 | device.use(description="More options").click() 502 | device.use(className="android.widget.RelativeLayout", 503 | instance=0).wait() 504 | device.use(className="android.widget.RelativeLayout", 505 | instance=0).click() 506 | device.use(text="Not optimized").wait() 507 | device.use(text="Not optimized").click() 508 | device.use(text="All apps").wait() 509 | device.use(text="All apps").click() 510 | device.use(scrollable=True, instance=1).scroll.to( 511 | text=self.app.app_name) 512 | device.use(scrollable=True, instance=1).scroll.to( 513 | text=self.app.app_name) 514 | device.use(text=self.app.app_name).click() 515 | device.use(text=self.app.app_name).click() 516 | device.use(text="Optimize").wait() 517 | device.use(text="Optimize").click() 518 | device.use(text="DONE").wait() 519 | device.use(text="DONE").click() 520 | self.devices[1].battery_state = False 521 | 522 | device.use.press("back") 523 | self.devices[0].use.press("back") 524 | time.sleep(self.rest_interval*1) 525 | device.use.press("back") 526 | self.devices[0].use.press("back") 527 | time.sleep(self.rest_interval*1) 528 | 529 | def battery_lazy_1(self): 530 | device = self.devices[1] 531 | device.use.open_quick_settings() 532 | self.devices[0].use.open_quick_settings() 533 | if self.devices[1].battery_state is True: 534 | device.use(description="Battery Saver").wait() 535 | device.use(description="Battery Saver").click() 536 | self.devices[1].battery_state = False 537 | elif self.devices[1].battery_state is False: 538 | device.use(description="Battery Saver").wait() 539 | device.use(description="Battery Saver").click() 540 | self.devices[1].battery_state = True 541 | device.use.press("back") 542 | self.devices[0].use.press("back") 543 | time.sleep(self.rest_interval*1) 544 | device.use.press("back") 545 | self.devices[0].use.press("back") 546 | time.sleep(self.rest_interval*1) 547 | 548 | def display_immediate_2(self): 549 | self.devices[1].use( 550 | resourceId="com.android.systemui:id/recent_apps").long_click() 551 | time.sleep(self.rest_interval*1) 552 | self.devices[1].use( 553 | resourceId="com.android.systemui:id/recent_apps").long_click() 554 | 555 | def display_immediate_1(self): 556 | device = self.devices[1] 557 | orientation1 = self.devices[0].use.orientation 558 | device.use.set_orientation("n") 559 | device.use.set_orientation("l") 560 | device.use.set_orientation(orientation1) 561 | time.sleep(self.rest_interval*1) 562 | 563 | def permssion_lazy_1(self): 564 | last_activity = self.devices[1].use.app_current()['activity'] 565 | 566 | if self.android_system == "emulator8": 567 | self.change_permission_emulator8() 568 | self.devices[1].permission = False 569 | 570 | applist = ["com.dragon.read"] 571 | self.devices[1].use.wait_activity(last_activity, timeout=5) 572 | current_activity = self.devices[1].use.app_current()['activity'] 573 | if self.app.package_name in applist or (self.choice != 2 and last_activity != current_activity): 574 | for device in self.devices: 575 | print("permission restart") 576 | device.stop_app(self.app) 577 | 578 | def change_permission_emulator8(self): 579 | device0 = self.devices[0].use 580 | device1 = self.devices[1].use 581 | orientation1 = device0.orientation 582 | orientation2 = device1.orientation 583 | self.clear_and_start_setting(device0, device1) 584 | device0.set_orientation("n") 585 | device1.set_orientation("n") 586 | while device0(text="Apps & notifications").count < 0 or device1(text="Apps & notifications").count < 0: 587 | time.sleep(self.rest_interval*1) 588 | print("Apps & notifications") 589 | device0(text="Apps & notifications").click() 590 | device1(text="Apps & notifications").click() 591 | device0(text="App info").wait(timeout=3.0) 592 | device1(text="App info").wait(timeout=3.0) 593 | device0(text="App info").click() 594 | device1(text="App info").click() 595 | device0(scrollable=True, instance=1).scroll.to(text=self.app.app_name) 596 | device1(scrollable=True, instance=1).scroll.to(text=self.app.app_name) 597 | device0(text=self.app.app_name).click() 598 | device1(text=self.app.app_name).click() 599 | device0(scrollable=True, instance=0).scroll.to(text="Permissions") 600 | device1(scrollable=True, instance=0).scroll.to(text="Permissions") 601 | device0(text="Permissions").click() 602 | device1(text="Permissions").click() 603 | time.sleep(self.rest_interval*1) 604 | while device0(text="OFF", className="android.widget.Switch").count > 0: 605 | try: 606 | device0(text="OFF", className="android.widget.Switch").click() 607 | except: 608 | continue 609 | while device1(text="ON", className="android.widget.Switch").count > 0: 610 | try: 611 | device1(text="ON", className="android.widget.Switch").click() 612 | except: 613 | continue 614 | print("End Permissions change") 615 | device0.set_orientation(orientation1) 616 | device1.set_orientation(orientation2) 617 | for _ in range(5): 618 | device0.press("back") 619 | device1.press("back") 620 | time.sleep(self.rest_interval*1) 621 | 622 | def language(self): 623 | device0 = self.devices[0].use 624 | device1 = self.devices[1].use 625 | self.clear_and_start_setting(device0, device1) 626 | device0.set_orientation("n") 627 | device1.set_orientation("n") 628 | 629 | if self.devices[1].language == "en": 630 | time.sleep(self.rest_interval*1) 631 | print("System") 632 | device1(scrollable=True, instance=0).scroll.to(text="System") 633 | device1(text="System").click() 634 | device1(text="Gboard").wait(timeout=3.0) 635 | device1(text="Gboard").click() 636 | device1(text="Languages").wait(timeout=3.0) 637 | device1(text="Languages").click() 638 | device1(text="Add a language").wait() 639 | device1(text="Add a language").click() 640 | device1(scrollable=True, instance=0).scroll.to(text="简体中文") 641 | device1(text="简体中文").wait(timeout=3.0) 642 | device1(text="简体中文").click() 643 | device1(text="中国").wait(timeout=3.0) 644 | device1(text="中国").click() 645 | device1(description="More options").wait(timeout=3.0) 646 | device1(description="More options").click() 647 | time.sleep(self.rest_interval*1) 648 | device1.click(796, 144) 649 | device1(text="English (United States)").wait(timeout=3.0) 650 | device1(text="English (United States)").click() 651 | device1(description="Remove").wait(timeout=3.0) 652 | device1(description="Remove").click() 653 | device1(text="OK").wait(timeout=3.0) 654 | device1(text="OK").click() 655 | self.devices[1].language = "ch" 656 | elif self.devices[1].language == "ch": 657 | device1(scrollable=True, instance=0).scroll.to(text="系统") 658 | device1(text="系统").click() 659 | device1(text="语言和输入法").wait(timeout=3.0) 660 | device1(text="语言和输入法").click() 661 | device1(text="语言").wait(timeout=3.0) 662 | device1(text="语言").click() 663 | device1(text="添加语言").wait() 664 | device1(text="添加语言").click() 665 | device1(text="English (United States)").click() 666 | device1(description="更多选项").click() 667 | time.sleep(self.rest_interval*1) 668 | device1.click(796, 144) 669 | device1(text="简体中文(中国)").wait(timeout=3.0) 670 | device1(text="简体中文(中国)").click() 671 | device1(description="移除").wait(timeout=3.0) 672 | device1(description="移除").click() 673 | device1(text="确定").wait(timeout=3.0) 674 | device1(text="确定").click() 675 | self.devices[1].language = "en" 676 | time.sleep(self.rest_interval*1) 677 | device0.press("back") 678 | device0.press("back") 679 | time.sleep(self.rest_interval*1) 680 | for _ in range(4): 681 | device1.press("back") 682 | time.sleep(self.rest_interval*1) 683 | 684 | def time(self): 685 | device0 = self.devices[0].use 686 | device1 = self.devices[1].use 687 | self.clear_and_start_setting(device0, device1) 688 | device0.set_orientation("n") 689 | device1.set_orientation("n") 690 | 691 | if self.devices[1].hourformat == "12h": 692 | time.sleep(self.rest_interval*1) 693 | print("System") 694 | device1(scrollable=True, instance=0).scroll.to(text="System") 695 | device1(text="System").click() 696 | device1(text="Date & time").wait(timeout=3.0) 697 | device1(text="Date & time").click() 698 | device1(text="Use 24-hour format").wait(timeout=3.0) 699 | device1(text="Use 24-hour format").click() 700 | self.devices[1].hourformat = "24h" 701 | elif self.devices[1].hourformat == "24h": 702 | time.sleep(self.rest_interval*1) 703 | print("System") 704 | device1(scrollable=True, instance=0).scroll.to(text="System") 705 | device1(text="System").click() 706 | device1(text="Date & time").wait(timeout=3.0) 707 | device1(text="Date & time").click() 708 | device1(text="Use 24-hour format").wait(timeout=3.0) 709 | device1(text="Use 24-hour format").click() 710 | self.devices[1].hourformat = "12h" 711 | 712 | device0.press("back") 713 | device1.press("back") 714 | time.sleep(self.rest_interval*1) 715 | for _ in range(2): 716 | device1.press("back") 717 | time.sleep(self.rest_interval*1) 718 | 719 | def clear_and_start_setting(self, device0, device1): 720 | device0.set_orientation("n") 721 | device1.set_orientation("n") 722 | device0.app_clear("com.android.settings") 723 | device1.app_clear("com.android.settings") 724 | device0.app_start("com.android.settings") 725 | device1.app_start("com.android.settings") 726 | 727 | def init_setting(self): 728 | if self.android_system == "emulator8": 729 | self.init_setting_emulator8() 730 | 731 | def init_setting_emulator8(self): 732 | for device in self.devices: 733 | device.use.open_quick_settings() 734 | time.sleep(self.rest_interval*1) 735 | lines = device.use.dump_hierarchy().splitlines() 736 | for line in lines: 737 | if 'android.widget.Switch' in line and "content-desc=\"Airplane mode" in line and "text=\"On" in line: 738 | device.use(description="Airplane mode").click() 739 | time.sleep(self.rest_interval*1) 740 | elif 'android.widget.Switch' in line and "content-desc=\"Airplane mode" in line and "text=\"On" in line: 741 | device.use(description="Airplane mode").click() 742 | time.sleep(self.rest_interval*1) 743 | elif 'android.widget.Switch' in line and "content-desc=\"Battery Saver" in line and "text=\"On" in line: 744 | device.use(description="Battery Saver").click() 745 | time.sleep(self.rest_interval*1) 746 | elif 'android.widget.TextView' in line and "text=\"Alarms only" in line: 747 | device.use(text="Alarms only").click() 748 | device.use(text="ON").wait() 749 | device.use(text="ON").click() 750 | device.use(text="DONE").wait() 751 | device.use(text="DONE").click() 752 | time.sleep(self.rest_interval*1) 753 | device.use.press("home") 754 | -------------------------------------------------------------------------------- /RegDroid/policy.py: -------------------------------------------------------------------------------- 1 | 2 | import random 3 | 4 | from event import Event 5 | 6 | 7 | class Policy(object): 8 | """ 9 | The policy of exploration 10 | """ 11 | 12 | def __init__(self, devices, app, emulator_path, android_system, root_path): 13 | 14 | self.app = app 15 | self.devices = devices 16 | self.emulator_path = emulator_path 17 | self.android_system = android_system 18 | self.root_path = root_path 19 | 20 | def choose_event(self): 21 | pass 22 | 23 | 24 | class RandomPolicy(Policy): 25 | def __init__(self, devices, app, emulator_path, android_system, root_path, 26 | pro_click, pro_longclick, pro_scroll, pro_edit, pro_naturalscreen, pro_leftscreen, pro_back, pro_splitscreen, pro_home): 27 | 28 | self.pro_click = pro_click 29 | self.pro_longclick = pro_click+pro_longclick 30 | self.pro_scroll = pro_click+pro_longclick+pro_scroll 31 | self.pro_edit = pro_click+pro_longclick+pro_scroll+pro_edit 32 | self.pro_naturalscreen = pro_click+pro_longclick + \ 33 | pro_scroll+pro_edit+pro_naturalscreen 34 | self.pro_leftscreen = pro_click+pro_longclick + \ 35 | pro_scroll+pro_edit+pro_naturalscreen+pro_leftscreen 36 | self.pro_back = pro_click+pro_longclick+pro_scroll + \ 37 | pro_edit+pro_naturalscreen+pro_leftscreen+pro_back 38 | self.pro_splitscreen = pro_click+pro_longclick+pro_scroll+pro_edit + \ 39 | pro_naturalscreen+pro_leftscreen+pro_back+pro_splitscreen 40 | self.pro_home = pro_click+pro_longclick+pro_scroll+pro_edit + \ 41 | pro_naturalscreen+pro_leftscreen+pro_back+pro_splitscreen+pro_home 42 | self.app = app 43 | self.devices = devices 44 | self.emulator_path = emulator_path 45 | self.android_system = android_system 46 | self.root_path = root_path 47 | self.pro_all = pro_click+pro_longclick+pro_scroll+pro_edit + \ 48 | pro_naturalscreen+pro_leftscreen+pro_back+pro_splitscreen+pro_home 49 | 50 | @staticmethod 51 | def random_text(): 52 | text_style = random.randint(0, 8) 53 | text_length = random.randint(1, 5) 54 | nums = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] 55 | letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", 56 | "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] 57 | symbols = [",", ".", "!", "?"] 58 | i = 0 59 | random_string = "" 60 | if text_style == 0: 61 | while i < text_length: 62 | now_num = nums[random.randint(0, len(nums)-1)] 63 | random_string = random_string+now_num 64 | i = i+1 65 | elif text_style == 1: 66 | while i < text_length: 67 | now_letters = letters[random.randint(0, len(nums)-1)] 68 | random_string = random_string+now_letters 69 | i = i+1 70 | elif text_style == 2: 71 | while i < text_length: 72 | s_style = random.randint(0, 2) 73 | if s_style == 0: 74 | now_letters = nums[random.randint(0, len(nums)-1)] 75 | random_string = random_string+now_letters 76 | elif s_style == 1: 77 | now_letters = letters[random.randint(0, len(letters)-1)] 78 | random_string = random_string+now_letters 79 | elif s_style == 2: 80 | now_letters = symbols[random.randint(0, len(symbols)-1)] 81 | random_string = random_string+now_letters 82 | i = i+1 83 | elif text_style == 3: 84 | country = ["Beijing", "London", "Paris", "New York", "Tokyo"] 85 | countrynum = random.randint(0, 4) 86 | random_string = country[countrynum] 87 | elif text_style == 4: 88 | random_string = letters[random.randint(0, len(letters)-1)] 89 | elif text_style == 5: 90 | random_string = nums[random.randint(0, len(nums)-1)] 91 | elif text_style == 6: 92 | special_text = ["www.google.com", "t"] 93 | specialnum = random.randint(0, len(special_text)-1) 94 | random_string = special_text[specialnum] 95 | return random_string 96 | 97 | def random_event(self, views, arg1, device, event_count): 98 | event_view_num = random.randint(0, len(views)-1) 99 | event_view = views[event_view_num] 100 | return Event(event_view, arg1, device, event_count) 101 | 102 | def choose_event(self, device, event_count): 103 | event_type = random.randint(0, self.pro_all-1) 104 | event = None 105 | click_classname_lists = ["android.widget.RadioButton", "android.view.View", "android.widget.ImageView", "android.widget.View", "android.widget.CheckBox", "android.widget.Button", 106 | "android.widget.Switch", "android.widget.ImageButton", "android.widget.TextView", "android.widget.CheckedTextView", "android.widget.TableRow", "android.widget.EditText", "android.support.v7.widget.ar"] 107 | # click_package_lists=[self.app.package_name,"android","com.android.settings","com.google.android", 108 | # "com.google.android.inputmethod.latin","com.google.android.permissioncontroller","com.android.packageinstaller","com.android.permissioncontroller","com.google.android.packageinstaller"] 109 | # delete keyboard 110 | click_package_lists = [self.app.package_name, "android", "com.android.settings", "com.google.android", 111 | "com.google.android.permissioncontroller", "com.android.packageinstaller", "com.android.permissioncontroller", "com.google.android.packageinstaller"] 112 | print(f"random:{event_type}") 113 | if event_type < self.pro_click: 114 | views = [] 115 | import_views = [] 116 | click_classname_lists_important = [ 117 | "android.widget.CheckBox", "android.widget.Button", "android.widget.Switch"] 118 | for view in device.state.all_views: 119 | if view.className in click_classname_lists_important and view.package in click_package_lists and view.clickable == "true": 120 | views.append(view) 121 | import_views.append(view) 122 | if view.className in click_classname_lists and view.package in click_package_lists and view.clickable == "true": 123 | views.append(view) 124 | # fix Focus item clickable = false 125 | if "focus" in self.app.package_name: 126 | focus_list = ["android.widget.RadioButton", "android.widget.ImageView", "android.widget.View", "android.widget.CheckBox", "android.widget.Button", "android.widget.Switch", 127 | "android.widget.ImageButton", "android.widget.TextView", "android.widget.CheckedTextView", "android.widget.TableRow", "android.widget.EditText", "android.support.v7.widget.ar"] 128 | if view.className in focus_list and view.package in click_package_lists and view.clickable == "false": 129 | views.append(view) 130 | if views: 131 | event = self.random_event( 132 | views, "click", device, event_count 133 | ) 134 | else: 135 | # print("re_choice") 136 | event = self.choose_event(device, event_count) 137 | elif event_type < self.pro_longclick: 138 | # print("longclick") 139 | if device.use(longClickable=True).count < 1: 140 | # print("re_choice") 141 | event = self.choose_event(device, event_count) 142 | else: 143 | if views := [ 144 | view 145 | for view in device.state.all_views 146 | if view.className in click_classname_lists 147 | and view.package in click_package_lists 148 | and view.longClickable == "true" 149 | ]: 150 | event = self.random_event( 151 | views, "longclick", device, event_count 152 | ) 153 | else: 154 | # print("re_choice") 155 | event = self.choose_event(device, event_count) 156 | elif event_type < self.pro_scroll: 157 | # print("scroll") 158 | if device.use(scrollable=True).count < 1: 159 | # print("re_choice") 160 | event = self.choose_event(device, event_count) 161 | else: 162 | if views := [ 163 | view 164 | for view in device.state.all_views 165 | if view.scrollable == "true" 166 | ]: 167 | event_view_num = random.randint(0, len(views)-1) 168 | event_view = views[event_view_num] 169 | direction_list = ["backward", "forward", "right", "left"] 170 | direction_num = random.randint(0, len(direction_list)-1) 171 | event = Event( 172 | event_view, 173 | f"scroll_{direction_list[direction_num]}", 174 | device, 175 | event_count, 176 | ) 177 | else: 178 | # print("re_choice") 179 | event = self.choose_event(device, event_count) 180 | elif event_type < self.pro_edit: 181 | if device.use(className="android.widget.EditText").count < 1: 182 | # print("re_choice") 183 | event = self.choose_event(device, event_count) 184 | else: 185 | if views := [ 186 | view 187 | for view in device.state.all_views 188 | if view.className == "android.widget.EditText" 189 | ]: 190 | event = self.random_event( 191 | views, "edit", device, event_count 192 | ) 193 | text = self.random_text() 194 | event.set_text(text) 195 | else: 196 | # print("re_choice") 197 | event = self.choose_event(device, event_count) 198 | elif event_type < self.pro_naturalscreen: 199 | # print("naturalscreen") 200 | if device.use.orientation == "left": 201 | event = Event(None, "naturalscreen", device, event_count) 202 | else: 203 | event = self.choose_event(device, event_count) 204 | elif event_type < self.pro_leftscreen: 205 | # print("leftscreen") 206 | if device.use.orientation == "natural": 207 | event = Event(None, "leftscreen", device, event_count) 208 | else: 209 | event = self.choose_event(device, event_count) 210 | elif event_type < self.pro_back: 211 | # print("back") 212 | if self.app.main_activity != device.use.app_current()['activity']: 213 | event = Event(None, "back", device, event_count) 214 | else: 215 | event = self.choose_event(device, event_count) 216 | elif event_type < self.pro_splitscreen: 217 | # print("splitscreen") 218 | event = Event(None, "splitscreen", device, event_count) 219 | else: 220 | # print("home") 221 | event = Event(None, "home", device, event_count) 222 | return event 223 | -------------------------------------------------------------------------------- /RegDroid/regdroid.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | import os 4 | import sys 5 | import time 6 | import traceback 7 | from threading import Timer 8 | 9 | from device import Device 10 | from app import App 11 | from executor import Executor 12 | from utils import Utils 13 | 14 | 15 | class RegDroid(object): 16 | instance = None 17 | 18 | def __init__(self, 19 | devices_serial, 20 | pro_click=100, 21 | pro_longclick=10, 22 | pro_scroll=10, 23 | pro_edit=10, 24 | pro_naturalscreen=5, 25 | pro_leftscreen=10, 26 | pro_back=5, 27 | pro_home=5, 28 | pro_splitscreen=0, 29 | app_path=None, 30 | is_emulator=True, 31 | choice=0, 32 | emulator_path=None, 33 | android_system=None, 34 | root_path=None, 35 | resource_path=None, 36 | strategy_list=None, 37 | testcase_count=None, 38 | start_testcase_count=None, 39 | event_num=None, 40 | timeout=None, 41 | policy_name="random", 42 | setting_random_denominator=5, 43 | serial_or_parallel=None, 44 | app_name=None, 45 | emulator_name=None, 46 | is_login_app=None, 47 | rest_interval=None, 48 | trace_path=None): 49 | 50 | logging.basicConfig(level=logging.INFO) 51 | self.logger = logging.getLogger('RegDroid') 52 | self.enabled = True 53 | RegDroid.instance = self 54 | self.timer = None 55 | 56 | self.policy_name = policy_name 57 | self.timeout = timeout 58 | self.pro_click = pro_click 59 | self.pro_longclick = pro_longclick 60 | self.pro_scroll = pro_scroll 61 | self.pro_home = pro_home 62 | self.pro_edit = pro_edit 63 | self.pro_naturalscreen = pro_naturalscreen 64 | self.pro_leftscreen = pro_leftscreen 65 | self.pro_back = pro_back 66 | self.pro_splitscreen = pro_splitscreen 67 | self.app_path = app_path 68 | self.is_emulator = is_emulator 69 | self.devices_serial = devices_serial 70 | self.devices = [] 71 | self.choice = choice 72 | self.emulator_path = emulator_path 73 | self.android_system = android_system 74 | self.resource_path = resource_path 75 | self.strategy_list = strategy_list 76 | self.testcase_count = testcase_count 77 | self.start_testcase_count = start_testcase_count 78 | self.event_num = event_num 79 | self.app = App(app_path[0], root_path, app_name) 80 | self.setting_random_denominator = setting_random_denominator 81 | self.serial_or_parallel = serial_or_parallel 82 | self.emulator_name = emulator_name 83 | self.is_login_app = is_login_app 84 | self.rest_interval = rest_interval 85 | self.trace_path = trace_path 86 | 87 | if root_path is not None: 88 | if not os.path.isdir(root_path): 89 | os.makedirs(root_path) 90 | else: 91 | print("None root path") 92 | return 93 | self.root_path = root_path+self.app.package_name+"/" 94 | if not os.path.isdir(self.root_path): 95 | os.makedirs(self.root_path) 96 | 97 | i = 0 98 | for device_serial in devices_serial: 99 | device = Device( 100 | device_serial=device_serial, 101 | is_emulator=is_emulator, 102 | device_num=i, 103 | rest_interval=rest_interval) 104 | self.devices.append(device) 105 | i = i+1 106 | 107 | self.executor = Executor( 108 | devices=self.devices, 109 | app=self.app, 110 | app_path=self.app_path, 111 | strategy_list=self.strategy_list, 112 | pro_click=self.pro_click, 113 | pro_longclick=self.pro_longclick, 114 | pro_scroll=self.pro_scroll, 115 | pro_home=self.pro_home, 116 | pro_edit=self.pro_edit, 117 | pro_naturalscreen=self.pro_naturalscreen, 118 | pro_leftscreen=self.pro_leftscreen, 119 | pro_back=self.pro_back, 120 | pro_splitscreen=self.pro_splitscreen, 121 | emulator_path=self.emulator_path, 122 | android_system=self.android_system, 123 | root_path=self.root_path, 124 | resource_path=self.resource_path, 125 | testcase_count=self.testcase_count, 126 | start_testcase_count=self.start_testcase_count, 127 | event_num=self.event_num, 128 | timeout=self.timeout, 129 | policy_name=self.policy_name, 130 | setting_random_denominator=self.setting_random_denominator, 131 | serial_or_parallel=self.serial_or_parallel, 132 | emulator_name=self.emulator_name, 133 | is_login_app=self.is_login_app, 134 | rest_interval=self.rest_interval, 135 | trace_path=self.trace_path, 136 | choice=self.choice) 137 | 138 | @staticmethod 139 | def get_instance(): 140 | if RegDroid.instance is None: 141 | print("Error: RegDroid is not initiated!") 142 | sys.exit(-1) 143 | return RegDroid.instance 144 | 145 | def start(self): 146 | """ 147 | start interacting 148 | :return: 149 | """ 150 | 151 | if not self.enabled: 152 | return 153 | 154 | if self.timeout > 0: 155 | self.timer = Timer(self.timeout, self.stop) 156 | self.timer.start() 157 | self.start_time = time.time() 158 | 159 | # connect device and install app 160 | if self.is_login_app != 0: 161 | self.devices[0].connect() 162 | self.devices[0].install_app(self.app_path[0]) 163 | self.devices[1].connect() 164 | self.devices[1].install_app(self.app_path[1]) 165 | else: 166 | for device in self.devices: 167 | device.restart(self.emulator_path, self.emulator_name) 168 | device.connect() 169 | 170 | # add some files to the devices 171 | resourcelist = os.listdir(self.resource_path) 172 | for device in self.devices: 173 | device.log_crash(self.root_path+"/" + 174 | device.device_serial+"_logcat.txt") 175 | for resource in resourcelist: 176 | device.add_file(self.resource_path, resource, "/sdcard") 177 | # if "anki" in self.app.package_name: 178 | # device.mkdir("/storage/emulated/0/AnkiDroid/") 179 | # device.add_file(self.resource_path,"collection.anki2","/storage/emulated/0/AnkiDroid/") 180 | 181 | if self.choice == 0: # run 182 | if self.serial_or_parallel == 0: 183 | for strategy in self.strategy_list: 184 | try: 185 | self.executor.start(strategy) 186 | except Exception as e: 187 | traceback.print_exc() 188 | else: 189 | self.executor.start(0) 190 | utils = Utils(self.devices) 191 | # utils.generate_outline_html(self.app.output_path, self.strategy_list) 192 | elif self.choice == 1: # replay 193 | for strategy in self.strategy_list: 194 | self.executor.replay(strategy) 195 | utils = Utils(self.devices) 196 | # utils.generate_replay_all_html(self.app.output_path, self.strategy_list) 197 | elif self.choice == 2: # test 198 | self.executor.test() 199 | else: # screenshot 200 | for device in self.devices: 201 | device.screenshot_and_getstate(self.root_path, 7) 202 | 203 | def resize(self, path): 204 | import cv2 205 | image = cv2.imread(path) 206 | (h, w) = image.shape[:2] 207 | if w > 2000: 208 | (cX, cY) = (w // 2, h // 2) 209 | M = cv2.getRotationMatrix2D((cX, cY), -90, 1.0) 210 | import numpy as np 211 | cos = np.abs(M[0, 0]) 212 | sin = np.abs(M[0, 1]) 213 | nW = int((h * sin) + (w * cos)) 214 | nH = int((h * cos) + (w * sin)) 215 | M[0, 2] += (nW / 2) - cX 216 | M[1, 2] += (nH / 2) - cY 217 | image = cv2.warpAffine(image, M, (nW, nH)) 218 | image = cv2.resize(image, (256, 512)) 219 | cv2.imwrite(path, image) 220 | elif w > 512: 221 | image = cv2.resize(image, (256, 512)) 222 | cv2.imwrite(path, image) 223 | 224 | def stop(self): 225 | # for root, dirs, files in os.walk(self.root_path, topdown=False): 226 | # for name in files: 227 | # if ".png" in name: 228 | # image_path=os.path.join(root, name) 229 | # self.resize(image_path) 230 | self.enabled = False 231 | print(time.time() - self.start_time) 232 | if self.timer and self.timer.isAlive(): 233 | self.timer.cancel() 234 | -------------------------------------------------------------------------------- /RegDroid/start.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import time 4 | import sys 5 | 6 | from regdroid import RegDroid 7 | 8 | 9 | def parse_args(): 10 | """ 11 | parse command line input 12 | """ 13 | parser = argparse.ArgumentParser(description="Start RegDroid to detect regression issues.", 14 | formatter_class=argparse.RawTextHelpFormatter) 15 | parser.add_argument("-pro_click", action="store", dest="pro_click", required=False, type=int, 16 | help="The percentage of click event", default=45) 17 | parser.add_argument("-pro_longclick", action="store", dest="pro_longclick", required=False, type=int, 18 | help="The percentage of click event", default=25) 19 | parser.add_argument("-pro_scroll", action="store", dest="pro_scroll", required=False, type=int, 20 | help="The percentage of click event", default=5) 21 | parser.add_argument("-pro_home", action="store", dest="pro_home", required=False, type=int, 22 | help="The percentage of click event", default=0) 23 | parser.add_argument("-pro_edit", action="store", dest="pro_edit", required=False, type=int, 24 | help="The percentage of click event", default=15) 25 | parser.add_argument("-pro_naturalscreen", action="store", dest="pro_naturalscreen", required=False, type=int, 26 | help="The percentage of click event", default=1) 27 | parser.add_argument("-pro_leftscreen", action="store", dest="pro_leftscreen", required=False, type=int, 28 | help="The percentage of click event", default=80) 29 | parser.add_argument("-pro_back", action="store", dest="pro_back", required=False, 30 | help="The percentage of click event", default=1, type=int) 31 | parser.add_argument("-pro_splitscreen", action="store", dest="pro_splitscreen", required=False, type=int, 32 | help="The percentage of click event", default=0) 33 | parser.add_argument("-app_path", action="append", dest="app_path", required=True, 34 | help="The path of the application you want to test") 35 | parser.add_argument("-is_emulator", action="store", dest="is_emulator", required=False, default=0, type=int, 36 | help="Whether the devices are emulators") 37 | parser.add_argument('-append_device', action='append', dest="append_device", required=True, 38 | help="Serial of the device") 39 | parser.add_argument('-serial_or_parallel', action='store', dest="serial_or_parallel", required=False, default=0, type=int, 40 | help="0 is serial, 1 is parallel") 41 | parser.add_argument("-output", action="append", dest="strategy_list", required=False, 42 | help="set the output directory name") 43 | parser.add_argument("-choice", action="store", dest="choice", required=False, default=0, type=int, 44 | help="Run or replay") 45 | parser.add_argument("-emulator_path", action="store", dest="emulator_path", required=False, default="emulator", 46 | help="Emulator path") 47 | parser.add_argument("-android_system", action="store", dest="android_system", required=False, default="emulator8", 48 | help="System of the device") 49 | parser.add_argument("-root_path", action="store", dest="root_path", required=False, default="../Output/", 50 | help="Output path") 51 | parser.add_argument("-emulator_name", action="store", dest="emulator_name", required=False, default="Android8.0", 52 | help="the emulator name") 53 | parser.add_argument("-resource_path", action="store", dest="resource_path", required=False, default="Document/", 54 | help="Resource path") 55 | parser.add_argument("-testcase_count", action="store", dest="testcase_count", required=False, default=10, type=int, 56 | help="How many testcases are generated for each strategy") 57 | parser.add_argument("-start_testcase", action="store", dest="start_testcase_count", required=False, default=0, type=int, 58 | help="start from which testcase") 59 | parser.add_argument("-event_num", action="store", dest="event_num", required=False, default=100, type=int, 60 | help="How many events are in each test case") 61 | parser.add_argument("-timeout", action="store", dest="timeout", required=False, default=-1, type=int, 62 | help="How long to run at most") 63 | parser.add_argument("-policy_name", action="store", dest="policy_name", required=False, default="random", 64 | help="Policy name") 65 | parser.add_argument("-setting_random_denominator", action="store", dest="setting_random_denominator", required=False, default=5, type=int, 66 | help="Setting random denominator") 67 | parser.add_argument("-app_name", action="store", dest="app_name", required=False, 68 | help="App name") 69 | parser.add_argument("-is_login_app", action="store", dest="is_login_app", required=False, default=1, type=int, 70 | help="0 = app needed to login, 1 = app did not need to login") 71 | parser.add_argument("-rest_interval", action="store", dest="rest_interval", required=False, default=1, type=int, 72 | help="time to sleep") 73 | parser.add_argument("-trace_path", action="store", dest="trace_path", required=False, default="../Trace", 74 | help="path of traces") 75 | 76 | options = parser.parse_args() 77 | # print options 78 | return options 79 | 80 | 81 | def main(): 82 | opts = parse_args() 83 | print(sys.argv) 84 | import os 85 | if not os.path.exists(opts.app_path[0]): 86 | print(f"APK {opts.app_path[0]} does not exist.") 87 | return 88 | if not os.path.exists(opts.app_path[1]): 89 | print(f"APK {opts.app_path[1]} does not exist.") 90 | return 91 | 92 | if len(opts.append_device) < 2: 93 | print("You need to define at least two devices") 94 | return 95 | if len(opts.app_path) != 2: 96 | print("You need to define two apps") 97 | return 98 | 99 | if len(opts.strategy_list)+1 != len(opts.append_device) and opts.serial_or_parallel == 1: 100 | print("You need n+1 devices to execute n strategies") 101 | return 102 | 103 | if len(opts.append_device) > 2 and opts.serial_or_parallel == 0: 104 | print("You can only execute serial strategy in 2 device") 105 | return 106 | 107 | regdroid = RegDroid( 108 | pro_click=opts.pro_click, 109 | pro_longclick=opts.pro_longclick, 110 | pro_scroll=opts.pro_scroll, 111 | pro_home=opts.pro_home, 112 | pro_edit=opts.pro_edit, 113 | pro_naturalscreen=opts.pro_naturalscreen, 114 | pro_leftscreen=opts.pro_leftscreen, 115 | pro_back=opts.pro_back, 116 | pro_splitscreen=opts.pro_splitscreen, 117 | app_path=opts.app_path, 118 | is_emulator=opts.is_emulator, 119 | devices_serial=opts.append_device, 120 | choice=opts.choice, 121 | emulator_path=opts.emulator_path, 122 | android_system=opts.android_system, 123 | root_path=opts.root_path, 124 | resource_path=opts.resource_path, 125 | strategy_list=opts.strategy_list, 126 | testcase_count=opts.testcase_count, 127 | start_testcase_count=opts.start_testcase_count, 128 | event_num=opts.event_num, 129 | timeout=opts.timeout, 130 | policy_name=opts.policy_name, 131 | setting_random_denominator=opts.setting_random_denominator, 132 | serial_or_parallel=opts.serial_or_parallel, 133 | app_name=opts.app_name, 134 | emulator_name=opts.emulator_name, 135 | is_login_app=opts.is_login_app, 136 | rest_interval=opts.rest_interval, 137 | trace_path=opts.trace_path 138 | ) 139 | start_time = time.time() 140 | regdroid.start() 141 | regdroid.stop() 142 | end_time = time.time() 143 | struct_time = time.gmtime(end_time-start_time) 144 | 145 | print('The program finished in{0}year{1}month{2}day{3}hours{4}minutes{5}seconds'.format( 146 | struct_time.tm_year - 1970, 147 | struct_time.tm_mon - 1, 148 | struct_time.tm_mday - 1, 149 | struct_time.tm_hour, 150 | struct_time.tm_min, 151 | struct_time.tm_sec)) 152 | 153 | 154 | if __name__ == "__main__": 155 | main() 156 | -------------------------------------------------------------------------------- /RegDroid/state.py: -------------------------------------------------------------------------------- 1 | 2 | from view import View 3 | 4 | 5 | class State(object): 6 | """ 7 | Record the information of the app's state 8 | """ 9 | 10 | def __init__(self, lines): 11 | self.lines = lines 12 | self.classname_list = [] 13 | self.resourceid_list = [] 14 | self.num_list = [] 15 | self.all_views = self.get_view() 16 | self.views = [] 17 | for view in self.all_views: 18 | if view.level == 2: 19 | self.views.append(view) 20 | 21 | def same_but_not_language(self, state): 22 | for view in self.views: 23 | if "com.google.android.inputmethod.latin" not in view.line and "com.android.systemui" not in view.line: # Decrease accuracy 24 | flag = any(view.same_but_not_language(view2) for view2 in state.views) 25 | if not flag: 26 | return False 27 | return True 28 | 29 | def same(self, state): 30 | for view in self.views: 31 | if "com.google.android.inputmethod.latin" not in view.line and "com.android.systemui" not in view.line: # Decrease accuracy 32 | flag = any(view.same(view2) for view2 in state.views) 33 | if not flag: 34 | return False 35 | return True 36 | 37 | def get_instance(self, view): 38 | # get_instance 39 | flag = False 40 | i = 0 41 | while i < len(self.classname_list): 42 | if self.classname_list[i] == view.className and self.resourceid_list[i] == view.resourceId: 43 | flag = True 44 | self.num_list[i] = self.num_list[i]+1 45 | view.set_instance(self.num_list[i]) 46 | break 47 | i += 1 48 | if flag is False: 49 | self.classname_list.append(view.className) 50 | self.resourceid_list.append(view.resourceId) 51 | self.num_list.append(0) 52 | view.set_instance(0) 53 | return view 54 | 55 | def get_view(self): 56 | all_views = [] 57 | stack = [] 58 | for line in self.lines: 59 | if '' in line: 60 | if not stack: 61 | view = View(line, None, []) 62 | else: 63 | view = View(line, stack[-1], []) 64 | stack[-1].add_son(view) 65 | view = self.get_instance(view) 66 | all_views.append(view) 67 | elif '' in line: 72 | view = stack[-1] 73 | stack.pop() 74 | view = self.get_instance(view) 75 | all_views.append(view) 76 | if stack: 77 | stack[-1].add_son(view) 78 | 79 | return all_views 80 | -------------------------------------------------------------------------------- /RegDroid/style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RegDroid/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import re 4 | import traceback 5 | 6 | from event import Event 7 | 8 | 9 | class Utils(object): 10 | 11 | def __init__(self, devices): 12 | self.devices = devices 13 | 14 | def write_error(self, fail_device, run_count, event_list, f_write, num): 15 | # print("write_error") 16 | event_count = 0 17 | f_write.write("Start::"+str(num+1)+"::run_count::"+str(run_count)+'\n') 18 | for event in event_list: 19 | event_count = event_count+1 20 | if event.view is not None: 21 | f_write.write(str(event.event_count)+"::"+event.action+"::device" + 22 | str(event.device.device_num)+"::"+event.text+"::"+event.view.line) 23 | else: 24 | f_write.write(str(event.event_count)+"::"+event.action+"::device" + 25 | str(event.device.device_num)+"::"+event.text+"::None::"+'\n') 26 | f_write.flush 27 | f_write.write("End::"+'\n'+'\n') 28 | f_write.flush() 29 | 30 | def write_read_event(self, string, event_count, event, device_string, device_count): 31 | f_read_trace = self.devices[device_count].f_read_trace 32 | if string is not None: 33 | f_read_trace.write(str(event_count)+string) 34 | else: 35 | if event.view is not None: 36 | f_read_trace.write(str(event_count)+"::"+event.action+"::"+device_string + 37 | "::"+event.text+"::"+event.view.text+"::"+event.view.description+'\n') 38 | else: 39 | f_read_trace.write(str(event_count)+"::"+event.action + 40 | "::"+device_string+"::"+event.text+"::None::None"+'\n') 41 | f_read_trace.flush() 42 | 43 | def write_one_device_event(self, event, device_count, f_trace): 44 | if event.view is not None: 45 | f_trace.write(str(event.event_count)+"::"+event.action+"::device"+str( 46 | self.devices[device_count].device_num)+"::"+event.text+"::"+event.view.line) 47 | else: 48 | f_trace.write(str(event.event_count)+"::"+event.action+"::device" + 49 | str(self.devices[device_count].device_num)+"::"+event.text+"::None"+'\n') 50 | f_trace.flush() 51 | event.set_device(self.devices[device_count]) 52 | self.devices[device_count].error_event_lists.append(event) 53 | self.devices[device_count].wrong_event_lists.append(event) 54 | 55 | def write_event(self, event, device_count, f_trace): 56 | if event.view is not None: 57 | f_trace.write(str(event.event_count)+"::"+event.action+"::device"+str( 58 | self.devices[device_count].device_num)+"::"+event.text+"::"+event.view.line) 59 | f_trace.write(str(event.event_count)+"::"+event.action+"::device" + 60 | str(self.devices[0].device_num)+"::"+event.text+"::"+event.view.line) 61 | else: 62 | f_trace.write(str(event.event_count)+"::"+event.action+"::device" + 63 | str(self.devices[device_count].device_num)+"::"+event.text+"::None"+'\n') 64 | f_trace.write(str(event.event_count)+"::"+event.action+"::device" + 65 | str(self.devices[0].device_num)+"::"+event.text+"::None"+'\n') 66 | f_trace.flush() 67 | event.set_device(self.devices[0]) 68 | self.devices[device_count].error_event_lists.append(event) 69 | self.devices[device_count].wrong_event_lists.append(event) 70 | new_event = Event(event.view, event.action, 71 | self.devices[device_count], event.event_count) 72 | self.devices[device_count].error_event_lists.append(new_event) 73 | self.devices[device_count].wrong_event_lists.append(new_event) 74 | 75 | def start_thread(self): 76 | for device in self.devices: 77 | if device.thread is not None: 78 | device.thread.start() 79 | for device in self.devices: 80 | if device.thread is not None: 81 | device.thread.join() 82 | 83 | def create_dir(self, path): 84 | if not os.path.isdir(path): 85 | os.makedirs(path) 86 | 87 | def draw_error_frame(self): 88 | for device in self.devices: 89 | import cv2 90 | image = cv2.imread(device.screenshot_path) 91 | cv2.rectangle(image, (1, 1), (1430, 2550), (0, 0, 255), 20) 92 | cv2.imwrite(device.screenshot_path, image) 93 | 94 | def draw_event(self, event): 95 | try: 96 | for device in self.devices: 97 | import cv2 98 | image = cv2.imread(device.screenshot_path) 99 | if device.screenshot_path is not None and event.view is not None: 100 | if event.action == "click": 101 | cv2.rectangle(image, (int(event.view.xmin), int(event.view.ymin)), (int(event.view.xmax), int(event.view.ymax)), (0, 0, 255), 5) 102 | elif event.action == "longclick": 103 | cv2.rectangle(image, (int(event.view.xmin), int(event.view.ymin)), (int(event.view.xmax), int(event.view.ymax)), (0, 225, 255), 5) 104 | elif event.action == "edit": 105 | cv2.rectangle(image, (int(event.view.xmin), int(event.view.ymin)), (int(event.view.xmax), int(event.view.ymax)), (225, 0, 255), 5) 106 | else: 107 | cv2.rectangle(image, (int(event.view.xmin), int(event.view.ymin)), (int(event.view.xmax), int(event.view.ymax)), (225, 225, 255), 5) 108 | else: 109 | if event.action == "wrong": 110 | cv2.rectangle(image, (0, 0), (1430, 2550), (0, 225, 255), 20) 111 | else: 112 | cv2.putText(image, event.action, (100, 300), 113 | cv2.FONT_HERSHEY_SIMPLEX, 5, (0, 0, 255), 1, cv2.LINE_AA) 114 | # image=cv2.resize(image, (256, 512)) 115 | cv2.imwrite(device.screenshot_path, image) 116 | except Exception: 117 | traceback.print_exc() 118 | 119 | def generate_html(self, path, html_path, run_count): 120 | line_list = [] 121 | f_html = open(os.path.join(html_path, str(run_count) + 122 | "_trace.html"), 'w', encoding='utf-8') 123 | f_style = open("style.html", 'r', encoding='utf-8') 124 | f_write_trace = open(os.path.join( 125 | path, "read_trace.txt"), 'r', encoding='utf-8') 126 | lines_trace = f_write_trace.readlines() 127 | img_list = os.listdir(path+"/screen") 128 | new_str = "" 148 | old_str = "" 149 | 150 | for line in f_style: 151 | f_html.write(re.sub(old_str, new_str, line)) 152 | 153 | def is_number(self, str): 154 | try: 155 | if str == 'NaN': 156 | return False 157 | float(str) 158 | return True 159 | except ValueError: 160 | return False 161 | 162 | def find_action_in_file(self, state_num, lines): 163 | for line in lines: 164 | line_num = line[0:line.find("::")] 165 | if self.is_number(line_num) and self.is_number(state_num): 166 | if float(state_num) + 1.0 == float(line_num): 167 | line = line[line.find("::") + 2 : len(line)] 168 | action = line[0:line.find("::")] 169 | return state_num + "::" + action 170 | return state_num 171 | 172 | def print_dividing_line(self, success, event_count): 173 | if success is False: 174 | print(str(event_count) + "fail device_0-------------------") 175 | else: 176 | print(str(event_count) + "---------------------------------") 177 | 178 | def generate_outline_html(self, output_path, strategy_list): 179 | outline_path = output_path + '/all_run_bugs.html' 180 | f_outline_html = open(outline_path, 'w+', encoding="UTF-8") 181 | 182 | insert_lines = '\n' \ 183 | '\n' \ 184 | '\n' \ 185 | 'All bug report\n' \ 186 | '\n' \ 187 | '\n' 188 | 189 | tip = "
\n" \ 190 | "

" \ 191 | "This is a bug page that runs the regdroid tool" \ 192 | "

\n" \ 193 | "

All the bug connections are listed below\n" \ 194 | "

\n" \ 195 | "

\n" \ 196 | "-----------------------------------------------------------------------------------" \ 197 | "

\n" \ 198 | "

" \ 199 | "X-Picture:Y means image index(Y) in the test trace(X) of a policy" \ 200 | "

\n" \ 201 | "

" \ 202 | "You can click on it to jump to another detaied page" \ 203 | "

\n" \ 204 | "

\n" 205 | insert_lines = insert_lines + tip 206 | bug_index = 1 207 | for strategy in strategy_list: 208 | insert_lines = insert_lines + '
' 209 | directory_num_list = [] 210 | bug_event_num_list = [] 211 | bug_file_name = output_path + "/strategy_" + strategy + "/error_realtime.txt" 212 | lines = open(bug_file_name, 'r', encoding="UTF-8").readlines() 213 | step = 2 214 | lines__ = [lines[i:i + step] for i in range(0, len(lines), step)] 215 | temp = 0 216 | for line in lines__: 217 | find_str1 = "run_count" 218 | find_str2 = "End" 219 | if len(line) > 1: 220 | if line[0].find(find_str1) > -1: 221 | directory_num_list.append( 222 | line[0][line[0].find(find_str1) + 11:]) 223 | elif line[1].find(find_str1) > -1: 224 | directory_num_list.append( 225 | line[1][line[1].find(find_str1) + 11:]) 226 | elif line[0].find(find_str2) > -1: 227 | bug_event_num_list.append(temp) 228 | elif line[1].find(find_str2) > -1: 229 | bug_event_num_list.append( 230 | line[0][0:line[0].find("::")]) 231 | temp = line[1][0:4] 232 | else: 233 | if line[0].find(find_str1) > -1: 234 | directory_num_list.append( 235 | line[0][line[0].find(find_str1) + 11:]) 236 | elif line[0].find(find_str2) > -1: 237 | bug_event_num_list.append(temp) 238 | 239 | if len(directory_num_list) == 0: 240 | continue 241 | 242 | insert_lines = insert_lines + '

Policy: ' + strategy + '

' + '\n' 243 | 244 | for directory_num, bug_event_num in zip(directory_num_list, bug_event_num_list): 245 | directory_num = directory_num.strip('\n') 246 | insert_lines = insert_lines + 'Bug' + str(bug_index) + ': '\ 247 | '' \ 249 | + directory_num.replace('\n', '') + \ 250 | '-Picture:' + bug_event_num + '
\n' 251 | bug_index = bug_index + 1 252 | insert_lines = insert_lines + '
\n' \ 253 | '\n' \ 254 | '\n' 255 | f_outline_html.write(''.join(insert_lines)) 256 | f_outline_html.close() 257 | 258 | def generate_replay_all_html(self, output_path, strategy_list): 259 | outline_path = output_path + '/all_replay_bugs.html' 260 | f_outline_html = open(outline_path, 'w+', encoding="UTF-8") 261 | insert_lines = '\n' \ 262 | '\n' \ 263 | '\n' \ 264 | 'All bug report\n' \ 265 | '\n' \ 266 | '\n' 267 | 268 | tip = "
\n" \ 269 | "

" \ 270 | "This is a bug page that runs the regdroid tool" \ 271 | "

\n" \ 272 | "

All the bug connections are listed below\n" \ 273 | "

\n" \ 274 | "

\n" \ 275 | "-----------------------------------------------------------------------------------" \ 276 | "

\n" \ 277 | "

" \ 278 | "X_trace.html is a bug's hayperlink, you can click on it" \ 279 | "

\n" \ 280 | "

" \ 281 | "And then slide to the last few images to check the bug" \ 282 | "

\n" \ 283 | "

\n" 284 | insert_lines = insert_lines + tip + '
\n' 285 | bug_index = 1 286 | for strategy in strategy_list: 287 | 288 | replay_base_path = output_path + "/strategy_" + strategy + "/error_replay/" 289 | files = os.listdir(replay_base_path) 290 | temp_insert_lines = "" 291 | have_html = False 292 | for file in files: 293 | replay_path = replay_base_path + file 294 | html_files = os.listdir(replay_path) 295 | have_html = False 296 | 297 | for html in html_files: 298 | if html.find('html') > -1: 299 | have_html = True 300 | temp_insert_lines = temp_insert_lines + 'Bug' + str(bug_index) + ': '\ 301 | '' \ 303 | + html.replace('\n', '') + \ 304 | '
\n' 305 | if have_html: 306 | insert_lines = insert_lines + '

Policy: ' + strategy + '

\n' 307 | insert_lines = insert_lines + temp_insert_lines 308 | have_html = False 309 | insert_lines = insert_lines + '
\n' \ 310 | '\n' \ 311 | '\n' 312 | f_outline_html.write(''.join(insert_lines)) 313 | f_outline_html.close() 314 | -------------------------------------------------------------------------------- /RegDroid/view.py: -------------------------------------------------------------------------------- 1 | 2 | class View(object): 3 | 4 | def __init__(self, line, father, sons): 5 | self.level = line.find('