Update scripts for Android build

 - Use d8 instead of dx due to deprecation
 - Use apksigner instead of jarsigner for signature scheme v2 required by newer SDKs
 - Move apk signing to be last step (required for signature scheme v2)
 - Force uncompressed arsc files (required for SDK versions 30 and up)
 - Add '--tool-api' argument for selecting build tools version
 - Add permission for reading external storage in manifest

VK-GL-CTS Issue: 4163

Components: Framework
Change-Id: Iea4429f861148168ffe680cdf9a497cb467b8d6a
diff --git a/android/package/AndroidManifest.xml b/android/package/AndroidManifest.xml
index 53b1108..ddd8919 100644
--- a/android/package/AndroidManifest.xml
+++ b/android/package/AndroidManifest.xml
@@ -35,6 +35,7 @@
 	</application>
 
 	<uses-feature android:glEsVersion="0x00020000"/>
+	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 	<uses-permission android:name="android.permission.GET_TASKS" />
 	<uses-permission android:name="android.permission.INTERNET" />
diff --git a/external/vulkancts/README.md b/external/vulkancts/README.md
index 1ba58f6..b66b139 100644
--- a/external/vulkancts/README.md
+++ b/external/vulkancts/README.md
@@ -224,7 +224,9 @@
 
 ### Android
 
-	adb push <vulkancts>/external/vulkancts/mustpass/master/vk-default.txt /sdcard/vk-default.txt
+For Android build using SDK 29 or greater, it is recommended to use `/sdcard/Documents/` instead of `/sdcard/` due to scoped storage.
+
+	adb push <vulkancts>/external/vulkancts/mustpass/main/vk-default.txt /sdcard/vk-default.txt
 	adb shell
 
 In device shell:
diff --git a/scripts/android/build_apk.py b/scripts/android/build_apk.py
index cad3269..b39d4b7 100644
--- a/scripts/android/build_apk.py
+++ b/scripts/android/build_apk.py
@@ -44,38 +44,40 @@
 from build.build import *
 
 class SDKEnv:
-	def __init__(self, path):
+	def __init__(self, path, desired_version):
 		self.path				= path
-		self.buildToolsVersion	= SDKEnv.selectBuildToolsVersion(self.path)
+		self.buildToolsVersion	= SDKEnv.selectBuildToolsVersion(self.path, desired_version)
 
 	@staticmethod
 	def getBuildToolsVersions (path):
 		buildToolsPath	= os.path.join(path, "build-tools")
-		versions		= []
+		versions		= {}
 
 		if os.path.exists(buildToolsPath):
 			for item in os.listdir(buildToolsPath):
 				m = re.match(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$', item)
 				if m != None:
-					versions.append((int(m.group(1)), int(m.group(2)), int(m.group(3))))
+					versions[int(m.group(1))] = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
 
 		return versions
 
 	@staticmethod
-	def selectBuildToolsVersion (path):
-		preferred	= [(25, 0, 2)]
+	def selectBuildToolsVersion (path, preferred):
 		versions	= SDKEnv.getBuildToolsVersions(path)
 
 		if len(versions) == 0:
 			return (0,0,0)
 
-		for candidate in preferred:
-			if candidate in versions:
-				return candidate
+		if preferred == -1:
+			return max(versions.values())
+
+		if preferred in versions:
+			return versions[preferred]
 
 		# Pick newest
-		versions.sort()
-		return versions[-1]
+		newest_version = max(versions.values())
+		print("Couldn't find Android Tool version %d, %d was selected." % (preferred, newest_version[0]))
+		return newest_version
 
 	def getPlatformLibrary (self, apiVersion):
 		return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar")
@@ -169,19 +171,20 @@
 		self.ndk		= ndk
 
 class Configuration:
-	def __init__(self, env, buildPath, abis, nativeApi, minApi, nativeBuildType, gtfTarget, verbose, layers, angle):
+	def __init__(self, env, buildPath, abis, nativeApi, javaApi, minApi, nativeBuildType, gtfTarget, verbose, layers, angle):
 		self.env				= env
 		self.sourcePath			= DEQP_DIR
 		self.buildPath			= buildPath
 		self.abis				= abis
 		self.nativeApi			= nativeApi
-		self.javaApi			= 28
+		self.javaApi			= javaApi
 		self.minApi				= minApi
 		self.nativeBuildType	= nativeBuildType
 		self.gtfTarget			= gtfTarget
 		self.verbose			= verbose
 		self.layers				= layers
 		self.angle				= angle
+		self.dCompilerName		= "d8"
 		self.cmakeGenerator		= selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR])
 
 	def check (self):
