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] 32platformSdkVersion = sys.argv[5] 33genDir = sys.argv[6] 34inputFiles = sys.argv[7:] 35 36tempDir = f"{genDir}/temp" 37outDir = f"{genDir}/out" 38 39os.makedirs(tempDir, exist_ok=True) 40os.makedirs(outDir, exist_ok=True) 41 42packageNamesOutput = open(f"{outDir}/{GENERATED_APK_PACKAGE_NAMES_FILE}", "w") 43 44usedHashCodes = {} 45 46for inputFile in inputFiles: 47 text = open(inputFile, "r").read() 48 49 annotationIndex = 0 50 while True: 51 try: 52 annotationIndex = text.index(ANNOTATION, annotationIndex) 53 if annotationIndex < 0: 54 break 55 except: 56 break 57 58 annotationIndex += len(ANNOTATION) 59 startIndex = text.index(TRIPLE_QUOTE, annotationIndex) 60 if startIndex < 0: 61 continue 62 63 endIndex = text.index(TRIPLE_QUOTE, startIndex + len(TRIPLE_QUOTE)) 64 if endIndex < 0: 65 continue 66 67 string = text[startIndex + len(TRIPLE_QUOTE): endIndex] 68 hashCode = java_string_hashcode(string) 69 70 if hashCode in usedHashCodes: 71 if usedHashCodes[hashCode] != string: 72 sys.exit(f"Manifest XML with hash code {hashCode} already used: {string}") 73 usedHashCodes[hashCode] = string 74 75 if ANDROID_NAMESPACE not in string: 76 string = string.replace("<manifest", f"<manifest {ANDROID_NAMESPACE}\n", 1) 77 78 packageName = PACKAGE_NAME_PREFIX + hashCode 79 string = string.replace(">", f"\npackage=\"{packageName}\"\n>", 1) 80 string = string.replace("<application", "<application\nandroid:hasCode=\"false\"\n") 81 major_version = platformSdkVersion.split(".")[0] 82 string = string.replace("platformSdkVersion", major_version) 83 84 outputPath = f"{tempDir}/{hashCode}.xml" 85 outputFile = open(outputPath, "w") 86 outputFile.write(string) 87 outputFile.close() 88 89 packageNamesOutput.write(packageName) 90 packageNamesOutput.write("\n") 91 92 apkPath = f"{outDir}/{hashCode}.apk" 93 94 subprocess.run([ 95 aapt2, "link", 96 "-I", frameworkRes, 97 "--manifest", outputPath, 98 "--warn-manifest-validation", 99 "--rename-manifest-package", packageName, 100 "-o", apkPath 101 ], check = True) 102 103 subprocess.run([ 104 apkSigner, "sign", 105 "--ks", keyStore, 106 "--ks-pass", "pass:password", 107 apkPath 108 ], check = True) 109 110 # apksigner will generate an idsig file, but it's not useful for the test, so get rid of it 111 idsigPath = f"{outDir}/{hashCode}.idsig" 112 if os.path.isfile(idsigPath): 113 os.remove(idsigPath) 114