1""" 2Parses @AndroidManifestXml annotations to pre-generate APKs for tests 3 4Assumes a Kotlin triple quoted string literal which is prepended by a custom annotation. See the 5AndroidManifestXml annotation for more information. 6""" 7 8import subprocess 9import sys 10import os 11 12ANNOTATION = "@AndroidManifestXml" 13TRIPLE_QUOTE = "\"\"\"" 14PACKAGE_NAME_PREFIX = "android.content.pm.parsing.cts.generated" 15GENERATED_APK_PACKAGE_NAMES_FILE = "GeneratedApkPackageNames.txt" 16ANDROID_NAMESPACE = "xmlns:android=\"http://schemas.android.com/apk/res/android\"" 17 18def java_string_hashcode(string): 19 """ 20 Simulates Java's hashCode so that APKs can be looked up at runtime by using the manifest 21 hashCode as the file name. 22 """ 23 hash = 0 24 for char in string: 25 hash = int((((31 * hash + ord(char)) ^ 0x80000000) & 0xFFFFFFFF) - 0x80000000) 26 return str(abs(hash)) 27 28aapt2 = sys.argv[1] 29frameworkRes = sys.argv[2] 30apkSigner = sys.argv[3] 31keyStore = sys.argv[4] 32genDir = sys.argv[5] 33inputFiles = sys.argv[6:] 34 35tempDir = f"{genDir}/temp" 36outDir = f"{genDir}/out" 37 38os.makedirs(tempDir, exist_ok=True) 39os.makedirs(outDir, exist_ok=True) 40 41packageNamesOutput = open(f"{outDir}/{GENERATED_APK_PACKAGE_NAMES_FILE}", "w") 42 43usedHashCodes = {} 44 45for inputFile in inputFiles: 46 text = open(inputFile, "r").read() 47 48 annotationIndex = 0 49 while True: 50 try: 51 annotationIndex = text.index(ANNOTATION, annotationIndex) 52 if annotationIndex < 0: 53 break 54 except: 55 break 56 57 annotationIndex += len(ANNOTATION) 58 startIndex = text.index(TRIPLE_QUOTE, annotationIndex) 59 if startIndex < 0: 60 continue 61 62 endIndex = text.index(TRIPLE_QUOTE, startIndex + len(TRIPLE_QUOTE)) 63 if endIndex < 0: 64 continue 65 66 string = text[startIndex + len(TRIPLE_QUOTE): endIndex] 67 hashCode = java_string_hashcode(string) 68 69 if hashCode in usedHashCodes: 70 if usedHashCodes[hashCode] != string: 71 sys.exit(f"Manifest XML with hash code {hashCode} already used: {string}") 72 usedHashCodes[hashCode] = string 73 74 if ANDROID_NAMESPACE not in string: 75 string = string.replace("<manifest", f"<manifest {ANDROID_NAMESPACE}\n", 1) 76 77 packageName = PACKAGE_NAME_PREFIX + hashCode 78 string = string.replace(">", f"\npackage=\"{packageName}\"\n>", 1) 79 string = string.replace("<application", "<application\nandroid:hasCode=\"false\"\n") 80 81 outputPath = f"{tempDir}/{hashCode}.xml" 82 outputFile = open(outputPath, "w") 83 outputFile.write(string) 84 outputFile.close() 85 86 packageNamesOutput.write(packageName) 87 packageNamesOutput.write("\n") 88 89 apkPath = f"{outDir}/{hashCode}.apk" 90 91 subprocess.run([ 92 aapt2, "link", 93 "-I", frameworkRes, 94 "--manifest", outputPath, 95 "--warn-manifest-validation", 96 "--rename-manifest-package", packageName, 97 "-o", apkPath 98 ], check = True) 99 100 subprocess.run([ 101 apkSigner, "sign", 102 "--ks", keyStore, 103 "--ks-pass", "pass:password", 104 apkPath 105 ], check = True) 106 107 # apksigner will generate an idsig file, but it's not useful for the test, so get rid of it 108 idsigPath = f"{outDir}/{hashCode}.idsig" 109 if os.path.isfile(idsigPath): 110 os.remove(idsigPath) 111