@@ -203,12 +206,20 @@
 		if self.env.sdk.buildToolsVersion == (0,0,0):
 			raise Exception("No build tools directory found at %s" % os.path.join(self.env.sdk.path, "build-tools"))
 
-		androidBuildTools = ["aapt", "zipalign", "dx"]
+		if not os.path.exists(os.path.join(self.env.sdk.path, "platforms", "android-%d" % self.javaApi)):
+			raise Exception("No SDK with api version %d directory found at %s for Java Api" % (self.javaApi, os.path.join(self.env.sdk.path, "platforms")))
+
+		# Try to find first d8 since dx was deprecated
+		if which(self.dCompilerName, [self.env.sdk.getBuildToolsPath()]) == None:
+			print("Couldn't find %s, will try to find dx", self.dCompilerName)
+			self.dCompilerName = "dx"
+
+		androidBuildTools = ["aapt", "zipalign", "apksigner", self.dCompilerName]
 		for tool in androidBuildTools:
 			if which(tool, [self.env.sdk.getBuildToolsPath()]) == None:
-				raise Exception("Missing Android build tool: %s" % tool)
+				raise Exception("Missing Android build tool: %s in %s" % (tool, self.env.sdk.getBuildToolsPath()))
 
-		requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"]
+		requiredToolsInPath = ["javac", "jar", "keytool"]
 		for tool in requiredToolsInPath:
 			if which(tool) == None:
 				raise Exception("%s not in PATH" % tool)
@@ -426,6 +437,9 @@
 	def getClassesJarPath (self):
 		return [BuildRoot(), self.appDirName, "bin", "classes.jar"]
 
+	def getClassesDexDirectory (self):
+		return [BuildRoot(), self.appDirName, "bin",]
+
 	def getClassesDexPath (self):
 		return [BuildRoot(), self.appDirName, "bin", "classes.dex"]
 
@@ -549,19 +563,23 @@
 		return [self.package.getClassesDexPath()]
 
 	def update (self, config):
-		dxPath		= which("dx", [config.env.sdk.getBuildToolsPath()])
-		srcPaths	= resolvePaths(config, self.getInputs())
-		dexPath		= resolvePath(config, self.package.getClassesDexPath())
+		dxPath		= which(config.dCompilerName, [config.env.sdk.getBuildToolsPath()])
+		dexPath		= resolvePath(config, self.package.getClassesDexDirectory())
 		jarPaths	= [resolvePath(config, self.package.getClassesJarPath())]
 
 		for lib in self.libraries:
 			jarPaths.append(resolvePath(config, lib.getClassesJarPath()))
 
-		executeAndLog(config, [
-				dxPath,
-				"--dex",
-				"--output", dexPath
-			] + jarPaths)
+		args = [ dxPath ]
+		if config.dCompilerName == "d8":
+			args.append("--lib")
+			args.append(config.env.sdk.getPlatformLibrary(config.javaApi))
+		else:
+			args.append("--dex")
+		args.append("--output")
+		args.append(dexPath)
+
+		executeAndLog(config, args + jarPaths)
 
 class CreateKeystore (BuildStep):
 	def __init__ (self):
