Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions watchface/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,4 @@ example, using `pngquant` on all images, to help keep the watch face size to a m

To package the watch face, the [Pack](https://github.com/google/pack) is used. This is a native
library, so the pre-builts are provided in `jniLibs`. A script is also included for building these
fresh, but that should not be necessary.

## Signing the APK

For the purposes of this project, a key is generated at runtime and used to sign the APK. This is
not the approach to take in production, but the watch face APK must be signed, and it doesn't so
much matter what key is used to do it.
fresh, but that should not be necessary.
5 changes: 4 additions & 1 deletion watchface/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = 36
testInstrumentationRunner = "com.android.developers.testing.AndroidifyTestRunner"
consumerProguardFiles("proguard-rules.pro")
}
buildFeatures {
buildConfig = true
}

compileOptions {
sourceCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
targetCompatibility = JavaVersion.toVersion(libs.versions.javaVersion.get())
Expand Down
10 changes: 8 additions & 2 deletions watchface/pack-java/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion watchface/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,29 @@
-dontwarn org.eclipse.wst.xml.xpath2.processor.**

# Ignore missing Java SE annotation processing classes, often from libraries like AutoValue/JavaPoet
-dontwarn javax.lang.model.**
-dontwarn javax.lang.model.**

-keep class com.android.developers.androidify.watchface.creator.PackPackage {
native <methods>;
}

-keep class com.android.developers.androidify.watchface.creator.PackPackage$Resource { *; }

# Keep all classes in the BouncyCastle provider, as they are loaded via reflection
-keep class org.bouncycastle.** { *; }
-keep interface org.bouncycastle.** { *; }

# Keep the APK Signer library
-keep class com.android.apksig.** { *; }
-keep interface com.android.apksig.** { *; }

# Keep Apache Xerces XML parser
-keep class org.apache.xerces.** { *; }

## Keep standard Java XML (JAXP), DOM, and SAX interfaces and classes
-keep interface org.w3c.dom.** { *; }
-keep class org.w3c.dom.** { *; }
-keep interface org.xml.sax.** { *; }
-keep class org.xml.sax.** { *; }
-keep class javax.xml.** { *; }
-keep interface javax.xml.** { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ data class PackPackage(
var resources: MutableList<Resource> = mutableListOf()

data class Resource(
val subdirectory: String,
val name: String,
val contentsBase64: String,
@JvmField val subdirectory: String,
@JvmField val name: String,
@JvmField val contentsBase64: String,
) {
companion object {
fun fromBase64Contents(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.android.apksig.util.DataSink
import com.android.apksig.util.DataSource
import com.android.apksig.util.DataSources
import com.android.apksig.util.ReadableDataSink
import com.android.developers.androidify.watchface.BuildConfig
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
Expand All @@ -38,8 +39,10 @@ import java.security.cert.X509Certificate
import java.util.Calendar
import java.util.Date

private const val KEY_ALIAS = "com.android.developers.androidify.ApkSigningKey"
private const val CERT_ALIAS = "com.android.developers.androidify.Cert"
private val keyAlias : String
get() = "com.android.developers.androidify.ApkSigningKey-" + BuildConfig.BUILD_TYPE
private val certAlias: String
get() = "com.android.developers.androidify.Cert-" + BuildConfig.BUILD_TYPE
private const val ANDROID_KEYSTORE = "AndroidKeyStore"

/**
Expand Down Expand Up @@ -71,13 +74,13 @@ fun signApk(unsignedApk: ByteArray): ByteArray {
private fun getOrCreateSigningKeyPair(): KeyPair {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }

if (keyStore.containsAlias(KEY_ALIAS)) {
val entry = keyStore.getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry
if (keyStore.containsAlias(keyAlias)) {
val entry = keyStore.getEntry(keyAlias, null) as KeyStore.PrivateKeyEntry
return KeyPair(entry.certificate.publicKey, entry.privateKey)
}

val parameterSpec = KeyGenParameterSpec.Builder(
KEY_ALIAS,
keyAlias,
KeyProperties.PURPOSE_SIGN,
).run {
setDigests(KeyProperties.DIGEST_SHA256)
Expand Down Expand Up @@ -151,7 +154,7 @@ private fun storeCertificate(certificate: X509Certificate): Boolean {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply {
load(null)
}
keyStore.setCertificateEntry(CERT_ALIAS, certificate)
keyStore.setCertificateEntry(certAlias, certificate)
true
} catch (e: Exception) {
// Log the exception for debugging
Expand All @@ -169,7 +172,7 @@ private fun getOrCreateCertificate(keyPair: KeyPair): X509Certificate {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply {
load(null)
}
return (keyStore.getCertificate(CERT_ALIAS) ?: createCertificate(keyPair)) as X509Certificate
return (keyStore.getCertificate(certAlias) ?: createCertificate(keyPair)) as X509Certificate
}

/**
Expand Down