From a282b3e72e85e909b45148df13c3289c778bbeb2 Mon Sep 17 00:00:00 2001 From: Ajesh R Date: Thu, 31 Jul 2025 11:18:29 +0530 Subject: [PATCH] Adds R8 use cases to the repo --- app/benchmark-rules.pro | 1 - app/build.gradle.kts | 1 + app/lib/.gitignore | 1 + app/lib/build.gradle.kts | 13 +++++ .../src/main/java/com/example/lib/MyClass.kt | 4 ++ app/nativelib/.gitignore | 1 + app/nativelib/build.gradle.kts | 54 +++++++++++++++++++ app/nativelib/consumer-rules.pro | 0 app/nativelib/proguard-rules.pro | 21 ++++++++ .../nativelib/ExampleInstrumentedTest.kt | 24 +++++++++ app/nativelib/src/main/AndroidManifest.xml | 4 ++ app/nativelib/src/main/cpp/CMakeLists.txt | 38 +++++++++++++ app/nativelib/src/main/cpp/nativelib.cpp | 10 ++++ .../java/com/example/nativelib/NativeLib.kt | 18 +++++++ .../com/example/nativelib/ExampleUnitTest.kt | 17 ++++++ .../samples/apps/nowinandroid/MainActivity.kt | 8 +++ .../samples/apps/nowinandroid/SecretBox.kt | 42 +++++++++++++++ build.gradle.kts | 3 +- core/datastore/consumer-proguard-rules.pro | 10 +++- gradle/libs.versions.toml | 9 +++- settings.gradle.kts | 1 + stacktrace | 36 +++++++++++++ 22 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 app/lib/.gitignore create mode 100644 app/lib/build.gradle.kts create mode 100644 app/lib/src/main/java/com/example/lib/MyClass.kt create mode 100644 app/nativelib/.gitignore create mode 100644 app/nativelib/build.gradle.kts create mode 100644 app/nativelib/consumer-rules.pro create mode 100644 app/nativelib/proguard-rules.pro create mode 100644 app/nativelib/src/androidTest/java/com/example/nativelib/ExampleInstrumentedTest.kt create mode 100644 app/nativelib/src/main/AndroidManifest.xml create mode 100644 app/nativelib/src/main/cpp/CMakeLists.txt create mode 100644 app/nativelib/src/main/cpp/nativelib.cpp create mode 100644 app/nativelib/src/main/java/com/example/nativelib/NativeLib.kt create mode 100644 app/nativelib/src/test/java/com/example/nativelib/ExampleUnitTest.kt create mode 100644 app/src/main/kotlin/com/google/samples/apps/nowinandroid/SecretBox.kt create mode 100644 stacktrace diff --git a/app/benchmark-rules.pro b/app/benchmark-rules.pro index 96b67f2d1..48d2ca28d 100644 --- a/app/benchmark-rules.pro +++ b/app/benchmark-rules.pro @@ -3,7 +3,6 @@ # Obsfuscation must be disabled for the build variant that generates Baseline Profile, otherwise # wrong symbols would be generated. The generated Baseline Profile will be properly applied when generated # without obfuscation and your app is being obfuscated. --dontobfuscate # Please add these rules to your existing keep rules in order to suppress warnings. # This is generated automatically by the Android Gradle plugin. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6aec2d1bc..095efdb38 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -70,6 +70,7 @@ android { } dependencies { + implementation(projects.app.nativelib) implementation(projects.feature.interests) implementation(projects.feature.foryou) implementation(projects.feature.bookmarks) diff --git a/app/lib/.gitignore b/app/lib/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/lib/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/lib/build.gradle.kts b/app/lib/build.gradle.kts new file mode 100644 index 000000000..a95692d3c --- /dev/null +++ b/app/lib/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("java-library") + alias(libs.plugins.kotlin.jvm) +} +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 + } +} diff --git a/app/lib/src/main/java/com/example/lib/MyClass.kt b/app/lib/src/main/java/com/example/lib/MyClass.kt new file mode 100644 index 000000000..99ce9e67c --- /dev/null +++ b/app/lib/src/main/java/com/example/lib/MyClass.kt @@ -0,0 +1,4 @@ +package com.example.lib + +class MyClass { +} \ No newline at end of file diff --git a/app/nativelib/.gitignore b/app/nativelib/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/nativelib/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/nativelib/build.gradle.kts b/app/nativelib/build.gradle.kts new file mode 100644 index 000000000..6778ee488 --- /dev/null +++ b/app/nativelib/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.example.nativelib" + compileSdk = 36 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + externalNativeBuild { + cmake { + cppFlags("") + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.test.espresso.core) +} \ No newline at end of file diff --git a/app/nativelib/consumer-rules.pro b/app/nativelib/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/app/nativelib/proguard-rules.pro b/app/nativelib/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/app/nativelib/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/nativelib/src/androidTest/java/com/example/nativelib/ExampleInstrumentedTest.kt b/app/nativelib/src/androidTest/java/com/example/nativelib/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..dd8f87ac7 --- /dev/null +++ b/app/nativelib/src/androidTest/java/com/example/nativelib/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.nativelib + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.nativelib.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/nativelib/src/main/AndroidManifest.xml b/app/nativelib/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/app/nativelib/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/nativelib/src/main/cpp/CMakeLists.txt b/app/nativelib/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..73f56e9dc --- /dev/null +++ b/app/nativelib/src/main/cpp/CMakeLists.txt @@ -0,0 +1,38 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("nativelib") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + nativelib.cpp +) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) \ No newline at end of file diff --git a/app/nativelib/src/main/cpp/nativelib.cpp b/app/nativelib/src/main/cpp/nativelib.cpp new file mode 100644 index 000000000..5522a3b41 --- /dev/null +++ b/app/nativelib/src/main/cpp/nativelib.cpp @@ -0,0 +1,10 @@ +#include +#include + +extern "C" JNIEXPORT jstring JNICALL +Java_com_example_nativelib_NativeLib_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + std::string hello = "Hello from C++"; + return env->NewStringUTF(hello.c_str()); +} \ No newline at end of file diff --git a/app/nativelib/src/main/java/com/example/nativelib/NativeLib.kt b/app/nativelib/src/main/java/com/example/nativelib/NativeLib.kt new file mode 100644 index 000000000..71e6384f8 --- /dev/null +++ b/app/nativelib/src/main/java/com/example/nativelib/NativeLib.kt @@ -0,0 +1,18 @@ +package com.example.nativelib + +class NativeLib { + + /** + * A native method that is implemented by the 'nativelib' native library, + * which is packaged with this application. + */ + external fun stringFromJNI(): String + + companion object { + // Used to load the 'nativelib' library on application startup. + init { + System.loadLibrary("nativelib") + } + } + +} \ No newline at end of file diff --git a/app/nativelib/src/test/java/com/example/nativelib/ExampleUnitTest.kt b/app/nativelib/src/test/java/com/example/nativelib/ExampleUnitTest.kt new file mode 100644 index 000000000..d6145e7f9 --- /dev/null +++ b/app/nativelib/src/test/java/com/example/nativelib/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.nativelib + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index ecc23d80e..b2ada79a8 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -16,12 +16,15 @@ package com.google.samples.apps.nowinandroid +import android.os.Build.VERSION_CODES import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -50,7 +53,10 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import com.example.nativelib.NativeLib + import javax.inject.Inject +import kotlin.reflect.KVisibility @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -79,6 +85,8 @@ class MainActivity : ComponentActivity() { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) + accessSecretMessage(LibraryClass()) + // We keep this as a mutable state, so that we can track changes inside the composition. // This allows us to react to dark/light mode changes. var themeSettings by mutableStateOf( diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/SecretBox.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/SecretBox.kt new file mode 100644 index 000000000..8fc7b792e --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/SecretBox.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid + +import android.util.Log + +val TAG: String = "R8" + +/** + * A class with private members to demonstrate reflection. + */ +class LibraryClass { + private val secretMessage: Message = Message("R8 will remove me") +} + +data class Message( + val message: String, + val id: Int = 0 +) + +// In your app code: +fun accessSecretMessage(instance: LibraryClass) { + // Use Java reflection from Kotlin to access the private field + val secretField = instance::class.java.getDeclaredField("secretMessage") + secretField.isAccessible = true + val message = secretField.get(instance) as Message + Log.d(TAG, message.toString()) +} diff --git a/build.gradle.kts b/build.gradle.kts index b7989bab4..7b1789be5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,5 +60,6 @@ plugins { alias(libs.plugins.roborazzi) apply false alias(libs.plugins.secrets) apply false alias(libs.plugins.room) apply false - alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation + alias(libs.plugins.module.graph) apply true + alias(libs.plugins.kotlin.android) apply false // Plugin applied to allow module graph generation } diff --git a/core/datastore/consumer-proguard-rules.pro b/core/datastore/consumer-proguard-rules.pro index 173273916..30c4fdc2f 100644 --- a/core/datastore/consumer-proguard-rules.pro +++ b/core/datastore/consumer-proguard-rules.pro @@ -1,4 +1,12 @@ # Keep DataStore fields -keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* { ; -} \ No newline at end of file +} + +-keepclassmembers,includedescriptorclasses class com.google.samples.apps.nowinandroid.LibraryClass { + private * secretMessage; +} + +-printconfiguration r8/full-r8-config.txt # Prints the entire configuration for the app +-printusage r8/usage.txt # Prints where R8 removed code from the app +-printseeds r8/seeds.txt \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 477dde916..6a621ce71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.8.7" androidxLintGradle = "1.0.0-alpha03" -androidxMacroBenchmark = "1.3.4" +androidxMacroBenchmark = "1.4.0-beta02" androidxMetrics = "1.0.0-beta01" androidxNavigation = "2.8.5" androidxProfileinstaller = "1.4.1" @@ -59,6 +59,9 @@ room = "2.6.1" secrets = "2.0.1" truth = "1.4.4" turbine = "1.2.0" +junit = "4.13.2" +junitVersion = "1.2.1" +material = "1.12.0" [bundles] androidx-compose-ui-test = ["androidx-compose-ui-test", "androidx-compose-ui-testManifest"] @@ -161,6 +164,9 @@ firebase-performance-gradlePlugin = { group = "com.google.firebase", name = "per kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", version.ref = "room" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } @@ -198,3 +204,4 @@ nowinandroid-android-room = { id = "nowinandroid.android.room" } nowinandroid-android-test = { id = "nowinandroid.android.test" } nowinandroid-hilt = { id = "nowinandroid.hilt" } nowinandroid-jvm-library = { id = "nowinandroid.jvm.library" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b8c6e45c..e8c31b2d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -83,3 +83,4 @@ check(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { https://developer.android.com/build/jdks#jdk-config-in-studio """.trimIndent() } +include(":app:nativelib") diff --git a/stacktrace b/stacktrace new file mode 100644 index 000000000..889269b57 --- /dev/null +++ b/stacktrace @@ -0,0 +1,36 @@ +java.lang.RuntimeException: Unable to start activity ComponentInfo{com.google.samples.apps.nowinandroid/com.google.samples.apps.nowinandroid.MainActivity}: java.util.NoSuchElementException: List is empty. + at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4048) + at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4235) + at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:112) + at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:174) + at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:109) + at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:81) + at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2636) + at android.os.Handler.dispatchMessage(Handler.java:107) + at android.os.Looper.loopOnce(Looper.java:232) + at android.os.Looper.loop(Looper.java:317) + at android.app.ActivityThread.main(ActivityThread.java:8705) + at java.lang.reflect.Method.invoke(Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886) +Caused by: java.util.NoSuchElementException: List is empty. + at i4.k.m0(SourceFile:22) + at i4.k.l0(SourceFile:12) + at com.google.samples.apps.nowinandroid.MainActivity.onCreate(SourceFile:54) + at android.app.Activity.performCreate(Activity.java:9002) + at android.app.Activity.performCreate(Activity.java:8980) + at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1526) + at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4030) + at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4235)  + at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:112)  + at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:174)  + at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:109)  + at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:81)  + at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2636)  + at android.os.Handler.dispatchMessage(Handler.java:107)  + at android.os.Looper.loopOnce(Looper.java:232)  + at android.os.Looper.loop(Looper.java:317)  + at android.app.ActivityThread.main(ActivityThread.java:8705)  + at java.lang.reflect.Method.invoke(Native Method)  + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)  + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)  \ No newline at end of file