├── .vscode
└── settings.json
├── language
├── translator.py
├── generate.py
└── merge.py
├── logcat
├── .DS_Store
├── analyse.py
└── collect.py
├── bin
└── apktool
│ ├── apktool.jar
│ └── apktool.sh
├── .gitignore
├── config
├── languages.json
├── app.json
└── baidu.json
├── apktool
├── config.json
├── godeye.py
├── apktool.py
└── smali.py
├── files
├── textfiles.py
├── jsonfiles.py
└── xmlfiles.py
├── global_config.py
├── config.py
├── launcher.py
├── README.md
├── android
├── pm.py
├── adb.py
├── am.py
├── am.md
└── pm.md
├── generator.py
├── initializer.py
├── repository.py
├── translator.py
├── file_operator.py
├── importer.py
├── app_gui.py
└── LICENSE
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "/usr/local/bin/python3"
3 | }
--------------------------------------------------------------------------------
/language/translator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 |
--------------------------------------------------------------------------------
/language/generate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/logcat/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/AndroidTools/HEAD/logcat/.DS_Store
--------------------------------------------------------------------------------
/bin/apktool/apktool.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/AndroidTools/HEAD/bin/apktool/apktool.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python:
2 | *.py[cod]
3 | *.so
4 | *.egg
5 | *.egg-info
6 | *.xls
7 | *.xml
8 | dist
9 | build
10 | __pycahce__
--------------------------------------------------------------------------------
/config/languages.json:
--------------------------------------------------------------------------------
1 | {
2 | "app_title": {
3 | "zh": "Translate My App",
4 | "en": "Translate My App"
5 | }
6 | }
--------------------------------------------------------------------------------
/apktool/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": "",
3 | "keywords": ["sysdebug"],
4 | "traceback": true,
5 | "traceback_generation": 3,
6 | "result_store_path": "."
7 | }
8 |
--------------------------------------------------------------------------------
/files/textfiles.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | def read_text(fname) -> str:
5 | '''Read text file full content.'''
6 | with open(fname, 'r') as f:
7 | content = f.read()
8 | f.close()
9 | return content
10 |
--------------------------------------------------------------------------------
/files/jsonfiles.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import json
5 |
6 | def write_json(path, json_obj):
7 | '''Write json object to json file.'''
8 | json_str = json.dumps(json_obj)
9 | with open(path, "w") as f:
10 | f.write(json_str)
11 |
12 | def read_json(path):
13 | '''Read json object from json file.'''
14 | with open(path, "r") as f:
15 | return json.load(f)
16 |
--------------------------------------------------------------------------------
/global_config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 |
6 | def config_logging(filename: str = 'app.log'):
7 | '''Config loggin library globaly.'''
8 | LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
9 | DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
10 | logging.basicConfig(filename=filename, filemode='a', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)
11 | logging.FileHandler(filename=filename, encoding='utf-8')
12 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | API_URL = 'http://api.fanyi.baidu.com/api/trans/vip/translate' # 百度语音识别 API
5 | BAIDU_CONFIG_URL = "config/baidu.json"
6 | APP_CONFIG_PATH = "config/app.json" # 应用配置文件
7 | REPO_CONFIG_PATH = "config/repo.json" # 仓库配置文件地址
8 | REPO_CONFIG_PATH_PREFIX = "config/repo_%s.json"
9 | APP_LANGUAGE_CONFIG_PATH = "config/languages.json" # 应用多语言配置文件
10 | TRANSLATE_EXCEL_SHEET_NAME = "Translation"
11 | TRANSLATE_EXCEL_FILE_NAME = "Translations.xlsx"
12 |
--------------------------------------------------------------------------------
/config/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "zh",
3 | "support_languages": [],
4 | "languages": {
5 | "中文简体": ["zh", "zh-rCN"],
6 | "中文(台湾)": ["zh-rTW"],
7 | "中文(香港)": ["zh-rHK"],
8 | "德语": ["de-rAT", "de-rCH", "de-rDE", "de-rLI"],
9 | "法语": ["fr-rBE", "fr-rCA", "fr-rCH", "fr-rFR"],
10 | "日语": ["ja"],
11 | "英语": [
12 | "en",
13 | "en-rUS",
14 | "en-rGB",
15 | "en-rAU",
16 | "en-rCA",
17 | "en-rIE",
18 | "en-rIN",
19 | "en-rNZ",
20 | "en-rSG",
21 | "en-rZA"
22 | ],
23 | "韩语": ["ko", "ko-rKR"]
24 | },
25 | "ios_language_black_list": [],
26 | "android_language_black_list": [],
27 | "ios_resources_root_directory": "",
28 | "android_resources_root_directory": "",
29 | "translate_excel_output_directory": ""
30 | }
31 |
--------------------------------------------------------------------------------
/launcher.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import logging
5 | import tkinter
6 | from tkinter import Tk
7 | from initializer import LanguageFactory
8 | from initializer import Initializer
9 | from app_gui import MainDialog
10 | from app_gui import RepoInitDialog
11 |
12 | def __config_logging():
13 | '''配置日志'''
14 | LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
15 | DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
16 | logging.basicConfig(filename='app.log', filemode='a', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT)
17 | logging.FileHandler(filename='app.log', encoding='utf-8')
18 |
19 | if __name__ == "__main__":
20 | __config_logging()
21 | # 加载多语言资源
22 | factory = LanguageFactory()
23 | factory.load_languages()
24 | # Tk
25 | root = Tk()
26 | root.title(factory.get_entry("app_title"))
27 | # 进行项目初始化
28 | initializer = Initializer()
29 | if not initializer.is_repo_initialized() :
30 | # 进入项目初始化页面
31 | RepoInitDialog(root).pack()
32 | else:
33 | # 进入正常编辑页面
34 | MainDialog(root).pack()
35 | root.mainloop()
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android 脚本工具合集
2 |
3 | 汇集了开发过程中会用到的多种脚本工具。
4 |
5 | ## 1、多语言管理工具
6 |
7 | ### 1.1 多语言合并脚本工具
8 |
9 | 用来将一个多语言资源文件合并到另一个多语言资源文件。比如,将别人翻译或者修改的多语言合并到主干。该脚本通过对 key 对比实现合并,不改变之前多语言资源的顺序。
10 |
11 | 进入 language 目录,执行命令,
12 |
13 | ```
14 | python merge.py -f 被合并的多语言资源文件位置 -t 合并到的多语言资源文件位置
15 | ```
16 |
17 | ### 1.2 根据 Android 多语言资源生成 iOS 多语言文件
18 |
19 | 根据 Android 的多语言资源文件和目录,生成 iOS 对应的多语言资源文件或者目录。
20 |
21 | ```
22 | python generate.py -f 用来生成的资源文件 -o 输出到的位置
23 | ```
24 |
25 | ### 1.3 将多语言文件翻译成其他语言
26 |
27 | 翻译多语言文件成其他语言,支持指定被翻译多语言文件和输出到的位置,如果已经存在指定的词条,则无需翻译,只对没有翻译结果的进行翻译。
28 |
29 | ```
30 | python translate.py -f 被翻译的资源文件 -o 输出到的位置
31 | ```
32 |
33 | 也可以直接指定要翻译的多语言的目录,此时根据目录名自动识别语言类型,然后根据默认多语言,补充和翻译不存在的词条,
34 |
35 | ```
36 | python translate.py -f 被翻译的资源的目录
37 | ```
38 |
39 | ### 1.4 根据 Android 多语言资源生成 Excel
40 |
41 | 根据 Android 多语言资源文件或者目录生成 Excel,如果传入的参数是文件只生成其自己对应的 Excel;如果传入的是目录,则每个语种对应的文件生成一个 sheet:
42 |
43 | ```
44 | python genexcel.py -f 用来生成的文件或者目录
45 | ```
46 |
47 | ## 2、日志采集和分析工具
48 |
49 | ### 2.1 日志采集工具
50 |
51 | 自动采集 Android 某个应用或者进程的日志并输出到文件中,便于对日志文件进行分析。使用:进入 logcat 目录,执行命令,
52 |
53 | ```
54 | python collect.py -p 你的包名 -l 输出日志文件位置 -f yes
55 | ```
56 |
57 | ### 2.2 日志分析工具
58 |
59 | 对上述采集到的日志文件进行分析,从大到小输出打印最多的日志等。使用:进入 logcat 目录,执行命令,
60 |
61 | ```
62 | python analyse.py -f 日志文件地址 -p 包名
63 | ```
64 |
65 | ## 3、上帝之眼
66 |
67 |
--------------------------------------------------------------------------------
/apktool/godeye.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | '''
5 | ///////////////////////////////////////// .........
6 | // .. ..... ..
7 | // The Android Smali File Searcher ... ........ ...
8 | // ... ......... ...
9 | // @Author Shouheng Wang .............. ...
10 | // ...............
11 | ///////////////////////////////////////// .........
12 | '''
13 |
14 | import os, sys, time, shutil, logging
15 | from typing import List
16 | from smali import SmaliSearcherConfiguration
17 | sys.path.insert(0, '../')
18 | import global_config
19 | from files.jsonfiles import read_json
20 |
21 | SMALI_SEARCHER_CONFIGURATION_FILE = "config.json"
22 |
23 | def read_configuration() -> SmaliSearcherConfiguration:
24 | '''Read smali searcher configuration.'''
25 | json_object = read_json(SMALI_SEARCHER_CONFIGURATION_FILE)
26 | configuration = SmaliSearcherConfiguration()
27 | configuration.package = json_object["package"]
28 | configuration.keywords = json_object["keywords"]
29 | configuration.traceback = json_object["traceback"]
30 | configuration.traceback_generation = json_object["traceback_generation"]
31 | return configuration
32 |
33 |
34 |
35 | if __name__ == "__main__":
36 | '''Program entrance.'''
37 | global_config.config_logging('../log/app.log')
38 |
--------------------------------------------------------------------------------
/config/baidu.json:
--------------------------------------------------------------------------------
1 | {
2 | "app_id": "your_app_id",
3 | "app_secret": "your_app_secret",
4 | "mappings": {
5 | "zh": "zh",
6 | "ja": "jp",
7 | "zh-rCN": "cht",
8 | "zh-rTW": "cht",
9 | "zh-rHK": "yue",
10 | "en": "en",
11 | "en-rUS": "en",
12 | "en-rGB": "en",
13 | "en-rAU": "en",
14 | "en-rCA": "en",
15 | "en-rIE": "en",
16 | "en-rIN": "en",
17 | "en-rNZ": "en",
18 | "en-rSG": "en",
19 | "en-rZA": "en",
20 | "ar-rEG": "ara",
21 | "ar-rIL": "ara",
22 | "de-rAT": "de",
23 | "de-rCH": "de",
24 | "de-rDE": "de",
25 | "de-rLI": "de",
26 | "ko-rKR": "kor",
27 | "ko": "kor",
28 | "th-rTH": "th",
29 | "pt-rBR": "pt",
30 | "pt-rPT": "pt",
31 | "el-rGR": "el",
32 | "bg-rBG": "bul",
33 | "fi-rFI": "fin",
34 | "sl-rSI": "slo",
35 | "fr-rBE": "fra",
36 | "fr-rCA": "fra",
37 | "fr-rCH": "fra",
38 | "fr-rFR": "fra",
39 | "nl-BE": "nl",
40 | "nl-rNL": "nl",
41 | "cs-rCZ": "cs",
42 | "sv-rSE": "swe",
43 | "vi-rVN": "vie",
44 | "es-rES": "spa",
45 | "es-rUS": "spa",
46 | "es": "spa",
47 | "ru": "ru",
48 | "ru-rRU": "ru",
49 | "it-rCH": "it",
50 | "it-rIT": "it",
51 | "pl-rPL": "pl",
52 | "da-rDK": "dan",
53 | "ro-rRO": "rom",
54 | "hu-rHU": "hu"
55 | }
56 | }
--------------------------------------------------------------------------------
/bin/apktool/apktool.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright (C) 2007 The Android Open Source Project
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # This script is a wrapper for smali.jar, so you can simply call "smali",
18 | # instead of java -jar smali.jar. It is heavily based on the "dx" script
19 | # from the Android SDK
20 |
21 | # Set up prog to be the path of this script, including following symlinks,
22 | # and set up progdir to be the fully-qualified pathname of its directory.
23 | prog="$0"
24 | while [ -h "${prog}" ]; do
25 | newProg=`/bin/ls -ld "${prog}"`
26 |
27 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
28 | if expr "x${newProg}" : 'x/' >/dev/null; then
29 | prog="${newProg}"
30 | else
31 | progdir=`dirname "${prog}"`
32 | prog="${progdir}/${newProg}"
33 | fi
34 | done
35 | oldwd=`pwd`
36 | progdir=`dirname "${prog}"`
37 | cd "${progdir}"
38 | progdir=`pwd`
39 | prog="${progdir}"/`basename "${prog}"`
40 | cd "${oldwd}"
41 |
42 | jarfile=apktool.jar
43 | libdir="$progdir"
44 | if [ ! -r "$libdir/$jarfile" ]
45 | then
46 | echo `basename "$prog"`": can't find $jarfile"
47 | exit 1
48 | fi
49 |
50 | javaOpts=""
51 |
52 | # If you want DX to have more memory when executing, uncomment the following
53 | # line and adjust the value accordingly. Use "java -X" for a list of options
54 | # you can pass here.
55 | #
56 | javaOpts="-Xmx512M -Dfile.encoding=utf-8"
57 |
58 | # Alternatively, this will extract any parameter "-Jxxx" from the command line
59 | # and pass them to Java (instead of to dx). This makes it possible for you to
60 | # add a command-line parameter such as "-JXmx256M" in your ant scripts, for
61 | # example.
62 | while expr "x$1" : 'x-J' >/dev/null; do
63 | opt=`expr "$1" : '-J\(.*\)'`
64 | javaOpts="${javaOpts} -${opt}"
65 | shift
66 | done
67 |
68 | if [ "$OSTYPE" = "cygwin" ] ; then
69 | jarpath=`cygpath -w "$libdir/$jarfile"`
70 | else
71 | jarpath="$libdir/$jarfile"
72 | fi
73 |
74 | # add current location to path for aapt
75 | PATH=$PATH:`pwd`;
76 | export PATH;
77 | exec java $javaOpts -Djava.awt.headless=true -jar "$jarpath" "$@"
--------------------------------------------------------------------------------
/files/xmlfiles.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | from xml.dom.minidom import parse
5 | import xml.dom.minidom
6 | import logging
7 |
8 | def read_android_strings(fname):
9 | '''Read android languages.'''
10 | dist = {}
11 | try:
12 | # Use minidom to open xml.
13 | DOMTree = xml.dom.minidom.parse(fname)
14 | except Exception:
15 | logging.error("Failed to read Android strings: " + fname)
16 | return dist
17 | collection = DOMTree.documentElement
18 | strings = collection.getElementsByTagName("string")
19 | index = 0
20 | for string in strings:
21 | index += 1
22 | try:
23 | keyword = string.getAttribute('name')
24 | translation = ""
25 | if len(string.childNodes) == 1:
26 | node = string.childNodes[0]
27 | # Handle CDATA label.
28 | if node.nodeType == 4:
29 | translation = ""
30 | else:
31 | translation = node.data
32 | else:
33 | # Handle child nodes.
34 | for node in string.childNodes:
35 | # Handle CDATA label.
36 | if node.nodeType == 4:
37 | translation = translation + ""
38 | else:
39 | translation = translation + node.toxml()
40 | # Do filter for some chars.
41 | if '\\\'' in translation:
42 | translation = translation.replace('\\\'', '\'')
43 | dist[keyword] = translation
44 | except BaseException as e:
45 | logging.error("Invalid entry at index " + str(index) + " " + str(keyword) + " : " + str(e))
46 | return dist
47 |
48 | def write_android_resources(dist, fname):
49 | '''Write android string resources.'''
50 | content = '\n\n'
51 | for k, v in dist.items():
52 | # Handle chars etc.
53 | if '\'' in v:
54 | v = v.replace("\'", "\\\'")
55 | # Handle > and <
56 | if ('>' in v or '<' in v) and '', '>')
58 | v = v.replace('<', '<')
59 | # Handle …
60 | if '…' in v and '' + v + '\n'
64 | content += ''
65 | with open(fname, 'w', encoding='utf-8') as f:
66 | f.write(content)
67 |
--------------------------------------------------------------------------------
/language/merge.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | '''
5 | ////////////////////////////////////////////////////////////////////
6 | // Android language files merge tool
7 | // Usage:
8 | // python merge.py
9 | // -f the_path_of_langauge_file_to_merge_from
10 | // -t the_path_of_language_file_to_merge_to
11 | // @Author: Mr.Shouheng
12 | ////////////////////////////////////////////////////////////////////
13 | '''
14 |
15 | import sys, getopt
16 | import logging
17 | import sys
18 | sys.path.insert(0, '../')
19 | import global_config
20 | from files.xmlfiles import read_android_strings, write_android_resources
21 |
22 | command_info = "\
23 | Options: \n\
24 | -h[--help] Help info\n\
25 | -f[--from] Languages file to merge from\n\
26 | -t[--to] Languages file to merge to\
27 | "
28 |
29 | def __show_invalid_command(info: str):
30 | '''Show invliad command info.'''
31 | print('Error: Unrecognized command: %s' % info)
32 | print(command_info)
33 |
34 | class MergeInfo:
35 | ''''Merge info object.'''
36 | def __init__(self, merge_from: str, merge_to: str):
37 | self.merge_from = merge_from
38 | self.merge_to = merge_to
39 |
40 | def __parse_command(argv) -> MergeInfo:
41 | '''Parse merge info from input command.'''
42 | try:
43 | # : and = means accept arguments
44 | opts, args = getopt.getopt(argv, "-h:-f:-t:", ["help", "from=", 'to='])
45 | except BaseException as e:
46 | __show_invalid_command(str(e))
47 | sys.exit(2)
48 | if len(opts) == 0:
49 | __show_invalid_command('empty parameters')
50 | return
51 | merge_from = merge_to = None
52 | for opt, arg in opts:
53 | if opt in ('-f', '--from'):
54 | merge_from = arg
55 | elif opt in ('-t', '--to'):
56 | merge_to = arg
57 | elif opt in ('-h', '--help'):
58 | print(command_info)
59 | return
60 | if merge_from == None or merge_to == None:
61 | __show_invalid_command('Launchage merge file required')
62 | return
63 | return MergeInfo(merge_from, merge_to)
64 |
65 | def __do_merge(info: MergeInfo):
66 | ''''Merge launguage files.'''
67 | strings_from = read_android_strings(info.merge_from)
68 | strings_to = read_android_strings(info.merge_to)
69 | for k, v in strings_from.items():
70 | strings_to[k] = v
71 | write_android_resources(strings_to, info.merge_to)
72 |
73 | def __execute(argv):
74 | ''''Execute command.'''
75 | # Parse command.
76 | info = __parse_command(argv)
77 | if info == None:
78 | return
79 | logging.debug(">> Merging launguages from [" + str(info.merge_from) + "] to [" + str(info.merge_to)+ "].")
80 | __do_merge(info)
81 |
82 | if __name__ == "__main__":
83 | '''Program entrance.'''
84 | global_config.config_logging('../log/app.log')
85 | __execute(sys.argv[1:])
86 |
--------------------------------------------------------------------------------
/android/pm.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # ////////////////////////////////////////////////////////////////////
5 | #
6 | # The Android Debug Bridge Wrapper Project by Python
7 | #
8 | # @Author Mr.Shouheng
9 | #
10 | # @References:
11 | # - https://developer.android.com/studio/command-line/adb?hl=zh-cn#am
12 | #
13 | # ////////////////////////////////////////////////////////////////////
14 |
15 | from adb import *
16 | from typing import List
17 |
18 | class PackageInfo:
19 | def __init__(self, pkg: str = '', path: str = '', uid: str = '', vcode: str = '') -> None:
20 | self.pkg = pkg
21 | self.path = path
22 | self.uid = uid
23 | self.vcode = vcode
24 |
25 | def __str__(self) -> str:
26 | return "(%s, %s, %s, %s)" % (self.pkg, self.path, self.uid, self.vcode)
27 |
28 | def get_packages(serial: str = None) -> List[PackageInfo]:
29 | '''Get all packages'''
30 | out = os_popen("adb %s shell pm list packages -f -U -u --show-versioncode ", serial)
31 | infos = []
32 | for line in out.split("\n"):
33 | info = _parse_package_line(line)
34 | infos.append(info)
35 | return infos
36 |
37 | def get_package_info(pkg: str, serial: str = None) -> PackageInfo:
38 | '''Get info of one package.'''
39 | out = os_popen("adb %s shell pm list packages -f -U -u --show-versioncode | grep %s ", serial, pkg)
40 | return _parse_package_line(out)
41 |
42 | def download_apk_of_package(pkg: str, to: str, serial: str = None) -> int:
43 | '''Download APK of given package name to given path.'''
44 | info = get_package_info(pkg, serial)
45 | if len(info.path) > 0:
46 | return pull(info.path, to, serial)
47 | return -1
48 |
49 | def _parse_package_line(line: str) -> PackageInfo:
50 | '''Parse package info from line text.'''
51 | parts = line.split(" ")
52 | info = PackageInfo()
53 | for part in parts:
54 | if len(part) > 0:
55 | pairs = part.split(":")
56 | if len(pairs) >= 2:
57 | if pairs[0] == "package":
58 | index = pairs[1].rindex("=")
59 | info.path = pairs[1][0:index]
60 | info.pkg = pairs[1][index+1:]
61 | elif pairs[0] == "versionCode":
62 | info.vcode = pairs[1]
63 | elif pairs[0] == "uid":
64 | info.uid = pairs[1]
65 | return info
66 |
67 | if __name__ == "__main__":
68 | TEST_PACKAGE_NAME = "me.shouheng.leafnote"
69 | TEST_DEVICE_SERIAL = "emulator-5556"
70 | TEST_APK_PATH = "/Users/wangshouheng/downloads/Snapdrop-0.3.apk"
71 | TEST_LOCAL_FILE = "adb.py"
72 | TEST_REMOTE_DIR = "/sdcard"
73 | TEST_REMOTE_FILE = "/sdcard/adb.py"
74 | # print_list(get_packages(TEST_DEVICE_SERIAL))
75 | # print(get_package_info(TEST_PACKAGE_NAME, TEST_DEVICE_SERIAL))
76 | print(download_apk_of_package(TEST_PACKAGE_NAME, "~/leafnote.apk", TEST_DEVICE_SERIAL))
77 | # os_system("adb %s shell pm > ./pm", TEST_DEVICE_SERIAL)
78 |
--------------------------------------------------------------------------------
/generator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import logging
6 | from repository import repository as repository
7 | from file_operator import ExcelOperator as ExcelOperator
8 | from file_operator import XmlOperator as XmlOperator
9 | from file_operator import FileOperator as FileOperator
10 | from config import TRANSLATE_EXCEL_SHEET_NAME
11 | from config import TRANSLATE_EXCEL_FILE_NAME
12 |
13 | # 文件生成工具类
14 | class Generator:
15 | # 初始化
16 | def __init__(self, appConfig):
17 | self.appConfig = appConfig
18 |
19 | # 根据需要翻译的词条生成用于翻译的 excel
20 | def gen_translate_excel(self, output_dir):
21 | repository.load()
22 | dist = {}
23 | for data in repository.datas:
24 | # 增加词条列
25 | keyword = data["keyword"]
26 | if "Keyword" not in dist:
27 | dist["Keyword"] = []
28 | dist["Keyword"].append(keyword)
29 | # 增加翻译列
30 | translates = data["translates"]
31 | for k, v in translates.items():
32 | if k not in dist:
33 | dist[k] = []
34 | dist[k].append(v)
35 | # 生成 Excel 文件
36 | excelOperator = ExcelOperator()
37 | out_file = os.path.join(self.appConfig.translate_excel_output_directory, TRANSLATE_EXCEL_FILE_NAME)
38 | excelOperator.write_excel(dist, out_file)
39 | return True
40 |
41 | # 生成 Android 多语言资源
42 | def gen_android_resources(self):
43 | repository.load()
44 | android_blacklist = self.appConfig.android_language_black_list
45 | xmlOperator = XmlOperator()
46 | for language in repository.languages:
47 | # 过滤黑名单
48 | if language in android_blacklist:
49 | continue
50 | dist = {}
51 | for data in repository.datas:
52 | keyword = data["keyword"]
53 | translates = data["translates"]
54 | translation = translates[language]
55 | dist[keyword] = translation
56 | # 写入资源
57 | filename = "values" if language == "default" else "values-" + language
58 | language_dir = os.path.join(self.appConfig.android_resources_root_directory, filename)
59 | if not os.path.exists(language_dir):
60 | os.mkdir(language_dir)
61 | fname = os.path.join(language_dir, "strings.xml")
62 | xmlOperator.write_android_resources(dist, fname)
63 |
64 | # 生成 iOS 多语言资源
65 | def gen_ios_resources(self):
66 | repository.load()
67 | ios_blacklist = self.appConfig.ios_language_black_list
68 | fileOperator = FileOperator()
69 | for language in repository.languages:
70 | # 过滤黑名单
71 | if language in ios_blacklist:
72 | continue
73 | dist = {}
74 | for data in repository.datas:
75 | keyword = data["keyword"]
76 | translates = data["translates"]
77 | translation = translates[language]
78 | dist[keyword] = translation
79 | # 写入资源
80 | language_dir = os.path.join(self.appConfig.ios_resources_root_directory, language + ".lproj")
81 | if not os.path.exists(language_dir):
82 | os.mkdir(language_dir)
83 | fname = os.path.join(language_dir, "Localizable.strings")
84 | fileOperator.write_ios_resources(dist, fname)
85 |
--------------------------------------------------------------------------------
/apktool/apktool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | '''
5 | /////////////////////////////////////////........................
6 | // ........................
7 | // The APK Tool Wrapper ........................
8 | // ........................
9 | /////////////////////////////////////////........................
10 | '''
11 |
12 | import os, sys, shutil, logging
13 | import hashlib
14 | import traceback
15 | from typing import List
16 | sys.path.insert(0, '../')
17 | import global_config
18 |
19 | SMALI_MIX_DIRECTORY = "smali_mix"
20 | ERROR_APK_FILE_NOT_EXISTS = "-1"
21 |
22 | class DecompileConfiguration:
23 | def __init__(self) -> None:
24 | # Should delete and re-decompile if given decompiled files or mixed smali files exists.
25 | self.force = False
26 |
27 | def decompile(apk: str, configuration: DecompileConfiguration) -> str:
28 | '''Decompile the APK.'''
29 | if not os.path.exists(apk):
30 | return ERROR_APK_FILE_NOT_EXISTS
31 | with open(apk, 'rb') as fp:
32 | data = fp.read()
33 | file_md5 = hashlib.md5(data).hexdigest()
34 | out = "./workspace_%s" % file_md5
35 | if os.path.exists(out) and not configuration.force:
36 | return os.path.join(out, SMALI_MIX_DIRECTORY)
37 | try:
38 | os.system("sh ../bin/apktool/apktool.sh D %s -o %s" % (apk, out))
39 | except BaseException as e:
40 | logging.error('Error while decopiling:\n %s' % traceback.format_exc())
41 | return out
42 |
43 | def mix_all_smalis(dir: str, configuration: DecompileConfiguration) -> str:
44 | '''
45 | Mix all smali directories under given dir.
46 | '''
47 | mix_to = os.path.join(dir, SMALI_MIX_DIRECTORY)
48 | if os.path.exists(mix_to) and not configuration.force:
49 | return mix_to
50 | files = os.listdir(dir)
51 | smalis = []
52 | for f in files:
53 | if f.startswith("smali") and f != SMALI_MIX_DIRECTORY:
54 | # search_under_smali(os.path.join(dir, f), configuration)
55 | smalis.append(os.path.join(dir, f))
56 | logging.info("Mixing " + str(smalis))
57 | # Mix all smali files internal.
58 | return _mix_all_smalis(mix_to, smalis)
59 |
60 | def decompile_and_mix_all_smalis(apk: str, configuration: DecompileConfiguration) -> str:
61 | '''Decompile given apk, mix all smali files together and return the mixed path.'''
62 | decompile_dir = decompile(apk, configuration)
63 | if decompile_dir == ERROR_APK_FILE_NOT_EXISTS:
64 | return ERROR_APK_FILE_NOT_EXISTS
65 | else:
66 | return mix_all_smalis(decompile_dir, configuration)
67 |
68 | def _mix_all_smalis(mix_to: str, smalis: List[str]) -> str:
69 | '''
70 | Mix all smali directories to one to reduce the complexity of the alogrithm.
71 | Also we can make a more interesting things based on mixed smalis.
72 | '''
73 | progress = 1
74 | for smali in smalis:
75 | print(" >>> Mixing [%s] %d%%" % (smali, progress*100/len(smalis)), end = '\r')
76 | logging.info(" >>> Mixing [%s] %d%%" % (smali, progress*100/len(smalis)))
77 | try:
78 | shutil.copytree(smali, mix_to, dirs_exist_ok=True)
79 | except shutil.Error as e:
80 | for src, dst, msg in e.args[0]:
81 | print(dst, src, msg)
82 | progress = progress + 1
83 | return mix_to
84 |
85 | if __name__ == "__main__":
86 | '''Program entrance.'''
87 | global_config.config_logging('../log/app.log')
88 | configuration = DecompileConfiguration()
89 | dir = decompile_and_mix_all_smalis("/Users/wangshouheng/Desktop/apkanalyse/app_.apk", configuration)
90 | print(dir)
91 |
--------------------------------------------------------------------------------
/logcat/analyse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | ////////////////////////////////////////////////////////////////////
6 | // Android logcat analyse tool
7 | // Usage:
8 | // python analyse.py
9 | // -f the_logcat_file_to_analyse
10 | // References:
11 | // - Description about logcat:
12 | // https://developer.android.com/studio/debug/am-logcat
13 | // @Author: Mr.Shouheng
14 | ////////////////////////////////////////////////////////////////////
15 | """
16 |
17 | import sys, getopt
18 | import logging
19 | import sys
20 | import re
21 | import time
22 | from datetime import datetime
23 | from typing import List
24 | sys.path.insert(0, '../')
25 | import global_config
26 |
27 | LOGCAT_LINE_PATTERN = "^\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}\.\d{1,3}"
28 |
29 | command_info = "\
30 | Options: \n\
31 | -h[--help] Help info\n\
32 | -f[--file] Log file to analyse\n\
33 | -p[--package] Package name of analysing log\
34 | "
35 |
36 | def __show_invalid_command(info: str):
37 | '''Show invliad command info.'''
38 | print('Error: Unrecognized command: %s' % info)
39 | print(command_info)
40 |
41 | class AnalyseInfo:
42 | ''''Analyse info object.'''
43 | def __init__(self, package: str, path: str):
44 | self.package = package
45 | self.path = path
46 |
47 | class AnalyseLineInfo:
48 | '''Line info.'''
49 | def __init__(self, time: int, pid: str, tid: str, level: str, tag: str) -> None:
50 | self.time = time
51 | self.pid = pid
52 | self.tid = tid
53 | self.level = level
54 | self.tag = tag
55 |
56 | def __parse_command(argv) -> AnalyseInfo:
57 | '''Parse analyse info from input command.'''
58 | try:
59 | # : and = means accept arguments
60 | opts, args = getopt.getopt(argv, "-h:-p:-f:", ["help", "package=", 'file='])
61 | except BaseException as e:
62 | __show_invalid_command(str(e))
63 | sys.exit(2)
64 | if len(opts) == 0:
65 | __show_invalid_command('empty parameters')
66 | return
67 | package = path = None
68 | for opt, arg in opts:
69 | if opt in ('-f', '--file'):
70 | path = arg
71 | elif opt in ('-p', '--package'):
72 | package = arg
73 | elif opt in ('-h', '--help'):
74 | print(command_info)
75 | return
76 | if package == None or path == None:
77 | __show_invalid_command('Package or log output path required!')
78 | return
79 | return AnalyseInfo(package, path)
80 |
81 | def __execute(argv):
82 | ''''Execute command.'''
83 | # Parse command.
84 | info = __parse_command(argv)
85 | if info == None:
86 | return
87 | logging.info(">> Collect logcat for package [" + info.package + "].")
88 | # Read and analyse logcat file
89 | __do_anlyse(info)
90 |
91 | def __do_anlyse(info: AnalyseInfo):
92 | '''Do analyse job.'''
93 | f = open(info.path)
94 | line = f.readline()
95 | tag_count = {}
96 | while line:
97 | matched = re.match(LOGCAT_LINE_PATTERN, line)
98 | if matched is not None:
99 | line_info = __do_analyse_line(line)
100 | if line_info.tag not in tag_count:
101 | tag_count[line_info.tag] = []
102 | tag_count[line_info.tag].append(line_info)
103 | line = f.readline()
104 | f.close()
105 | for k, v in sorted(tag_count.items(), key = lambda kv:(len(kv[1]), len(kv[0]))):
106 | print(k + ": " + str(len(v)))
107 |
108 | def __do_analyse_line(line: str) -> AnalyseLineInfo:
109 | '''Analyse given line.'''
110 | time_str = re.match(LOGCAT_LINE_PATTERN, line).group(0)
111 | left = line.removeprefix(time_str).strip()
112 | index = left.find(' ')
113 | pid = left[0:index]
114 | left = left.removeprefix(pid).strip()
115 | index = left.find(' ')
116 | tid = left[0:index]
117 | left = left.removeprefix(tid).strip()
118 | index = left.find(' ')
119 | level = left[0:index]
120 | left = left.removeprefix(level).strip()
121 | index = left.find(':')
122 | tag = left[0:index]
123 | time_array = time.strptime('2021-' + time_str, "%Y-%m-%d %H:%M:%S.%f")
124 | time_stamp = int(time.mktime(time_array))
125 | return AnalyseLineInfo(time_stamp, pid, tid, level, tag)
126 |
127 | if __name__ == "__main__":
128 | '''Program entrance.'''
129 | global_config.config_logging('../log/app.log')
130 | __execute(sys.argv[1:])
131 |
--------------------------------------------------------------------------------
/logcat/collect.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | ////////////////////////////////////////////////////////////////////
6 | // Android logcat collect tool
7 | // Usage:
8 | // python collect.py
9 | // -f force restart or not
10 | // -p the_package_to_collect_log
11 | // -l the_path_to_output_log
12 | // Collect log from Android logcat:
13 | //
14 | // References:
15 | // - Android logcat usage doc:
16 | // https://developer.android.com/studio/command-line/logcat?hl=zh-cn
17 | // @Author: Mr.Shouheng
18 | ////////////////////////////////////////////////////////////////////
19 | """
20 |
21 | import os
22 | import sys, getopt
23 | import logging
24 | import signal
25 | import sys
26 | import threading
27 | from time import sleep
28 | from typing import List
29 | sys.path.insert(0, '../')
30 | import global_config
31 | from android.adb import *
32 | from android.am import *
33 |
34 | LOGCAT_COLLECT_MAX_TIME = 10 # 10 minutes
35 |
36 | command_info = "\
37 | Options: \n\
38 | -h[--help] Help info\n\
39 | -f[--forcestop] Should stop living app\n\
40 | -p[--package] Package name of analysing\n\
41 | -l[--log] Log file path from logcat\
42 | "
43 |
44 | def __show_invalid_command(info: str):
45 | '''Show invliad command info.'''
46 | print('Error: Unrecognized command: %s' % info)
47 | print(command_info)
48 |
49 | class CollectInfo:
50 | ''''Collect info object.'''
51 | def __init__(self, package: str, path: str, fc: str):
52 | self.package = package
53 | self.path = path
54 | self.fc = fc
55 |
56 | def __parse_command(argv) -> CollectInfo:
57 | '''Parse collect info from input command.'''
58 | try:
59 | # : and = means accept arguments
60 | opts, args = getopt.getopt(argv, "-h:-p:-l:-f", ["help", "package=", 'log=', 'forcestop='])
61 | except BaseException as e:
62 | __show_invalid_command(str(e))
63 | sys.exit(2)
64 | if len(opts) == 0:
65 | __show_invalid_command('empty parameters')
66 | return
67 | package = path = forcestop = None
68 | for opt, arg in opts:
69 | if opt in ('-l', '--log'):
70 | path = arg
71 | elif opt in ('-p', '--package'):
72 | package = arg
73 | elif opt in ('-f', '--forcestop'):
74 | forcestop = arg
75 | elif opt in ('-h', '--help'):
76 | print(command_info)
77 | return
78 | if package == None or path == None:
79 | __show_invalid_command('Package or log output path required!')
80 | return
81 | return CollectInfo(package, path, forcestop)
82 |
83 | def __logcat_redirect(pro: str, pid: str, info: CollectInfo):
84 | '''Do logcat redirect.'''
85 | # Be sure to add the '-d' option, otherwise the logcat won't write to screen.
86 | os.system("adb shell logcat --pid=" + pid + " -d > " + info.path + "/" + info.package + "" + pro + ".log")
87 |
88 | def __kill_collect_process():
89 | '''Kill current process to stop all logcat collect threads.'''
90 | out = os.popen("ps -ef | grep collect.py | grep -v grep | awk '{print $2}'")
91 | pid = out.read().strip()
92 | logging.debug(">> Collect trying to kill collect process [" + pid + "].")
93 | # os.system("kill -9 " + pid)
94 | os.kill(int(pid), signal.SIGTERM)
95 |
96 | def __execute(argv):
97 | ''''Execute command.'''
98 | # Parse command.
99 | info = __parse_command(argv)
100 | if info == None:
101 | return
102 | # Restart App if necessary.
103 | if info.fc != None:
104 | restart_app_by_package_name(info.package)
105 | logging.info(">> Collect logcat for package [" + info.package + "].")
106 | # Parse all process and their pids
107 | processes = get_processes_of_pcakage_name(info.package)
108 | logging.info(">> Collect process: [" + str(processes) + "].")
109 | threads = [threading.Thread]
110 | for process in processes:
111 | logging.info(">> Collect process: [" + process.pro + "].")
112 | thread = threading.Thread(target=__logcat_redirect, args=(process.pro, process.pid, info))
113 | thread.name = process.pro + "(" + process.pid + ")"
114 | threads.append(thread)
115 | for thread in threads:
116 | thread.start()
117 | logging.info(">> Collect for process: [" + thread.name + "] started.")
118 | timer = threading.Timer(LOGCAT_COLLECT_MAX_TIME, __kill_collect_process)
119 | timer.start()
120 |
121 | if __name__ == "__main__":
122 | '''Program entrance.'''
123 | global_config.config_logging('../log/app.log')
124 | __execute(sys.argv[1:])
125 |
--------------------------------------------------------------------------------
/initializer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import shutil
5 | import os
6 | import time
7 | import json
8 | import logging
9 | from importer import Importer as Importer
10 | from file_operator import JsonOperator as JsonOperator
11 | from config import REPO_CONFIG_PATH
12 | from config import APP_CONFIG_PATH
13 | from config import APP_LANGUAGE_CONFIG_PATH
14 | from config import REPO_CONFIG_PATH_PREFIX
15 |
16 | # 项目初始化工具类
17 | class Initializer:
18 | # 初始化
19 | def __init__(self):
20 | pass
21 |
22 | # 仓库是否已经初始化
23 | def is_repo_initialized(self):
24 | return os.path.exists(REPO_CONFIG_PATH)
25 |
26 | # 初始化项目
27 | def initialize(self, andoird_dir, ios_dir, support_languages):
28 | logging.debug("[initialize] android dir : " + str(andoird_dir) + " ios dir : " + str(ios_dir) + " support languages : " + str(support_languages))
29 | # 备份 & 移除配置文件
30 | if os.path.exists(REPO_CONFIG_PATH):
31 | dest_path = REPO_CONFIG_PATH_PREFIX % (time.strftime('%Y-%m-%d %M-%I-%S', time.localtime()))
32 | shutil.copyfile(REPO_CONFIG_PATH, dest_path)
33 | os.remove(REPO_CONFIG_PATH)
34 | # 添加支持的多语言
35 | appConfig.initialize()
36 | appConfig.add_support_languages(support_languages)
37 | appConfig.android_resources_root_directory = andoird_dir
38 | appConfig.ios_resources_root_directory = ios_dir
39 | # 生成多语言仓库文件
40 | importer = Importer(appConfig)
41 | if len(andoird_dir) > 0:
42 | importer.import_android_resources()
43 | if len(ios_dir) > 0:
44 | importer.import_ios_resources()
45 | # 将配置写入到文件中
46 | appConfig.write_to_json()
47 | importer.gen_repo_config()
48 |
49 | # 应用配置
50 | class AppConfig:
51 | # 初始化
52 | def __init__(self):
53 | self.language = "" # 当前使用的多语言
54 | self.support_languages = [] # 支持的语言
55 | self.languages = {} # 所有多语言
56 | self.android_resources_root_directory = "" # Android 多语言资源根目录
57 | self.ios_resources_root_directory = "" # iOS 多语言资源根目录
58 | self.ios_language_black_list = [] # iOS 多语言黑名单,指语言缩写存在于 Android 而不存在于 iOS 中的
59 | self.android_language_black_list = [] # Android 多语言黑名单,指语言缩写存在于 iOS 而不存在于 Android 中的
60 | self.translate_excel_output_directory = "" # 翻译 Excel 导出文件位置
61 | # 加载配置文件
62 | with open(APP_CONFIG_PATH, "r") as f:
63 | config_json = json.load(f)
64 | self.language = config_json["language"]
65 | self.support_languages = config_json["support_languages"]
66 | self.languages = config_json["languages"]
67 | self.android_resources_root_directory = config_json.get("android_resources_root_directory")
68 | self.ios_resources_root_directory = config_json.get("ios_resources_root_directory")
69 | self.ios_language_black_list = config_json.get("ios_language_black_list")
70 | self.android_language_black_list = config_json.get("android_language_black_list")
71 | self.translate_excel_output_directory = config_json.get("translate_excel_output_directory")
72 |
73 | # 初始化应用配置
74 | def initialize(self):
75 | self.support_languages.clear()
76 |
77 | # 添加多语言支持
78 | def add_support_languages(self, support_languages):
79 | for support_language in support_languages:
80 | # 过滤已经存在的多语言
81 | if support_language not in self.support_languages:
82 | self.support_languages.append(support_language)
83 | logging.debug("Added support languages : " + str(self.support_languages))
84 |
85 | # 将配置写入到 json 文件中
86 | def write_to_json(self):
87 | json_obj = {
88 | "language": self.language,
89 | "support_languages": self.support_languages,
90 | "languages": self.languages,
91 | "ios_language_black_list": self.ios_language_black_list,
92 | "android_language_black_list": self.android_language_black_list,
93 | "ios_resources_root_directory": self.ios_resources_root_directory,
94 | "android_resources_root_directory": self.android_resources_root_directory,
95 | "translate_excel_output_directory": self.translate_excel_output_directory
96 | }
97 | jsonOperator = JsonOperator()
98 | jsonOperator.write_json(APP_CONFIG_PATH, json_obj)
99 |
100 | # 应用多语言工具类,用于翻译工具的多语言
101 | class LanguageFactory:
102 | # 初始化
103 | def __init__(self):
104 | self.entries = {}
105 |
106 | # 加载多语言资源
107 | def load_languages(self):
108 | jsonOperator = JsonOperator()
109 | self.entries = jsonOperator.read_json(APP_LANGUAGE_CONFIG_PATH)
110 |
111 | # 获取多语言词条
112 | def get_entry(self, name):
113 | return self.entries[name][appConfig.language]
114 |
115 | # the singleton app config bean
116 | appConfig = AppConfig()
117 |
--------------------------------------------------------------------------------
/repository.py:
--------------------------------------------------------------------------------
1 |
2 | #!/usr/bin/env python3
3 | # -*- coding: utf-8 -*-
4 |
5 | import logging
6 | from file_operator import JsonOperator as JsonOperator
7 | from config import REPO_CONFIG_PATH
8 |
9 | # 仓库加载
10 | class Repository:
11 | # 初始化
12 | def __init__(self):
13 | self.datas = []
14 | self.keywords = []
15 | self.languages = []
16 | self.__loaded = False
17 |
18 | # 加载数据仓库
19 | def load(self):
20 | if self.__loaded:
21 | return
22 | jsonOperator = JsonOperator()
23 | self.datas = jsonOperator.read_json(REPO_CONFIG_PATH)
24 | for data in self.datas:
25 | self.keywords.append(data["keyword"])
26 | if len(self.languages) == 0:
27 | for k, v in data["translates"].items():
28 | self.languages.append(k)
29 | self.__loaded = True
30 | logging.debug("Loaded keywords : " + str(self.keywords))
31 | logging.debug("Loaded languages : " + str(self.languages))
32 |
33 | # 尝试添加新的语言
34 | def try_to_add_new_language(self, language):
35 | if language not in self.languages:
36 | self.languages.append(language)
37 | logging.debug("Tring to add new language " + language + " into " + str(self.datas))
38 | for data in self.datas:
39 | data["translates"][language] = ""
40 | logging.info("Language added : " + language)
41 | return
42 | logging.info("Language exists : " + language)
43 |
44 | # 尝试添加新的词条
45 | def try_to_add_new_keyword(self, keyword, translation, language):
46 | if keyword not in self.keywords:
47 | # 添加一个新的词条
48 | translations = {}
49 | self.__init_keyword_translations(translation, translations, language)
50 | self.datas.append({"keyword": keyword, "comment": "", "translates": translations})
51 | self.keywords.append(keyword)
52 | else:
53 | # 判断词条是否发生了变更(之前调用 try_to_add_new_language 的时候处理了新增多语言的情况)
54 | for data in self.datas:
55 | if data["keyword"] == keyword:
56 | old_translation = data["translates"][language]
57 | # 应该过滤掉 old_translation 为空掉情况,此时说明它已经被处理过了,没必要再次处理
58 | # 所以这就意味着不要一次更改两个多语言文件的同一词条,因为我们通过词条变更来确定哪些词条的多语言需要重新翻译,
59 | # 如果同时修改,那么只能以多语言文件列表的第一个文件的改动为准,因此造成结果不可预知
60 | # 建议通过导出 Excel 的方式,通过修改 Excel 并重新导入到项目中的方式来导入自己翻译的多语言
61 | if old_translation != translation and len(old_translation) != 0:
62 | logging.debug("Found translation change for : " + keyword + " (" + language + ").")
63 | self.__init_keyword_translations(translation, data["translates"], language)
64 | # 处理完毕,找到一个就可以结束了
65 | break
66 |
67 | # 修改词条
68 | def try_ro_modify_keyword(self, keyword, translation, language):
69 | # 不存在的语言不支持修改
70 | if language not in self.languages:
71 | return
72 | # 遍历更新
73 | for data in self.datas:
74 | if data["keyword"] == keyword and language in data["translates"]:
75 | if data["translates"][language] != translation:
76 | data["translates"][language] = translation
77 | break
78 |
79 | # 更新多语言词条
80 | def update_keyword(self, keyword, translation, language):
81 | for data in self.datas:
82 | if data["keyword"] == keyword:
83 | if language in data["translates"]:
84 | data["translates"][language] = translation
85 |
86 | # 重新生成 repo json
87 | def rewrite_repo_json(self):
88 | jsonOperator = JsonOperator()
89 | jsonOperator.write_json(REPO_CONFIG_PATH, self.datas)
90 |
91 | # 获取仓库当前的状态
92 | def get_repo_state(self):
93 | self.load()
94 | missed_count = 0
95 | for data in self.datas:
96 | # 对每个词条进行处理
97 | for k, v in data["translates"].items():
98 | if len(v) == 0:
99 | missed_count = missed_count + 1
100 | break
101 | return {"missed_count": missed_count}
102 |
103 | # 获取用于翻译的多语言
104 | def get_keywords_to_translate(self):
105 | self.load()
106 | dist = {}
107 | for data in self.datas:
108 | keyword = data["keyword"]
109 | to_languages = []
110 | from_language = ""
111 | from_translation = ""
112 | for k,v in data["translates"].items():
113 | if len(v) == 0:
114 | to_languages.append(k)
115 | else:
116 | from_language = k
117 | from_translation = v
118 | dist[keyword] = []
119 | for language in to_languages:
120 | dist[keyword].append({"translation":from_translation, "from":from_language, "to":language})
121 | return dist
122 |
123 | # 初始化词条的多语言
124 | def __init_keyword_translations(self, translation, translations, language):
125 | for l in self.languages:
126 | if l == language:
127 | translations[l] = translation
128 | else:
129 | translations[l] = ""
130 |
131 | # The singleton repository
132 | repository = Repository()
133 |
--------------------------------------------------------------------------------
/android/adb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | from typing import List
6 |
7 | # ////////////////////////////////////////////////////////////////////
8 | #
9 | # The Android Debug Bridge Wrapper Project by Python
10 | #
11 | # @Author Mr.Shouheng
12 | #
13 | # @References:
14 | # - https://developer.android.com/studio/command-line/adb?hl=zh-cn#am
15 | #
16 | # ////////////////////////////////////////////////////////////////////
17 |
18 | class DeviceInfo:
19 | '''Adb attached device info.'''
20 | def __init__(self) -> None:
21 | self.infos = []
22 |
23 | def append(self, info: str):
24 | '''Append one info to infos.'''
25 | self.infos.append(info)
26 |
27 | def __str__(self) -> str:
28 | ret = ''
29 | for info in self.infos:
30 | ret = ret + info + '\n'
31 | return ret
32 |
33 | def get_devices() -> List[DeviceInfo]:
34 | '''Get all attached devices.'''
35 | text = os_popen("adb devices -l", None)
36 | lines = [line.strip() for line in text.split('\n')]
37 | devices = []
38 | for line in lines[1:]:
39 | parts = [part.strip() for part in line.split(' ')]
40 | device = DeviceInfo()
41 | index = 0
42 | for part in parts:
43 | if len(part) > 0:
44 | index = index+1
45 | if index == 1:
46 | device.append("serial:" + part)
47 | elif index == 2:
48 | device.append("state:" + part)
49 | else:
50 | device.append(part)
51 | devices.append(device)
52 | return devices
53 |
54 | def get_version() -> List[str]:
55 | '''Get adb version info.'''
56 | text = os_popen("adb version", None)
57 | lines = [line.strip() for line in text.split('\n')]
58 | return lines
59 |
60 | def reboot(serial: str = None) -> int:
61 | '''Reboot device.'''
62 | return os_system("adb %s reboot ", serial)
63 |
64 | def export_bugreport(path: str, serial: str = None) -> int:
65 | '''Export bugreport, ANR log etc.'''
66 | return os_system("adb %s bugreport %s", serial, path)
67 |
68 | def install(apk: str, serial: str = None) -> int:
69 | '''
70 | Install given package:
71 | - apk: path of APK.
72 | '''
73 | return os_system("adb %s install %s ", serial, apk)
74 |
75 | def uninstall(pkg: str, keep: bool, serial: str = None) -> bool:
76 | '''
77 | Uninstall given package.
78 | - pkg: package name
79 | - keep: keep the data and cache directories if true, otherwise false.
80 | '''
81 | if keep:
82 | return os_system("adb %s uninstall -k %s ", serial, pkg) == 0
83 | else:
84 | return os_system("adb %s uninstall %s ", serial, pkg) == 0
85 |
86 | def push(local: str, remote: str, serial: str = None) -> int:
87 | '''
88 | Push local file to remote directory.
89 | - local: local file path
90 | - remote: the remote path to push to
91 | '''
92 | return os_system("adb %s push %s %s", serial, local, remote)
93 |
94 | def pull(remote: str, local: str, serial: str = None) -> int:
95 | '''
96 | Pull file from remote phone:
97 | - remote: remote file path
98 | - local: local to store file
99 | '''
100 | return os_system("adb %s pull %s %s", serial, remote, local)
101 |
102 | def os_system(command: str, serial: str, *args) -> int:
103 | '''Execute command by os.system().'''
104 | f_args = []
105 | if serial is None:
106 | f_args.append(" ")
107 | else:
108 | f_args.append(" -s %s " % serial)
109 | f_args.extend(args)
110 | if len(f_args) == 1 and f_args[0] == " ":
111 | return os.system(command)
112 | return os.system(command % tuple(f_args))
113 |
114 | def os_popen(command: str, serial: str, *args) -> str:
115 | '''Execute command by os.popen().'''
116 | f_args = []
117 | if serial is None:
118 | f_args.append(" ")
119 | else:
120 | f_args.append(" -s %s " % serial)
121 | f_args.extend(args)
122 | if len(f_args) == 1 and f_args[0] == " ":
123 | out = os.popen(command)
124 | else:
125 | out = os.popen(command % tuple(f_args))
126 | return out.read().strip()
127 |
128 | def print_list(list: List):
129 | '''Print list.'''
130 | for item in list:
131 | print(str(item) + ", ")
132 |
133 | if __name__ == "__main__":
134 | TEST_PACKAGE_NAME = "me.shouheng.leafnote"
135 | TEST_DEVICE_SERIAL = "emulator-5556"
136 | TEST_APK_PATH = "/Users/wangshouheng/downloads/Snapdrop-0.3.apk"
137 | TEST_LOCAL_FILE = "adb.py"
138 | TEST_REMOTE_DIR = "/sdcard"
139 | TEST_REMOTE_FILE = "/sdcard/adb.py"
140 | # print_list(get_devices())
141 | # print(get_version())
142 | # print(reboot())
143 | # print(reboot(TEST_DEVICE_SERIAL))
144 | # print(export_bugreport('~/bugs'))
145 | # print(export_bugreport('~/bugs', TEST_DEVICE_SERIAL))
146 | # print(uninstall(TEST_PACKAGE_NAME, False))
147 | # print(uninstall(TEST_PACKAGE_NAME, False, TEST_DEVICE_SERIAL))
148 | # print(install(TEST_APK_PATH))
149 | # print(install(TEST_APK_PATH, TEST_DEVICE_SERIAL))
150 | # print(os_system("ls -al " + TEST_APK_PATH, None))
151 | # print(push(TEST_LOCAL_FILE, TEST_REMOTE_DIR, TEST_DEVICE_SERIAL))
152 | # print(pull(TEST_REMOTE_FILE, "~", TEST_DEVICE_SERIAL))
153 | # os_system("a", "b", "c", "d", "e")
154 | pull("/data/local/tmp/temp_sampling.trace", ".", TEST_DEVICE_SERIAL)
155 |
--------------------------------------------------------------------------------
/translator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import requests
5 | import hashlib
6 | import logging
7 | import json
8 | import time
9 | from config import API_URL
10 | from config import BAIDU_CONFIG_URL
11 | from repository import repository
12 |
13 | # 百度翻译工具类
14 | class BaiduTranslator:
15 | # 初始化
16 | def __init__(self):
17 | self.mappings = {} # 语言映射表
18 | self.reversed_mappings = {} # 反向语言映射表
19 | self.appid = "" # 文字识别 AppId
20 | self.secret = "" # 文字识别 App Secret
21 | self.__load_configs() # 加载配置文件
22 |
23 | # 将指定的字符串翻译为指定的语言(传入的 language 应当是 App 多语言缩写)
24 | def translate(self, words, language):
25 | try:
26 | mapped_language = self.__get_mapped_language(language)
27 | if mapped_language == None:
28 | logging.warning("Missed language mapping \"" + language + "\"")
29 | return ""
30 | translated = self.translate_directly(words, mapped_language)
31 | # 返回一个字典,包含翻译结果和映射的多语言
32 | return {"translation":translated, "mapped_language":mapped_language}
33 | except KeyError as ke:
34 | logging.error("Unknown language " + language + " : " + ke)
35 | return ""
36 |
37 | # 将指定的字符串翻译为指定的语言(传入的 mapped_language 就是 baidu 平台定义的语言缩写)
38 | def translate_directly(self, words, mapped_language):
39 | logging.info("Translate \"" + words + "\" to \"" + mapped_language + "\".")
40 | form_data = self.__get_post_form_data(words, mapped_language)
41 | # 增加一点延时
42 | time.sleep(1)
43 | response = requests.post(API_URL, form_data)
44 | result = response.json()
45 | try:
46 | translated = result["trans_result"][0]["dst"]
47 | logging.info("Translated \"" + words + "\" to \"" + translated + "\".")
48 | return translated
49 | except KeyError:
50 | logging.error("Translate error " + result["error_code"] + " " + result["error_msg"])
51 | return ""
52 |
53 | # 获取方向的语言映射列表,比如 香港和澳门的繁体都是繁体,此时可以用这个映射,避免重复翻译
54 | def get_reversed_mapped_languages(self, from_language):
55 | return self.reversed_mappings.get(from_language)
56 |
57 | # 判断是否配置完成
58 | def is_configed(self):
59 | return self.appid != "your_app_id" and self.secret != "your_app_secret"
60 |
61 | # 获取指定的语言对应的 Baidu 翻译语言参数
62 | def __get_mapped_language(self, language):
63 | return self.mappings.get(language)
64 |
65 | # 加载配置文件
66 | def __load_configs(self):
67 | with open(BAIDU_CONFIG_URL) as fp:
68 | data = json.load(fp)
69 | logging.debug(data["mappings"])
70 | self.mappings = data["mappings"]
71 | self.appid = data["app_id"]
72 | self.secret = data["app_secret"]
73 | # 构建反向的多语言映射
74 | for k,v in self.mappings.items():
75 | if v not in self.reversed_mappings:
76 | self.reversed_mappings[v] = []
77 | self.reversed_mappings[v].append(k)
78 |
79 | # 获取用于发送的 post form 数据
80 | def __get_post_form_data(self, words, language):
81 | dist = {}
82 | dist['q'] = words
83 | dist['from'] = 'auto'
84 | dist['to'] = language
85 | dist['appid'] = self.appid
86 | salt = '12321321'
87 | dist['salt'] = salt
88 | sign = self.appid + words + salt + self.secret
89 | hl = hashlib.md5()
90 | hl.update(sign.encode(encoding='utf-8'))
91 | md5_result = hl.hexdigest()
92 | dist['sign'] = md5_result
93 | return dist
94 |
95 | # 翻译工具类
96 | class Translator:
97 | # 初始化
98 | def __init__(self):
99 | pass
100 |
101 | # 开始翻译
102 | def start_translate(self, progress_callback, finish_callback):
103 | repository.load()
104 | translator = BaiduTranslator()
105 | dist = repository.get_keywords_to_translate()
106 | logging.debug("Dist to translate : " + str(dist))
107 | total = len(dist)
108 | count = 0
109 | # 遍历词条
110 | translated_languages = []
111 | for keyword,v in dist.items():
112 | logging.debug("Translating " + keyword)
113 | # 清空标记
114 | translated_languages.clear()
115 | for item in v:
116 | # from_language = item["from"]
117 | to_language = item["to"]
118 | translation = item["translation"]
119 | if to_language in translated_languages:
120 | continue
121 | # 可能已经被翻译过了
122 | result = translator.translate(translation, to_language)
123 | if len(result) != 0:
124 | translated = result["translation"]
125 | mapped_language = result["mapped_language"]
126 | # 反向的映射关系填充翻译结果
127 | reversed_mappings = translator.get_reversed_mapped_languages(mapped_language)
128 | logging.debug("Get reversed mappings for " + mapped_language + " : " + str(reversed_mappings))
129 | for reversed_mapping in reversed_mappings:
130 | # 标记方向映射列表,更新词条信息
131 | translated_languages.append(reversed_mapping)
132 | repository.update_keyword(keyword, translated, reversed_mapping)
133 | else:
134 | logging.warning("One keyword missed to translate : " + keyword)
135 | # 回调
136 | count = count + 1
137 | progress_callback(count * 100 / total)
138 | # 重写 repo json
139 | repository.rewrite_repo_json()
140 | finish_callback()
141 |
--------------------------------------------------------------------------------
/file_operator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | from xml.dom.minidom import parse
5 | import xml.dom.minidom
6 | import logging
7 | import json
8 | import xlwt
9 | import os
10 | import codecs
11 | import xlrd
12 | from config import TRANSLATE_EXCEL_SHEET_NAME
13 |
14 | # Xml 操作类
15 | class XmlOperator:
16 | # 初始化
17 | def __init__(self):
18 | pass
19 |
20 | # 读取 Android 的 xml 资源(当前只支持读取 string 标签)
21 | def read_android_resources(self, fname):
22 | logging.debug("Reading Android resources of \"" + fname + "\"")
23 | # 使用 minidom 解析器打开 XML 文档
24 | dist = {}
25 | try:
26 | DOMTree = xml.dom.minidom.parse(fname)
27 | except Exception:
28 | logging.error("Failed to read Android resources : " + fname)
29 | return dist
30 | collection = DOMTree.documentElement
31 | # 读取所有 string 标签
32 | strings = collection.getElementsByTagName("string")
33 | # 将读取到的所有元素拼接成字典列表
34 | index = 0
35 | for string in strings:
36 | index += 1
37 | try:
38 | keyword = string.getAttribute('name')
39 | translation = ""
40 | if len(string.childNodes) == 1:
41 | node = string.childNodes[0]
42 | # CDATA 特殊处理
43 | if node.nodeType == 4:
44 | translation = ""
45 | else:
46 | translation = node.data
47 | else:
48 | # 对子节点遍历,将所有对子节点拼接起来
49 | for node in string.childNodes:
50 | # CDATA 特殊处理
51 | if node.nodeType == 4:
52 | translation = translation + ""
53 | else:
54 | translation = translation + node.toxml()
55 | # 过滤处理
56 | if '\\\'' in translation:
57 | translation = translation.replace('\\\'', '\'')
58 | dist[keyword] = translation
59 | except BaseException as e:
60 | logging.error("Invalid entry at index " + str(index) + " " + str(keyword) + " : " + str(e))
61 | # 返回解析结果
62 | logging.debug("Read Android resources \"" + fname + "\" end.")
63 | return dist
64 |
65 | # 将 Android 的 xml 资源写入到文件中
66 | def write_android_resources(self, dist, fname):
67 | logging.debug("Writing Android resources " + fname + " : " + str(dist))
68 | content = '\n'
69 | for k, v in dist.items():
70 | # 处理 '
71 | if '\'' in v:
72 | v = v.replace("\'", "\\\'")
73 | # 处理 > 和 <
74 | if ('>' in v or '<' in v) and '', '>')
76 | v = v.replace('<', '<')
77 | # 处理 …
78 | if '…' in v and '' + v + '\n'
82 | content += ''
83 | with open(fname, 'w', encoding='utf-8') as f:
84 | f.write(content)
85 |
86 | # Json 操作类
87 | class JsonOperator:
88 | # 初始化
89 | def __init__(self):
90 | pass
91 |
92 | # 写入 json 到文件
93 | def write_json(self, fname, json_obj):
94 | json_str = json.dumps(json_obj)
95 | with open(fname, "w") as f:
96 | f.write(json_str)
97 |
98 | # 从文件读取 json 字符串
99 | def read_json(self, fname):
100 | with open(fname, "r") as f:
101 | return json.load(f)
102 |
103 | # 文件操作类,用于 iOS 文件操作
104 | class FileOperator:
105 | # 初始化
106 | def __init__(self):
107 | pass
108 |
109 | # 读取 iOS 词条
110 | def read_ios_keywords(self, fname):
111 | logging.debug("Reading ios keywrods : " + fname)
112 | dist = {}
113 | with open(fname, 'r') as f:
114 | # 读取每一行的数据
115 | ls = [line.strip() for line in f]
116 | f.close()
117 | for l in ls:
118 | sps = l.split("=")
119 | keyword = sps[0].strip()[1:-1]
120 | translation = sps[1].strip()[1:-2]
121 | dist[keyword] = translation
122 | return dist
123 |
124 | # 生成 iOS 的多语言资源
125 | def write_ios_resources(self, dist, fname):
126 | logging.debug("Writing iOS resources " + fname + " : " + str(dist))
127 | content = ''
128 | for k, v in dist.items():
129 | content = content + "\"" + k + "\" = \"" + v + "\";\n"
130 | with open(fname, 'w', encoding='utf-8') as f:
131 | f.write(content)
132 |
133 | # Excel 操作类
134 | class ExcelOperator:
135 | # 初始化
136 | def __init__(self):
137 | pass
138 |
139 | # 写 Excel
140 | # TODO 这里到 sheet name 应该从 dist 中读取,下面这样不具备通用性
141 | def write_excel(self, dist, file):
142 | # 创建 Excel 的工作簿
143 | book = xlwt.Workbook(encoding='utf-8', style_compression=0)
144 | sheet = book.add_sheet(TRANSLATE_EXCEL_SHEET_NAME, cell_overwrite_ok=True)
145 | # dist : {"a":[], "b":[], "c": []}
146 | row_count = 0
147 | col_count = 0
148 | for k, v in dist.items():
149 | sheet.write(row_count, col_count, k)
150 | for item in v:
151 | row_count = row_count + 1
152 | sheet.write(row_count, col_count, item)
153 | col_count = col_count + 1
154 | row_count = 0
155 | book.save(file)
156 |
157 | # 读取 Excel,{sheet_name:[[col 1], [col 2], []]}
158 | def read_excel(self, xlsfile):
159 | dists = {}
160 | book = xlrd.open_workbook(xlsfile)
161 | size = len(book.sheet_names())
162 | for i in range(size):
163 | sheet = book.sheet_by_index(i)
164 | dists[sheet.name] = self.__read_sheet(sheet)
165 | return dists
166 |
167 | # 读取 Excel Sheet
168 | def __read_sheet(self, sheet):
169 | col_list = []
170 | # 按列遍历
171 | for col in range(0, sheet.ncols):
172 | col_list.append([])
173 | # 按行遍历
174 | for row in range(0, sheet.nrows):
175 | value = sheet.cell_value(row, col)
176 | col_list[col].append(value)
177 | return col_list
178 |
--------------------------------------------------------------------------------
/android/am.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # ////////////////////////////////////////////////////////////////////
5 | #
6 | # The Android Debug Bridge Wrapper Project by Python
7 | #
8 | # @Author Mr.Shouheng
9 | #
10 | # @References:
11 | # - https://developer.android.com/studio/command-line/adb?hl=zh-cn#am
12 | #
13 | # ////////////////////////////////////////////////////////////////////
14 |
15 | import logging
16 | from time import sleep
17 | from typing import List
18 | from adb import *
19 | import sys
20 | sys.path.insert(0, '../')
21 | import global_config
22 |
23 | class ProcessInfo:
24 | '''Process information wrapper object.'''
25 | def __init__(self, pro: str, pid: str):
26 | self.pro = pro
27 | self.pid = pid
28 |
29 | def __str__(self) -> str:
30 | return "%s(%s)" % (self.pro, self.pid)
31 |
32 | def stop_app_by_package_name(pkg: str, serial: str = None) -> bool:
33 | '''Stop App of given package name.'''
34 | return os_system("adb %s shell am force-stop %s ", serial, pkg) == 0
35 |
36 | def get_launcher_of_package_name(pkg: str, serial: str = None) -> str:
37 | '''Get launcher class of given package name.'''
38 | out = os_popen("adb %s shell dumpsys package %s ", serial, pkg)
39 | index = out.find('Category: "android.intent.category.LAUNCHER"')
40 | part = out[0:index]
41 | s_index = part.rfind(pkg)
42 | part = part[s_index:index]
43 | e_index = part.find(' ')
44 | launcher = part[0:e_index]
45 | return launcher
46 |
47 | def start_app_by_package_name(pkg: str, serial: str = None) -> bool:
48 | '''Start App by launcher class.'''
49 | launcher = get_launcher_of_package_name(pkg, serial)
50 | return os_system("adb %s shell am start %s ", serial, launcher) == 0
51 |
52 | def restart_app_by_package_with_profiling(pkg: str, file: str, serial: str = None) -> int:
53 | '''Restart APP with given package name.'''
54 | stop_app_by_package_name(pkg, serial)
55 | launcher = get_launcher_of_package_name(pkg, serial)
56 | ret = os_popen("adb %s shell am start -n %s --start-profiler %s --sampling %s ", serial, launcher, file, '10')
57 | logging.debug("Start info: " + ret)
58 | return 1
59 |
60 | def stop_profiler_and_download_trace_file(pkg: str, file: str, to: str, serial: str = None) -> int:
61 | '''Stop profiler and download the trace file to local.'''
62 | pros = get_processes_of_pcakage_name(pkg, serial)
63 | logging.debug("Try to kill process : " + str(pros[0]))
64 | out = os_popen("adb %s shell am profile stop %s ", serial, pros[0].pid)
65 | logging.debug("Stop profiler: " + out)
66 | sleep(10) # Delays for few seconds, currently may writing to the profiler file. (It's emprt now.)
67 | ret = pull(file, to, serial)
68 | logging.debug("Pull file from %s to %s : %s" % (file, to, str(ret)))
69 | return ret
70 |
71 | def restart_app_by_with_profiling_and_duration(pkg: str, to: str, duration: int, serial: str = None) -> int:
72 | '''
73 | Restart APP and profiler by package name.
74 | - pkg: package name
75 | - to: local file path to store the profiler file
76 | - duration: the time duration to collect the profiler data
77 | '''
78 | smapling_file = "/data/local/tmp/temp_sampling.trace"
79 | restart_app_by_package_with_profiling(pkg, smapling_file, serial)
80 | sleep(duration)
81 | return stop_profiler_and_download_trace_file(pkg, smapling_file, to, serial)
82 |
83 | def dumpheap_by_package(pkg: str, to: str, serial: str = None) -> int:
84 | '''Dumpheap of given package.'''
85 | pros = get_processes_of_pcakage_name(pkg, serial)
86 | temp = "/data/local/tmp/temp.heap"
87 | if len(pros) == 0:
88 | return -1
89 | logging.debug("Try to dump process : " + str(pros[0]))
90 | os_system("adb %s shell am dumpheap %s %s ", serial, pros[0].pid, temp)
91 | return pull(temp, to, serial)
92 |
93 | def dumpheap_by_package(pkg: str, process_name: str, to: str, serial: str = None) -> int:
94 | '''Dumpheap of given package.'''
95 | pros = get_processes_of_pcakage_name(pkg, serial)
96 | temp = "/data/local/tmp/temp.heap"
97 | if len(pros) == 0:
98 | return -1
99 | pro = pros[0]
100 | for p in pros:
101 | if p == process_name:
102 | pro = p
103 | logging.debug("Try to dump process : " + str(pro))
104 | os_system("adb %s shell am dumpheap %s %s ", serial, pro.pid, temp)
105 | return pull(temp, to, serial)
106 |
107 | def restart_app_by_package_name(pkg: str, serial: str = None) -> bool:
108 | '''Restart App by package name.'''
109 | stop_app_by_package_name(pkg, serial)
110 | return start_app_by_package_name(pkg, serial)
111 |
112 | def get_processes_of_pcakage_name(pkg: str, serial: str = None) -> List[ProcessInfo]:
113 | '''Get all processes info of package name.'''
114 | processes = []
115 | # The 'awk' command is invlaid on adb shell, so it will take into effect.
116 | text = os_popen("adb %s shell \"ps -ef|grep %s|grep -v grep| awk \'{print $1}\'\"", serial, pkg)
117 | lines = [line.strip() for line in text.split('\n')]
118 | for line in lines:
119 | parts = [col.strip() for col in line.split(' ')]
120 | cols = []
121 | for part in parts:
122 | if part != '':
123 | cols.append(part)
124 | if len(cols) == 0:
125 | continue
126 | pid = cols[1]
127 | pro = cols[7].removeprefix(pkg)
128 | if pro == '':
129 | pro = ':main'
130 | process = ProcessInfo(pro, pid)
131 | processes.append(process)
132 | return processes
133 |
134 | def get_top_activity_info(serial: str = None) -> str:
135 | '''Get top activities info.'''
136 | return os_popen("adb %s shell dumpsys activity top", serial)
137 |
138 | def capture_screen(to: str, serial: str = None) -> int:
139 | '''Capture screen.'''
140 | temp = "/sdcard/screen.png"
141 | os_system("adb %s shell screencap %s ", serial, temp)
142 | return pull(temp, to, serial)
143 |
144 | if __name__ == "__main__":
145 | global_config.config_logging('../log/app.log')
146 | TEST_PACKAGE_NAME = ""
147 | TEST_DEVICE_SERIAL = "emulator-5556"
148 | TEST_APK_PATH = "/Users/wangshouheng/downloads/Snapdrop-0.3.apk"
149 | TEST_LOCAL_FILE = "adb.py"
150 | TEST_REMOTE_DIR = "/sdcard"
151 | TEST_REMOTE_FILE = "/sdcard/adb.py"
152 | # stop_app_by_package_name(TEST_PACKAGE_NAME)
153 | # stop_app_by_package_name(TEST_PACKAGE_NAME, TEST_DEVICE_SERIAL)
154 | # print(get_launcher_of_package_name(TEST_PACKAGE_NAME))
155 | # print(get_launcher_of_package_name(TEST_PACKAGE_NAME, TEST_DEVICE_SERIAL))
156 | # print(start_app_by_package_name(TEST_PACKAGE_NAME))
157 | # print(start_app_by_package_name(TEST_PACKAGE_NAME, TEST_DEVICE_SERIAL))
158 | # print_list(get_processes_of_pcakage_name(TEST_PACKAGE_NAME))
159 | # print_list(get_processes_of_pcakage_name(TEST_PACKAGE_NAME, TEST_DEVICE_SERIAL))
160 | # print(get_top_activity_info())
161 | # print(get_top_activity_info(TEST_DEVICE_SERIAL))
162 | # os_system("adb %s shell am > ./am.md", TEST_DEVICE_SERIAL)
163 | # restart_app_by_with_profiling_and_duration(TEST_PACKAGE_NAME, "~/desktop/sampling.trace", 6, TEST_DEVICE_SERIAL)
164 | # dumpheap_by_package(TEST_PACKAGE_NAME, ":main", "~/desktop/file.heap", TEST_DEVICE_SERIAL)
165 | # capture_screen("~/desktop/screen.png")
166 |
--------------------------------------------------------------------------------
/importer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import logging
6 | import json
7 | from file_operator import ExcelOperator as ExcelOperator
8 | from file_operator import XmlOperator as XmlOperator
9 | from file_operator import JsonOperator as JsonOperator
10 | from file_operator import FileOperator as FileOperator
11 | from repository import repository as repository
12 | from config import REPO_CONFIG_PATH
13 | from config import TRANSLATE_EXCEL_SHEET_NAME
14 |
15 | # 导入工具
16 | class Importer:
17 | # 初始化
18 | def __init__(self, appConfig):
19 | self.appConfig = appConfig
20 | self.support_languages = [] # 需要支持的语言
21 | self.keywords = [] # 关键字
22 | self.translates = {} # 翻译的词条
23 | self.entries = [] # 最终得到的翻译条目
24 |
25 | # 项目初始化的时候,导入 Android 翻译资源(values 文件夹根目录)
26 | def import_android_resources(self):
27 | # 遍历解析 Android 资源目录
28 | xmlOperator = XmlOperator()
29 | # 解析所有的多语言
30 | for f in os.listdir(self.appConfig.android_resources_root_directory):
31 | language = self.__get_android_file_language(f)
32 | if len(language) <= 0:
33 | continue
34 | # 语言名称
35 | self.support_languages.append(language)
36 | # 解析多语言的词条
37 | for f in os.listdir(self.appConfig.android_resources_root_directory):
38 | language = self.__get_android_file_language(f)
39 | if len(language) <= 0:
40 | continue
41 | path = os.path.join(self.appConfig.android_resources_root_directory, f, "strings.xml")
42 | dist = xmlOperator.read_android_resources(path)
43 | for k, v in dist.items():
44 | if k not in self.keywords:
45 | self.keywords.append(k)
46 | if k not in self.translates:
47 | self.translates[k] = {}
48 | for support_language in self.support_languages:
49 | if support_language != language:
50 | self.translates[k][support_language] = ""
51 | else:
52 | self.translates[k][support_language] = v
53 | # 新增多语言的情况:要为应用初始化的时候选中的多语言设置词条
54 | for sl in self.appConfig.support_languages:
55 | if sl not in self.support_languages:
56 | for k, v in self.translates.items():
57 | self.translates[k][sl] = ""
58 | # 输出用于调试的日志
59 | self.appConfig.add_support_languages(self.support_languages)
60 | logging.debug("Parsed From Android Resources : " + str(self.support_languages))
61 | logging.debug("Parsed Keywords : " + str(self.keywords))
62 | logging.debug(self.translates)
63 |
64 | # 项目初始化的时候,导入 iOS 翻译资源(lproj 文件夹根目录)
65 | def import_ios_resources(self):
66 | fileOperator = FileOperator()
67 | # 解析所有的多语言
68 | for f in os.listdir(self.appConfig.ios_resources_root_directory):
69 | language = self.__get_ios_file_language(f)
70 | if len(language) <= 0:
71 | continue
72 | # 语言名称
73 | self.support_languages.append(language)
74 | # 解析多语言的词条
75 | for f in os.listdir(self.appConfig.ios_resources_root_directory):
76 | language = self.__get_ios_file_language(f)
77 | if len(language) <= 0:
78 | continue
79 | path = os.path.join(self.appConfig.ios_resources_root_directory, f, "Localizable.strings")
80 | dist = fileOperator.read_ios_keywords(path)
81 | logging.debug("Read iOS keywords : " + str(dist))
82 | for k, v in dist.items():
83 | if k not in self.keywords:
84 | self.keywords.append(k)
85 | if k not in self.translates:
86 | self.translates[k] = {}
87 | for support_language in self.support_languages:
88 | if support_language != language:
89 | self.translates[k][support_language] = ""
90 | else:
91 | self.translates[k][support_language] = v
92 | # 新增多语言的情况:要为应用初始化的时候选中的多语言设置词条
93 | for sl in self.appConfig.support_languages:
94 | if sl not in self.support_languages:
95 | for k, v in self.translates.items():
96 | self.translates[k][sl] = ""
97 | # 输出用于调试的日志
98 | self.appConfig.add_support_languages(self.support_languages)
99 | logging.debug("Parsed From iOS Resources : " + str(self.support_languages))
100 | logging.debug("Parsed Keywords : " + str(self.keywords))
101 | logging.debug(self.translates)
102 |
103 | # 生成多语言仓库配置文件
104 | def gen_repo_config(self):
105 | json_obj = []
106 | for k, v in self.translates.items():
107 | json_obj.append({"keyword":k, "comment":"", "translates": v})
108 | logging.debug(json_obj)
109 | jsonOperator = JsonOperator()
110 | jsonOperator.write_json(REPO_CONFIG_PATH, json_obj)
111 |
112 | # 导入翻译 excel 文件,将翻译结果按照 keyword 写入配置文件
113 | def import_translated_excel(self, fname):
114 | # 读取 Excel
115 | excelOperator = ExcelOperator()
116 | dists = excelOperator.read_excel(fname)
117 | if TRANSLATE_EXCEL_SHEET_NAME in dists:
118 | # 加载仓库
119 | repository.load()
120 | col_list = dists.get(TRANSLATE_EXCEL_SHEET_NAME)
121 | # 获取所有的关键字
122 | keywords = []
123 | for row in range(1, len(col_list[0])):
124 | keyword = col_list[0][row]
125 | keywords.append(keyword)
126 | # 语言到词条映射
127 | for col in range(1, len(col_list)):
128 | language = ""
129 | translations = []
130 | for row in range(0, len(col_list[col])):
131 | value = col_list[col][row]
132 | if row == 0:
133 | language = value
134 | else:
135 | translations.append(value)
136 | # 词条对应起来
137 | for j in range(0, len(keywords)):
138 | keyword = keywords[j]
139 | translation = translations[j]
140 | # 更新到仓库
141 | repository.update_keyword(keyword, translation, language)
142 | # 重写 repo json
143 | repository.rewrite_repo_json()
144 | logging.debug(dists)
145 |
146 | # 比较改动的文件
147 | def update_ios_resource(self):
148 | repository.load()
149 | fileOperator = FileOperator()
150 | # 更新多语言
151 | for f in os.listdir(self.appConfig.ios_resources_root_directory):
152 | language = self.__get_ios_file_language(f)
153 | if len(language) <= 0:
154 | continue
155 | # 语言名称
156 | repository.try_to_add_new_language(language)
157 | # 更新词条
158 | for f in os.listdir(self.appConfig.ios_resources_root_directory):
159 | language = self.__get_ios_file_language(f)
160 | if len(language) <= 0:
161 | continue
162 | path = os.path.join(self.appConfig.ios_resources_root_directory, f, "Localizable.strings")
163 | dist = fileOperator.read_ios_keywords(path)
164 | for k, v in dist.items():
165 | repository.try_to_add_new_keyword(k, v, language)
166 | # 重写 repo json
167 | repository.rewrite_repo_json()
168 |
169 | # 比较改动的文件
170 | def update_android_resource(self):
171 | repository.load()
172 | # 解析指定的多语言路径中的词条
173 | xmlOperator = XmlOperator()
174 | # 更新多语言
175 | for f in os.listdir(self.appConfig.android_resources_root_directory):
176 | language = self.__get_android_file_language(f)
177 | if len(language) <= 0:
178 | continue
179 | # 语言名称
180 | repository.try_to_add_new_language(language)
181 | # 更新词条
182 | for f in os.listdir(self.appConfig.android_resources_root_directory):
183 | language = self.__get_android_file_language(f)
184 | if len(language) <= 0:
185 | continue
186 | path = os.path.join(self.appConfig.android_resources_root_directory, f, "strings.xml")
187 | # 词条新增 or 变更
188 | dist = xmlOperator.read_android_resources(path)
189 | for k, v in dist.items():
190 | repository.try_to_add_new_keyword(k, v, language)
191 | # 重写 repo json
192 | repository.rewrite_repo_json()
193 |
194 | # 将 iOS 多语言资源修改结果同步到仓库
195 | def import_modified_ios_resource(self):
196 | repository.load()
197 | fileOperator = FileOperator()
198 | # 更新词条
199 | for f in os.listdir(self.appConfig.ios_resources_root_directory):
200 | language = self.__get_ios_file_language(f)
201 | if len(language) <= 0:
202 | continue
203 | path = os.path.join(self.appConfig.ios_resources_root_directory, f, "Localizable.strings")
204 | dist = fileOperator.read_ios_keywords(path)
205 | for k, v in dist.items():
206 | repository.try_ro_modify_keyword(k, v, language)
207 | # 重写 repo json
208 | repository.rewrite_repo_json()
209 |
210 | # 将 Android 多语言资源修改结果同步到仓库
211 | def import_modified_android_resource(self):
212 | repository.load()
213 | xmlOperator = XmlOperator()
214 | # 更新词条
215 | for f in os.listdir(self.appConfig.android_resources_root_directory):
216 | language = self.__get_android_file_language(f)
217 | if len(language) <= 0:
218 | continue
219 | path = os.path.join(self.appConfig.android_resources_root_directory, f, "strings.xml")
220 | # 词条新增 or 变更
221 | dist = xmlOperator.read_android_resources(path)
222 | for k, v in dist.items():
223 | repository.try_ro_modify_keyword(k, v, language)
224 | # 重写 repo json
225 | repository.rewrite_repo_json()
226 |
227 | # Android:从文件名中获取文件的多语言
228 | def __get_android_file_language(self, f):
229 | language = ""
230 | if len(f) > 7:
231 | language = f[7:] # values-xx
232 | else:
233 | language = "default"
234 | # 读取 xml 内容
235 | path = os.path.join(self.appConfig.android_resources_root_directory, f, "strings.xml")
236 | if not os.path.exists(path):
237 | logging.error("Language file not found : " + path)
238 | language = ""
239 | return language
240 |
241 | # iOS:从文件名中获取文件的多语言
242 | def __get_ios_file_language(self, f):
243 | language = ""
244 | if len(f) > 5:
245 | language = f[0:-6]
246 | else:
247 | language = "default"
248 | path = os.path.join(self.appConfig.ios_resources_root_directory, f, "Localizable.strings")
249 | if not os.path.exists(path):
250 | logging.error("Language file not found : " + path)
251 | language = ""
252 | return language
253 |
--------------------------------------------------------------------------------
/app_gui.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | from tkinter import *
5 | import logging
6 | import os
7 | import threading
8 | from tkinter.filedialog import askdirectory
9 | from tkinter.messagebox import askokcancel
10 | from tkinter.filedialog import askopenfilename
11 | from tkinter.messagebox import showinfo
12 | from tkinter.messagebox import showwarning
13 | from initializer import appConfig as appConfig
14 | from repository import repository as repository
15 | from initializer import Initializer
16 | from importer import Importer as Importer
17 | from generator import Generator as Generator
18 | from translator import Translator as Translator
19 | from translator import BaiduTranslator as BaiduTranslator
20 |
21 | # 仓库初始化对话框
22 | class RepoInitDialog(Frame):
23 | # 初始化
24 | def __init__(self, root):
25 | self.root = root
26 | self.android_resources_dir = StringVar()
27 | self.ios_resources_dir = StringVar()
28 | self.support_languages = {}
29 | Frame.__init__(self, root)
30 | # 多语言资源根目录选择
31 | fileFrame = Frame(root)
32 | fileFrame.pack()
33 | Label(fileFrame, text=">> 初始化:项目多语言仓库初始化", justify=CENTER).grid(row=1, column=1)
34 | Label(fileFrame, text="1. 选择 Android 多语言资源根目录:", justify=LEFT).grid(row=2, column=1)
35 | Entry(fileFrame, textvariable=self.android_resources_dir).grid(row=2, column=2)
36 | Button(fileFrame, text="选择", command=self.select_android_directory).grid(row=2, column=3)
37 | Label(fileFrame, text="2. 选择 iOS 多语言资源根目录:", justify=LEFT).grid(row=3, column=1)
38 | Entry(fileFrame, textvariable=self.ios_resources_dir).grid(row=3, column=2)
39 | Button(fileFrame, text="选择", command=self.select_ios_directory).grid(row=3, column=3)
40 | # 选择要支持的语言
41 | languageFrame = Frame(root)
42 | languageFrame.pack()
43 | colCount = -1
44 | for k,v in appConfig.languages.items():
45 | colCount = colCount + 1
46 | self.support_languages[k] = BooleanVar()
47 | Checkbutton(languageFrame, text=k, variable=self.support_languages[k]).grid(row=2, column=colCount)
48 | # 初始化按钮
49 | startFrame = Frame(root)
50 | startFrame.pack()
51 | Button(startFrame, text="初始化", command=self.initialize_repo).grid(row=1, column=1)
52 |
53 | # 选择 Android 多语言资源根目录
54 | def select_android_directory(self):
55 | self.android_resources_dir.set(askdirectory())
56 | logging.debug(self.android_resources_dir)
57 |
58 | # 选择 iOS 多语言资源根目录
59 | def select_ios_directory(self):
60 | self.ios_resources_dir.set(askdirectory())
61 | logging.debug(self.ios_resources_dir)
62 |
63 | # 初始化仓库
64 | def initialize_repo(self):
65 | # 初始化应用配置
66 | support_languages = []
67 | for k,v in self.support_languages.items():
68 | if v.get():
69 | for language in appConfig.languages[k]:
70 | support_languages.append(language)
71 | # 初始化项目仓库
72 | init = Initializer()
73 | init.initialize(self.android_resources_dir.get(), self.ios_resources_dir.get(), support_languages)
74 | # 初始化完毕
75 | result = askokcancel(title = '初始化完成', message='已完成项目仓库初始化,请重启程序')
76 | if result:
77 | self.root.quit()
78 |
79 | # 主程序页面
80 | class MainDialog(Frame):
81 | # 初始化
82 | def __init__(self, root):
83 | self.importer = Importer(appConfig)
84 | self.generator = Generator(appConfig)
85 | self.translate_progress = StringVar()
86 | self.translate_started = False
87 | Frame.__init__(self, root)
88 | frame = Frame(root)
89 | frame.pack()
90 | # 更新词条
91 | # Label(frame, text="", justify=LEFT).grid(row=1, column=1)
92 | Label(frame, text="1.更新词条", justify=LEFT).grid(row=2, column=1)
93 | Button(frame, text="更新 Android 多语言词条", command=self.update_android_resource).grid(row=2, column=2)
94 | Button(frame, text="更新 iOS 多语言词条", command=self.update_ios_resource).grid(row=2, column=3)
95 | # 自助翻译
96 | # Label(frame, text="", justify=LEFT).grid(row=3, column=1)
97 | Label(frame, text="2.自助翻译", justify=LEFT).grid(row=4, column=1)
98 | Button(frame, text="自动翻译", command=self.auto_translate).grid(row=4, column=2)
99 | Label(frame, textvariable=self.translate_progress).grid(row=4, column=3)
100 | # 导入翻译资源 导出翻译资源
101 | # Label(frame, text="", justify=LEFT).grid(row=5, column=1)
102 | Label(frame, text="3.人工翻译", justify=LEFT).grid(row=6, column=1)
103 | Button(frame, text="导出翻译资源(Excel)", command=self.generate_translate_resources).grid(row=6, column=2)
104 | Button(frame, text="导入翻译资源(Excel)", command=self.import_translated_excel).grid(row=6, column=3)
105 | # 生成多语言资源
106 | # Label(frame, text="", justify=LEFT).grid(row=7, column=1)
107 | Label(frame, text="4.生成资源", justify=LEFT).grid(row=8, column=1)
108 | Button(frame, text="生成 Android 多语言资源", command=self.generate_android_resources).grid(row=8, column=2)
109 | Button(frame, text="生成 iOS 多语言资源", command=self.generate_ios_resources).grid(row=8, column=3)
110 | # 校验多语言资源
111 | # Label(frame, text="", justify=LEFT).grid(row=9, column=1)
112 | Label(frame, text="5.校验资源", justify=LEFT).grid(row=10, column=1)
113 | Button(frame, text="将 Android 多语言资源修改结果同步到仓库", command=self.import_modified_android_resource).grid(row=10, column=2)
114 | Button(frame, text="将 iOS 多语言资源修改结果同步到仓库", command=self.import_modified_ios_resource).grid(row=10, column=3)
115 |
116 | # 将 Android 多语言资源修改结果同步到仓库
117 | def import_modified_android_resource(self):
118 | # 如果没有设置过多语言根目录就设置下
119 | if len(appConfig.android_resources_root_directory) == 0:
120 | showinfo(title='提示', message='您在初始化项目的时候并没有为 Android 指定多语言根目录,无法完成更新。您可以尝试备份并删除 repo.json 文件重新初始化项目仓库。')
121 | return
122 | # 开始更新
123 | self.importer.import_modified_android_resource()
124 | showinfo(title='更新完成', message='已更新到多语言仓库!')
125 |
126 | # 将 iOS 多语言资源修改结果同步到仓库
127 | def import_modified_ios_resource(self):
128 | # 如果没有设置过多语言根目录就设置下
129 | if len(appConfig.ios_resources_root_directory) == 0:
130 | showinfo(title='提示', message='您在初始化项目的时候并没有为 iOS 指定多语言根目录,无法完成更新。您可以尝试备份并删除 repo.json 文件重新初始化项目仓库。')
131 | return
132 | # 开始更新
133 | self.importer.import_modified_ios_resource()
134 | showinfo(title='更新完成', message='已更新到多语言仓库!')
135 |
136 | # 生成 Android 多语言资源
137 | def generate_android_resources(self):
138 | # 判断没有翻译的词条数量
139 | ret = repository.get_repo_state()
140 | missed_cuount = ret["missed_count"]
141 | if missed_cuount != 0:
142 | result = askokcancel(title="警告", message="存在 %d 个词条没有完全翻译!仍然生成?" % missed_cuount)
143 | if result:
144 | self.__generate_android_resources_finaly()
145 | else:
146 | self.__generate_android_resources_finaly()
147 |
148 | # 生成 iOS 多语言资源
149 | def generate_ios_resources(self):
150 | # 判断没有翻译的词条数量
151 | ret = repository.get_repo_state()
152 | missed_cuount = ret["missed_count"]
153 | if missed_cuount != 0:
154 | result = askokcancel(title="警告", message="存在 %d 个词条没有完全翻译!仍然生成?" % missed_cuount)
155 | if result:
156 | self.__generate_ios_resources_finaly()
157 | else:
158 | self.__generate_ios_resources_finaly()
159 |
160 | # 生成用来翻译的 Excel 表格
161 | def generate_translate_resources(self):
162 | # 导出到的文件夹
163 | appConfig.translate_excel_output_directory = askdirectory()
164 | appConfig.write_to_json()
165 | # 导出 Excel 文件
166 | self.generator.gen_translate_excel(appConfig.translate_excel_output_directory)
167 | showinfo(title='导出完成', message='已导出翻译 Excel 到 %s !' % appConfig.translate_excel_output_directory)
168 |
169 | # 导入翻译资源
170 | def import_translated_excel(self):
171 | f = askopenfilename(title='选择 Excel 文件', filetypes=[('Excel', '*.xlsx'), ('All Files', '*')])
172 | self.importer.import_translated_excel(f)
173 | showinfo(title='更新完成', message='已更新到多语言仓库!')
174 |
175 | # 更新 Android 多语言
176 | def update_android_resource(self):
177 | # 如果没有设置过多语言根目录就设置下
178 | if len(appConfig.android_resources_root_directory) == 0:
179 | showinfo(title='提示', message='您在初始化项目的时候并没有为 Android 指定多语言根目录,无法完成更新。您可以尝试备份并删除 repo.json 文件重新初始化项目仓库。')
180 | return
181 | # 开始更新
182 | self.importer.update_android_resource()
183 | showinfo(title='更新完成', message='已更新到多语言仓库!')
184 |
185 | # 更新 iOS 多语言
186 | def update_ios_resource(self):
187 | # 如果没有设置过多语言根目录就设置下
188 | if len(appConfig.ios_resources_root_directory) == 0:
189 | showinfo(title='提示', message='您在初始化项目的时候并没有为 iOS 指定多语言根目录,无法完成更新。您可以尝试备份并删除 repo.json 文件重新初始化项目仓库。')
190 | return
191 | # 开始更新
192 | self.importer.update_ios_resource()
193 | showinfo(title='更新完成', message='已更新到多语言仓库!')
194 |
195 | # 自动进行多语言翻译
196 | def auto_translate(self):
197 | bd = BaiduTranslator()
198 | if not bd.is_configed():
199 | showinfo(title='百度 API 没有配置', message='请在 config/baidu.json 文件中填写您在平台申请的 appid 和 appsecret 之后再尝试!')
200 | return
201 | ret = repository.get_repo_state()
202 | missed_cuount = ret["missed_count"]
203 | if self.translate_started:
204 | showinfo(title='翻译已启动', message='翻译已经启动,程序正在翻译中……')
205 | return
206 | if missed_cuount == 0:
207 | showinfo(title='已全部翻译完成', message='所有词条已经翻译完毕,无需进行自动翻译')
208 | else:
209 | thread = threading.Thread(target=self.__start_translate)
210 | thread.start()
211 | self.translate_started = True
212 |
213 | # 正在开始执行翻译
214 | def __start_translate(self):
215 | translator = Translator()
216 | translator.start_translate(self.on_translation_progress_changed, self.on_translation_finished)
217 |
218 | # 通知翻译进度变化
219 | def on_translation_progress_changed(self, progress):
220 | logging.debug("On translation progress changed " + str(progress))
221 | self.translate_progress.set("当前进度: %d%%" % progress)
222 |
223 | # 增加一个完成的回调
224 | def on_translation_finished(self):
225 | self.translate_started = False
226 | showinfo(title='翻译完成', message='已完成翻译任务')
227 |
228 | # 生成 iOS 资源目录
229 | def __generate_ios_resources_finaly(self):
230 | # 如果没设置 iOS 资源目录,则需要选择下
231 | if len(appConfig.ios_resources_root_directory) == 0:
232 | showinfo(title='提示', message='请先选择 iOS 多语言资源根目录')
233 | appConfig.ios_resources_root_directory = askdirectory()
234 | appConfig.write_to_json()
235 | # 生成
236 | self.generator.gen_ios_resources()
237 | showinfo(title='导出完成', message='已导出 iOS 多语言文件到 ' + appConfig.ios_resources_root_directory)
238 |
239 | # 生成 Android 资源目录
240 | def __generate_android_resources_finaly(self):
241 | # 如果没设置 Android 资源目录,则需要选择下
242 | if len(appConfig.android_resources_root_directory) == 0:
243 | showinfo(title='提示', message='请先选择 Android 多语言资源根目录')
244 | appConfig.android_resources_root_directory = askdirectory()
245 | appConfig.write_to_json()
246 | # 生成
247 | self.generator.gen_android_resources()
248 | showinfo(title='导出完成', message='已导出 Android 多语言文件到 ' + appConfig.android_resources_root_directory)
249 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/android/am.md:
--------------------------------------------------------------------------------
1 | Activity manager (activity) commands:
2 | help
3 | Print this help text.
4 | start-activity [-D] [-N] [-W] [-P ] [--start-profiler ]
5 | [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]
6 | [--track-allocation] [--user | current]
7 | Start an Activity. Options are:
8 | -D: enable debugging
9 | -N: enable native debugging
10 | -W: wait for launch to complete
11 | --start-profiler : start profiler and send results to
12 | --sampling INTERVAL: use sample profiling with INTERVAL microseconds
13 | between samples (use with --start-profiler)
14 | --streaming: stream the profiling output to the specified file
15 | (use with --start-profiler)
16 | -P : like above, but profiling stops when app goes idle
17 | --attach-agent : attach the given agent before binding
18 | --attach-agent-bind : attach the given agent during binding
19 | -R: repeat the activity launch times. Prior to each repeat,
20 | the top activity will be finished.
21 | -S: force stop the target app before starting the activity
22 | --track-allocation: enable tracking of object allocations
23 | --user | current: Specify which user to run as; if not
24 | specified then run as the current user.
25 | --windowingMode : The windowing mode to launch the activity into.
26 | --activityType : The activity type to launch the activity as.
27 | --display : The display to launch the activity into.
28 | start-service [--user | current]
29 | Start a Service. Options are:
30 | --user | current: Specify which user to run as; if not
31 | specified then run as the current user.
32 | start-foreground-service [--user | current]
33 | Start a foreground Service. Options are:
34 | --user | current: Specify which user to run as; if not
35 | specified then run as the current user.
36 | stop-service [--user | current]
37 | Stop a Service. Options are:
38 | --user | current: Specify which user to run as; if not
39 | specified then run as the current user.
40 | broadcast [--user | all | current]
41 | Send a broadcast Intent. Options are:
42 | --user | all | current: Specify which user to send to; if not
43 | specified then send to all users.
44 | --receiver-permission : Require receiver to hold permission.
45 | --allow-background-activity-starts: The receiver may start activities
46 | even if in the background.
47 | instrument [-r] [-e ] [-p ] [-w]
48 | [--user | current]
49 | [--no-hidden-api-checks [--no-test-api-access]]
50 | [--no-isolated-storage]
51 | [--no-window-animation] [--abi ]
52 | Start an Instrumentation. Typically this target is in the
53 | form / or only if there
54 | is only one instrumentation. Options are:
55 | -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT). Use with
56 | [-e perf true] to generate raw output for performance measurements.
57 | -e : set argument to . For test runners a
58 | common form is [-e [,...]].
59 | -p : write profiling data to
60 | -m: Write output as protobuf to stdout (machine readable)
61 | -f : Write output as protobuf to a file (machine
62 | readable). If path is not specified, default directory and file name will
63 | be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto
64 | -w: wait for instrumentation to finish before returning. Required for
65 | test runners.
66 | --user | current: Specify user instrumentation runs in;
67 | current user if not specified.
68 | --no-hidden-api-checks: disable restrictions on use of hidden API.
69 | --no-test-api-access: do not allow access to test APIs, if hidden
70 | API checks are enabled.
71 | --no-isolated-storage: don't use isolated storage sandbox and
72 | mount full external storage
73 | --no-window-animation: turn off window animations while running.
74 | --abi : Launch the instrumented process with the selected ABI.
75 | This assumes that the process supports the selected ABI.
76 | trace-ipc [start|stop] [--dump-file ]
77 | Trace IPC transactions.
78 | start: start tracing IPC transactions.
79 | stop: stop tracing IPC transactions and dump the results to file.
80 | --dump-file : Specify the file the trace should be dumped to.
81 | profile start [--user current]
82 | [--sampling INTERVAL | --streaming]
83 | Start profiler on a process. The given argument
84 | may be either a process name or pid. Options are:
85 | --user | current: When supplying a process name,
86 | specify user of process to profile; uses current user if not
87 | specified.
88 | --sampling INTERVAL: use sample profiling with INTERVAL microseconds
89 | between samples.
90 | --streaming: stream the profiling output to the specified file.
91 | profile stop [--user current]
92 | Stop profiler on a process. The given argument
93 | may be either a process name or pid. Options are:
94 | --user | current: When supplying a process name,
95 | specify user of process to profile; uses current user if not
96 | specified.
97 | dumpheap [--user current] [-n] [-g]
98 | Dump the heap of a process. The given argument may
99 | be either a process name or pid. Options are:
100 | -n: dump native heap instead of managed heap
101 | -g: force GC before dumping the heap
102 | --user | current: When supplying a process name,
103 | specify user of process to dump; uses current user if not specified.
104 | set-debug-app [-w] [--persistent]
105 | Set application to debug. Options are:
106 | -w: wait for debugger when application starts
107 | --persistent: retain this value
108 | clear-debug-app
109 | Clear the previously set-debug-app.
110 | set-watch-heap
111 | Start monitoring pss size of , if it is at or
112 | above then a heap dump is collected for the user to report.
113 | clear-watch-heap
114 | Clear the previously set-watch-heap.
115 | clear-exit-info [--user | all | current] [package]
116 | Clear the process exit-info for given package
117 | bug-report [--progress | --telephony]
118 | Request bug report generation; will launch a notification
119 | when done to select where it should be delivered. Options are:
120 | --progress: will launch a notification right away to show its progress.
121 | --telephony: will dump only telephony sections.
122 | force-stop [--user | all | current]
123 | Completely stop the given application package.
124 | crash [--user ]
125 | Induce a VM crash in the specified package or process
126 | kill [--user | all | current]
127 | Kill all background processes associated with the given application.
128 | kill-all
129 | Kill all processes that are safe to kill (cached, etc).
130 | make-uid-idle [--user | all | current]
131 | If the given application's uid is in the background and waiting to
132 | become idle (not allowing background services), do that now.
133 | monitor [--gdb ]
134 | Start monitoring for crashes or ANRs.
135 | --gdb: start gdbserv on the given port at crash/ANR
136 | watch-uids [--oom ]
137 | Start watching for and reporting uid state changes.
138 | --oom: specify a uid for which to report detailed change messages.
139 | hang [--allow-restart]
140 | Hang the system.
141 | --allow-restart: allow watchdog to perform normal system restart
142 | restart
143 | Restart the user-space system.
144 | idle-maintenance
145 | Perform idle maintenance now.
146 | screen-compat [on|off]
147 | Control screen compatibility mode of .
148 | package-importance
149 | Print current importance of .
150 | to-uri [INTENT]
151 | Print the given Intent specification as a URI.
152 | to-intent-uri [INTENT]
153 | Print the given Intent specification as an intent: URI.
154 | to-app-uri [INTENT]
155 | Print the given Intent specification as an android-app: URI.
156 | switch-user
157 | Switch to put USER_ID in the foreground, starting
158 | execution of that user if it is currently stopped.
159 | get-current-user
160 | Returns id of the current foreground user.
161 | start-user [-w]
162 | Start USER_ID in background if it is currently stopped;
163 | use switch-user if you want to start the user in foreground.
164 | -w: wait for start-user to complete and the user to be unlocked.
165 | unlock-user [TOKEN_HEX]
166 | Attempt to unlock the given user using the given authorization token.
167 | stop-user [-w] [-f]
168 | Stop execution of USER_ID, not allowing it to run any
169 | code until a later explicit start or switch to it.
170 | -w: wait for stop-user to complete.
171 | -f: force stop even if there are related users that cannot be stopped.
172 | is-user-stopped
173 | Returns whether has been stopped or not.
174 | get-started-user-state
175 | Gets the current state of the given started user.
176 | track-associations
177 | Enable association tracking.
178 | untrack-associations
179 | Disable and clear association tracking.
180 | get-uid-state
181 | Gets the process state of an app given its .
182 | attach-agent
183 | Attach an agent to the specified , which may be either a process name or a PID.
184 | get-config [--days N] [--device] [--proto] [--display ]
185 | Retrieve the configuration and any recent configurations of the device.
186 | --days: also return last N days of configurations that have been seen.
187 | --device: also output global device configuration info.
188 | --proto: return result as a proto; does not include --days info.
189 | --display: Specify for which display to run the command; if not
190 | specified then run for the default display.
191 | supports-multiwindow
192 | Returns true if the device supports multiwindow.
193 | supports-split-screen-multi-window
194 | Returns true if the device supports split screen multiwindow.
195 | suppress-resize-config-changes
196 | Suppresses configuration changes due to user resizing an activity/task.
197 | set-inactive [--user ] true|false
198 | Sets the inactive state of an app.
199 | get-inactive [--user ]
200 | Returns the inactive state of an app.
201 | set-standby-bucket [--user ] active|working_set|frequent|rare
202 | Puts an app in the standby bucket.
203 | get-standby-bucket [--user ]
204 | Returns the standby bucket of an app.
205 | send-trim-memory [--user ]
206 | [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]
207 | Send a memory trim event to a . May also supply a raw trim int level.
208 | display [COMMAND] [...]: sub-commands for operating on displays.
209 | move-stack
210 | Move from its current display to .
211 | stack [COMMAND] [...]: sub-commands for operating on activity stacks.
212 | move-task [true|false]
213 | Move from its current stack to the top (true) or
214 | bottom (false) of .
215 | resize-docked-stack []
216 | Change docked stack to
217 | and supplying temporary different task bounds indicated by
218 |
219 | move-top-activity-to-pinned-stack:
220 | Moves the top activity from
221 | to the pinned stack using for the
222 | bounds of the pinned stack.
223 | positiontask
224 | Place in at
225 | list
226 | List all of the activity stacks and their sizes.
227 | info
228 | Display the information about activity stack in and .
229 | remove
230 | Remove stack .
231 | task [COMMAND] [...]: sub-commands for operating on activity tasks.
232 | lock
233 | Bring to the front and don't allow other tasks to run.
234 | lock stop
235 | End the current task lock.
236 | resizeable [0|1|2|3]
237 | Change resizeable mode of to one of the following:
238 | 0: unresizeable
239 | 1: crop_windows
240 | 2: resizeable
241 | 3: resizeable_and_pipable
242 | resize
243 | Makes sure is in a stack with the specified bounds.
244 | Forces the task to be resizeable and creates a stack if no existing stack
245 | has the specified bounds.
246 | update-appinfo [...]
247 | Update the ApplicationInfo objects of the listed packages for
248 | without restarting any processes.
249 | write
250 | Write all pending state to storage.
251 | compat [COMMAND] [...]: sub-commands for toggling app-compat changes.
252 | enable|disable|reset
253 | Toggles a change either by id or by name for .
254 | It kills (to allow the toggle to take effect).
255 | enable-all|disable-all .
257 | reset-all
258 | Removes all existing overrides for all changes for
259 | (back to default behaviour).
260 | It kills (to allow the toggle to take effect).
261 |
262 | specifications include these flags and arguments:
263 | [-a ] [-d ] [-t ] [-i ]
264 | [-c [-c ] ...]
265 | [-n ]
266 | [-e|--es ...]
267 | [--esn ...]
268 | [--ez ...]
269 | [--ei ...]
270 | [--el ...]
271 | [--ef ...]
272 | [--eu ...]
273 | [--ecn ]
274 | [--eia [, [,)
278 | [--ela [, [,)
282 | [--efa [, [,)
286 | [--esa [, [,; to embed a comma into a string,
291 | escape it using "\,")
292 | [-f ]
293 | [--grant-read-uri-permission] [--grant-write-uri-permission]
294 | [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]
295 | [--debug-log-resolution] [--exclude-stopped-packages]
296 | [--include-stopped-packages]
297 | [--activity-brought-to-front] [--activity-clear-top]
298 | [--activity-clear-when-task-reset] [--activity-exclude-from-recents]
299 | [--activity-launched-from-history] [--activity-multiple-task]
300 | [--activity-no-animation] [--activity-no-history]
301 | [--activity-no-user-action] [--activity-previous-is-top]
302 | [--activity-reorder-to-front] [--activity-reset-task-if-needed]
303 | [--activity-single-top] [--activity-clear-task]
304 | [--activity-task-on-home] [--activity-match-external]
305 | [--receiver-registered-only] [--receiver-replace-pending]
306 | [--receiver-foreground] [--receiver-no-abort]
307 | [--receiver-include-background]
308 | [--selector]
309 | [ | | ]
310 |
--------------------------------------------------------------------------------
/android/pm.md:
--------------------------------------------------------------------------------
1 | Package manager (package) commands:
2 | help
3 | Print this help text.
4 |
5 | path [--user USER_ID] PACKAGE
6 | Print the path to the .apk of the given PACKAGE.
7 |
8 | dump PACKAGE
9 | Print various system state associated with the given PACKAGE.
10 |
11 | has-feature FEATURE_NAME [version]
12 | Prints true and returns exit status 0 when system has a FEATURE_NAME,
13 | otherwise prints false and returns exit status 1
14 |
15 | list features
16 | Prints all features of the system.
17 |
18 | list instrumentation [-f] [TARGET-PACKAGE]
19 | Prints all test packages; optionally only those targeting TARGET-PACKAGE
20 | Options:
21 | -f: dump the name of the .apk file containing the test package
22 |
23 | list libraries
24 | Prints all system libraries.
25 |
26 | list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U]
27 | [--show-versioncode] [--apex-only] [--uid UID] [--user USER_ID] [FILTER]
28 | Prints all packages; optionally only those whose name contains
29 | the text in FILTER. Options are:
30 | -f: see their associated file
31 | -a: all known packages (but excluding APEXes)
32 | -d: filter to only show disabled packages
33 | -e: filter to only show enabled packages
34 | -s: filter to only show system packages
35 | -3: filter to only show third party packages
36 | -i: see the installer for the packages
37 | -l: ignored (used for compatibility with older releases)
38 | -U: also show the package UID
39 | -u: also include uninstalled packages
40 | --show-versioncode: also show the version code
41 | --apex-only: only show APEX packages
42 | --uid UID: filter to only show packages with the given UID
43 | --user USER_ID: only list packages belonging to the given user
44 |
45 | list permission-groups
46 | Prints all known permission groups.
47 |
48 | list permissions [-g] [-f] [-d] [-u] [GROUP]
49 | Prints all known permissions; optionally only those in GROUP. Options are:
50 | -g: organize by group
51 | -f: print all information
52 | -s: short summary
53 | -d: only list dangerous permissions
54 | -u: list only the permissions users will see
55 |
56 | list staged-sessions [--only-ready] [--only-sessionid] [--only-parent]
57 | Prints all staged sessions.
58 | --only-ready: show only staged sessions that are ready
59 | --only-sessionid: show only sessionId of each session
60 | --only-parent: hide all children sessions
61 |
62 | list users
63 | Prints all users.
64 |
65 | resolve-activity [--brief] [--components] [--query-flags FLAGS]
66 | [--user USER_ID] INTENT
67 | Prints the activity that resolves to the given INTENT.
68 |
69 | query-activities [--brief] [--components] [--query-flags FLAGS]
70 | [--user USER_ID] INTENT
71 | Prints all activities that can handle the given INTENT.
72 |
73 | query-services [--brief] [--components] [--query-flags FLAGS]
74 | [--user USER_ID] INTENT
75 | Prints all services that can handle the given INTENT.
76 |
77 | query-receivers [--brief] [--components] [--query-flags FLAGS]
78 | [--user USER_ID] INTENT
79 | Prints all broadcast receivers that can handle the given INTENT.
80 |
81 | install [-rtfdgw] [-i PACKAGE] [--user USER_ID|all|current]
82 | [-p INHERIT_PACKAGE] [--install-location 0/1/2]
83 | [--install-reason 0/1/2/3/4] [--originating-uri URI]
84 | [--referrer URI] [--abi ABI_NAME] [--force-sdk]
85 | [--preload] [--instant] [--full] [--dont-kill]
86 | [--enable-rollback]
87 | [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]
88 | [--apex] [--wait TIMEOUT]
89 | [PATH [SPLIT...]|-]
90 | Install an application. Must provide the apk data to install, either as
91 | file path(s) or '-' to read from stdin. Options are:
92 | -R: disallow replacement of existing application
93 | -t: allow test packages
94 | -i: specify package name of installer owning the app
95 | -f: install application on internal flash
96 | -d: allow version code downgrade (debuggable packages only)
97 | -p: partial application install (new split on top of existing pkg)
98 | -g: grant all runtime permissions
99 | -S: size in bytes of package, required for stdin
100 | --user: install under the given user.
101 | --dont-kill: installing a new feature split, don't kill running app
102 | --restrict-permissions: don't whitelist restricted permissions at install
103 | --originating-uri: set URI where app was downloaded from
104 | --referrer: set URI that instigated the install of the app
105 | --pkg: specify expected package name of app being installed
106 | --abi: override the default ABI of the platform
107 | --instant: cause the app to be installed as an ephemeral install app
108 | --full: cause the app to be installed as a non-ephemeral full app
109 | --install-location: force the install location:
110 | 0=auto, 1=internal only, 2=prefer external
111 | --install-reason: indicates why the app is being installed:
112 | 0=unknown, 1=admin policy, 2=device restore,
113 | 3=device setup, 4=user request
114 | --force-uuid: force install on to disk volume with given UUID
115 | --apex: install an .apex file, not an .apk
116 | --wait: when performing staged install, wait TIMEOUT milliseconds
117 | for pre-reboot verification to complete. If TIMEOUT is not
118 | specified it will wait for 60000 milliseconds.
119 |
120 | install-existing [--user USER_ID|all|current]
121 | [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE
122 | Installs an existing application for a new user. Options are:
123 | --user: install for the given user.
124 | --instant: install as an instant app
125 | --full: install as a full app
126 | --wait: wait until the package is installed
127 | --restrict-permissions: don't whitelist restricted permissions
128 |
129 | install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]
130 | [-p INHERIT_PACKAGE] [--install-location 0/1/2]
131 | [--install-reason 0/1/2/3/4] [--originating-uri URI]
132 | [--referrer URI] [--abi ABI_NAME] [--force-sdk]
133 | [--preload] [--instant] [--full] [--dont-kill]
134 | [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]
135 | [--multi-package] [--staged]
136 | Like "install", but starts an install session. Use "install-write"
137 | to push data into the session, and "install-commit" to finish.
138 |
139 | install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]
140 | Write an apk into the given install session. If the path is '-', data
141 | will be read from stdin. Options are:
142 | -S: size in bytes of package, required for stdin
143 |
144 | install-remove SESSION_ID SPLIT...
145 | Mark SPLIT(s) as removed in the given install session.
146 |
147 | install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs
148 | Add one or more session IDs to a multi-package session.
149 |
150 | install-commit SESSION_ID
151 | Commit the given active install session, installing the app.
152 |
153 | install-abandon SESSION_ID
154 | Delete the given active install session.
155 |
156 | set-install-location LOCATION
157 | Changes the default install location. NOTE this is only intended for debugging;
158 | using this can cause applications to break and other undersireable behavior.
159 | LOCATION is one of:
160 | 0 [auto]: Let system decide the best location
161 | 1 [internal]: Install on internal device storage
162 | 2 [external]: Install on external media
163 |
164 | get-install-location
165 | Returns the current install location: 0, 1 or 2 as per set-install-location.
166 |
167 | move-package PACKAGE [internal|UUID]
168 |
169 | move-primary-storage [internal|UUID]
170 |
171 | uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE]
172 | PACKAGE [SPLIT...]
173 | Remove the given package name from the system. May remove an entire app
174 | if no SPLIT names specified, otherwise will remove only the splits of the
175 | given app. Options are:
176 | -k: keep the data and cache directories around after package removal.
177 | --user: remove the app from the given user.
178 | --versionCode: only uninstall if the app has the given version code.
179 |
180 | clear [--user USER_ID] PACKAGE
181 | Deletes all data associated with a package.
182 |
183 | enable [--user USER_ID] PACKAGE_OR_COMPONENT
184 | disable [--user USER_ID] PACKAGE_OR_COMPONENT
185 | disable-user [--user USER_ID] PACKAGE_OR_COMPONENT
186 | disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT
187 | default-state [--user USER_ID] PACKAGE_OR_COMPONENT
188 | These commands change the enabled state of a given package or
189 | component (written as "package/class").
190 |
191 | hide [--user USER_ID] PACKAGE_OR_COMPONENT
192 | unhide [--user USER_ID] PACKAGE_OR_COMPONENT
193 |
194 | suspend [--user USER_ID] TARGET-PACKAGE
195 | Suspends the specified package (as user).
196 |
197 | unsuspend [--user USER_ID] TARGET-PACKAGE
198 | Unsuspends the specified package (as user).
199 |
200 | grant [--user USER_ID] PACKAGE PERMISSION
201 | revoke [--user USER_ID] PACKAGE PERMISSION
202 | These commands either grant or revoke permissions to apps. The permissions
203 | must be declared as used in the app's manifest, be runtime permissions
204 | (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.
205 |
206 | reset-permissions
207 | Revert all runtime permissions to their default state.
208 |
209 | set-permission-enforced PERMISSION [true|false]
210 |
211 | get-privapp-permissions TARGET-PACKAGE
212 | Prints all privileged permissions for a package.
213 |
214 | get-privapp-deny-permissions TARGET-PACKAGE
215 | Prints all privileged permissions that are denied for a package.
216 |
217 | get-oem-permissions TARGET-PACKAGE
218 | Prints all OEM permissions for a package.
219 |
220 | set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}
221 | get-app-link [--user USER_ID] PACKAGE
222 |
223 | trim-caches DESIRED_FREE_SPACE [internal|UUID]
224 | Trim cache files to reach the given free space.
225 |
226 | list users
227 | Lists the current users.
228 |
229 | create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]
230 | [--guest] [--pre-create-only] [--user-type USER_TYPE] USER_NAME
231 | Create a new user with the given USER_NAME, printing the new user identifier
232 | of the user.
233 | USER_TYPE is the name of a user type, e.g. android.os.usertype.profile.MANAGED.
234 | If not specified, the default user type is android.os.usertype.full.SECONDARY.
235 | --managed is shorthand for '--user-type android.os.usertype.profile.MANAGED'.
236 | --restricted is shorthand for '--user-type android.os.usertype.full.RESTRICTED'.
237 | --guest is shorthand for '--user-type android.os.usertype.full.GUEST'.
238 |
239 | remove-user USER_ID
240 | Remove the user with the given USER_IDENTIFIER, deleting all data
241 | associated with that user
242 |
243 | set-user-restriction [--user USER_ID] RESTRICTION VALUE
244 |
245 | get-max-users
246 |
247 | get-max-running-users
248 |
249 | compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]
250 | [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)
251 | Trigger compilation of TARGET-PACKAGE or all packages if "-a". Options are:
252 | -a: compile all packages
253 | -c: clear profile data before compiling
254 | -f: force compilation even if not needed
255 | -m: select compilation mode
256 | MODE is one of the dex2oat compiler filters:
257 | assume-verified
258 | extract
259 | verify
260 | quicken
261 | space-profile
262 | space
263 | speed-profile
264 | speed
265 | everything
266 | -r: select compilation reason
267 | REASON is one of:
268 | first-boot
269 | boot
270 | install
271 | bg-dexopt
272 | ab-ota
273 | inactive
274 | shared
275 | --reset: restore package to its post-install state
276 | --check-prof (true | false): look at profiles when doing dexopt?
277 | --secondary-dex: compile app secondary dex files
278 | --split SPLIT: compile only the given split name
279 | --compile-layouts: compile layout resources for faster inflation
280 |
281 | force-dex-opt PACKAGE
282 | Force immediate execution of dex opt for the given PACKAGE.
283 |
284 | bg-dexopt-job
285 | Execute the background optimizations immediately.
286 | Note that the command only runs the background optimizer logic. It may
287 | overlap with the actual job but the job scheduler will not be able to
288 | cancel it. It will also run even if the device is not in the idle
289 | maintenance mode.
290 |
291 | reconcile-secondary-dex-files TARGET-PACKAGE
292 | Reconciles the package secondary dex files with the generated oat files.
293 |
294 | dump-profiles TARGET-PACKAGE
295 | Dumps method/class profile files to
296 | /data/misc/profman/TARGET-PACKAGE.txt
297 |
298 | snapshot-profile TARGET-PACKAGE [--code-path path]
299 | Take a snapshot of the package profiles to
300 | /data/misc/profman/TARGET-PACKAGE[-code-path].prof
301 | If TARGET-PACKAGE=android it will take a snapshot of the boot image
302 |
303 | set-home-activity [--user USER_ID] TARGET-COMPONENT
304 | Set the default home activity (aka launcher).
305 | TARGET-COMPONENT can be a package name (com.package.my) or a full
306 | component (com.package.my/component.name). However, only the package name
307 | matters: the actual component used will be determined automatically from
308 | the package.
309 |
310 | set-installer PACKAGE INSTALLER
311 | Set installer package name
312 |
313 | get-instantapp-resolver
314 | Return the name of the component that is the current instant app installer.
315 |
316 | set-harmful-app-warning [--user ] []
317 | Mark the app as harmful with the given warning message.
318 |
319 | get-harmful-app-warning [--user ]
320 | Return the harmful app warning message for the given app, if present
321 |
322 | uninstall-system-updates []
323 | Removes updates to the given system application and falls back to its
324 | /system version. Does nothing if the given package is not a system app.
325 | If no package is specified, removes updates to all system applications.
326 |
327 | get-moduleinfo [--all | --installed] [module-name]
328 | Displays module info. If module-name is specified only that info is shown
329 | By default, without any argument only installed modules are shown.
330 | --all: show all module info
331 | --installed: show only installed modules
332 |
333 | log-visibility [--enable|--disable]
334 | Turns on debug logging when visibility is blocked for the given package.
335 | --enable: turn on debug logging (default)
336 | --disable: turn off debug logging
337 |
338 | specifications include these flags and arguments:
339 | [-a ] [-d ] [-t ] [-i ]
340 | [-c [-c ] ...]
341 | [-n ]
342 | [-e|--es ...]
343 | [--esn ...]
344 | [--ez ...]
345 | [--ei ...]
346 | [--el ...]
347 | [--ef ...]
348 | [--eu ...]
349 | [--ecn ]
350 | [--eia [, [,)
354 | [--ela [, [,)
358 | [--efa [, [,)
362 | [--esa [,