@@ -576,7 +594,7 @@
 	def update (self, config):
 		executeAndLog(config, [
 				"keytool",
-				"-genkey",
+				"-genkeypair",
 				"-keystore", resolvePath(config, self.keystorePath),
 				"-storepass", "android",
 				"-alias", "androiddebugkey",
@@ -623,6 +641,7 @@
 			"-M", resolvePath(config, self.package.getManifestPath()),
 			"-I", config.env.sdk.getPlatformLibrary(config.javaApi),
 			"-F", dstPath,
+			"-0", "arsc" # arsc files need to be uncompressed for SDK version 30 and up
 		]
 
 		for resPath in self.getResPaths():
@@ -777,8 +796,8 @@
 class SignAPK (BuildStep):
 	def __init__ (self, package):
 		self.package		= package
-		self.srcPath		= AddNativeLibsToAPK(self.package, []).getOutputs()[0]
-		self.dstPath		= [BuildRoot(), self.package.getAppDirName(), "tmp", "signed.apk"]
+		self.srcPath		= AlignAPK(self.package).getOutputs()[0]
+		self.dstPath		= [BuildRoot(), getBuildRootRelativeAPKPath(self.package)]
 		self.keystorePath	= CreateKeystore().getOutputs()[0]
 
 	def getInputs (self):
@@ -788,27 +807,31 @@
 		return [self.dstPath]
 
 	def update (self, config):
+		apksigner	= which("apksigner", [config.env.sdk.getBuildToolsPath()])
 		srcPath		= resolvePath(config, self.srcPath)
 		dstPath		= resolvePath(config, self.dstPath)
 
 		executeAndLog(config, [
-				"jarsigner",
-				"-keystore", resolvePath(config, self.keystorePath),
-				"-storepass", "android",
-				"-keypass", "android",
-				"-signedjar", dstPath,
-				srcPath,
-				"androiddebugkey"
+				apksigner,
+				"sign",
+				"--ks", resolvePath(config, self.keystorePath),
+				"--ks-key-alias", "androiddebugkey",
+				"--ks-pass", "pass:android",
+				"--key-pass", "pass:android",
+				"--min-sdk-version", str(config.minApi),
+				"--max-sdk-version", str(config.javaApi),
+				"--out", dstPath,
+				srcPath
 			])
 
 def getBuildRootRelativeAPKPath (package):
 	return os.path.join(package.getAppDirName(), package.getAppName() + ".apk")
 
-class FinalizeAPK (BuildStep):
+class AlignAPK (BuildStep):
 	def __init__ (self, package):
 		self.package		= package
-		self.srcPath		= SignAPK(self.package).getOutputs()[0]
-		self.dstPath		= [BuildRoot(), getBuildRootRelativeAPKPath(self.package)]
+		self.srcPath		= AddNativeLibsToAPK(self.package, []).getOutputs()[0]
+		self.dstPath		= [BuildRoot(), self.package.getAppDirName(), "tmp", "aligned.apk"]
 		self.keystorePath	= CreateKeystore().getOutputs()[0]
 
 	def getInputs (self):
@@ -862,8 +885,8 @@
 
 	# Finalize APK
 	steps.append(CreateKeystore())
+	steps.append(AlignAPK(package))
 	steps.append(SignAPK(package))
-	steps.append(FinalizeAPK(package))
 
 	return steps
 
@@ -921,6 +944,16 @@
 		dest='nativeApi',
 		default=28,
 		help="Android API level to target in native code")
+	parser.add_argument('--java-api',
+		type=int,
+		dest='javaApi',
+		default=28,
+		help="Android API level to target in Java code")
+	parser.add_argument('--tool-api',
+		type=int,
+		dest='toolApi',
+		default=-1,
+		help="Android Tools level to target (-1 being maximum present)")
 	parser.add_argument('--min-api',
 		type=int,
 		dest='minApi',
@@ -990,10 +1023,10 @@
 	args		= parseArgs()
 
 	ndk			= NDKEnv(os.path.realpath(args.ndkPath))
-	sdk			= SDKEnv(os.path.realpath(args.sdkPath))
+	sdk			= SDKEnv(os.path.realpath(args.sdkPath), args.toolApi)
 	buildPath	= os.path.realpath(args.buildRoot)
 	env			= Environment(sdk, ndk)
-	config		= Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, minApi=args.minApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget,
+	config		= Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, javaApi=args.javaApi, minApi=args.minApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget,
 						 verbose=args.verbose, layers=args.layers, angle=args.angle)
 
 	try: