Skip to content

feat: extract on-device review app and tools to :ApiDemos:reviewer module - #2428

Open
dkhawk wants to merge 3 commits into
feat/snippets-kotlin-appfrom
feat/on-device-reviewer-module
Open

dkhawk wants to merge 3 commits into
feat/snippets-kotlin-appfrom
feat/on-device-reviewer-module

Conversation

@dkhawk

@dkhawk dkhawk commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extract on-device review tools into dedicated :ApiDemos:reviewer module
  • Add Room database persistence (SampleCatalogDatabase, SampleEvaluationDao, SampleEvaluationEntity)
  • Implement ReviewerActivity, ReviewEvaluationDialog, AnnotationCanvasView, and GrievanceReportExporter
  • Wire reviewer toolbar actions into SamplesBaseActivity (Java & Kotlin)
  • Add BasicMapReviewerUiTest for reviewer toolbar validation
  • Add verification tooling guides, manual verification dashboard, and generated snippet catalogs

Stacked Base

Stacked on #2427 (feat/snippets-kotlin-app).

Reviewers

@LoyalAbbas

@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from 6179dc5 to 2f8a8c6 Compare September 15, 2026 00:26
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from 2f8a8c6 to f7c29f5 Compare September 15, 2026 00:35
@dkhawk
dkhawk marked this pull request as ready for review September 15, 2026 00:38
@dkhawk
dkhawk requested a review from LoyalAbbas September 15, 2026 00:38
@snippet-bot

snippet-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

No region tags are edited in this PR.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@dkhawk
dkhawk added this pull request to stack #2429 September 15, 2026 00:43
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from f7c29f5 to 7958e3a Compare September 15, 2026 19:08
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from 7958e3a to 92f23bf Compare September 15, 2026 22:58
Comment thread test/server.py Outdated
Comment on lines +351 to +352
server_address = ("0.0.0.0", PORT)
httpd = http.server.ThreadingHTTPServer(server_address, CatalogRequestHandler)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this bind 127.0.0.1 instead? On 0.0.0.0 with no auth, anyone on the same network can POST and get us to run ./gradlew installDebug (line 206), adb shell am start (line 220), ./gradlew --stop (line 177) and connected tests (line 279) on the dev machine. The shell quoting itself looks fine — it's the bind address I'd worry about.

Also, --java-home on line 345 defaults to /usr/lib/jvm/java-21-openjdk-amd64, which won't exist for anyone on a Mac. Fall back to JAVA_HOME when it's not passed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 5c3237d0. Updated test/server.py to bind to 127.0.0.1 by default via --host 127.0.0.1.

androidTestImplementation(libs.uiautomator)

implementation(project(":ApiDemos:common-ui"))
implementation(project(":ApiDemos:reviewer"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be debugImplementation? As implementation (same in java-app line 73) the reviewer module ships in release builds — so the Room DB, the FileProvider, the exported activity and the exported receiver all land in the APK a developer downloads to learn the SDK.

And since SamplesBaseActivity now hard-imports SampleReviewRepository, splitting it out later means touching the base class again — probably easier to decide now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because SamplesBaseActivity (which hosts the in-sample reviewer action bar and expectations bottom sheet) lives in src/main/ across :ApiDemos:kotlin-app and :ApiDemos:java-app, using debugImplementation breaks assembleRelease compilation for both sample apps. If we want :ApiDemos:reviewer excluded from release APKs in the future, we can introduce a no-op variant of SamplesBaseActivity.

Comment on lines +20 to +22
<external-files-path name="external_files" path="." />
<files-path name="internal_files" path="." />
<cache-path name="internal_cache" path="." />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we drop these three? GrievanceReportExporter only ever shares out of reports, which line 19 already covers — these path="." roots let the provider grant a URI to anything under filesDir, cacheDir and the external files dir.

Probably worth being strict about it here given how much of this repo gets copy-pasted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 5c3237d0. Dropped external_files, internal_files, and internal_cache broad paths, keeping only <external-files-path name="reports" path="reports" />.

Comment on lines +69 to 74
private fun applyImmersiveStickyMode() {
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, window.decorView)
insetsController.systemBarsBehavior =
androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
insetsController.hide(WindowInsetsCompat.Type.systemBars())
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be gated on isReviewerMode? Called from onCreate, onResume and onWindowFocusChanged, it hides the system bars for all 30+ samples, not just reviewer sessions.

It's also why setupEdgeToEdgeInsets below had to drop statusBars()/navigationBars() and go cutout-only — which undoes the edge-to-edge work we just did. Big UX change to land in a PR about extracting the reviewer module, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 5c3237d0. Gated applyImmersiveStickyMode() on if (!isReviewerMode) return in both Kotlin and Java SamplesBaseActivity.

Comment on lines +85 to +92
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
implementation(libs.ui)
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
implementation(libs.material.icons.extended)
debugImplementation(libs.ui.tooling)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this go in its own commit? Adding the whole Compose stack to kotlin-app — plus dropping kotlin.stdlib, cardview, recyclerview, multidex, volley and the -Xopt-in compiler arg — is a fair-sized change to land under "extract review tools to :ApiDemos:reviewer".

It does all still build, so the removals look safe, I'd just rather they were visible on their own rather than buried in a 12k-line diff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. The Compose dependencies and test libraries were introduced to support the in-app reviewer UI and its visual/unit test suite. Keeping this unified with the reviewer module PR avoids an intermediate broken dependency state.

Comment on lines +40 to +61
val scenario = ActivityScenario.launch(BasicMapDemoActivity::class.java)

scenario.onActivity { activity ->
val toolbar = activity.findViewById<MaterialToolbar>(R.id.top_bar)
assertNotNull("Top bar must be present in layout", toolbar)

// Verify action bar is attached to the toolbar
val actionBar = activity.supportActionBar
assertNotNull("SupportActionBar must be attached to MaterialToolbar", actionBar)

// Verify menu items are present in toolbar
val menu = toolbar.menu
assertNotNull("Toolbar menu must not be null", menu)

val infoItem = menu.findItem(2001)
assertNotNull("Criteria & Purpose (id 2001) action button must exist in toolbar menu", infoItem)

val goodJobItem = menu.findItem(2003)
assertNotNull("Good Job (id 2003) action button must exist in toolbar menu", goodJobItem)

val wrongItem = menu.findItem(2004)
assertNotNull("Something's Wrong (id 2004) action button must exist in toolbar menu", wrongItem)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the launch intent carry extra_is_reviewer_mode? Without it isReviewerMode is false, so onCreateOptionsMenu only adds 2001 ("About & APIs") — 2003 and 2004 are on the reviewer branch, which this never takes.

I couldn't run it (no device here), so I may be missing something. Also assertTrue is imported but unused, and there's no Java counterpart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 5c3237d0. Updated BasicMapReviewerUiTest to launch with an explicit Intent containing putExtra("extra_is_reviewer_mode", true) and putExtra("extra_sample_id", ...).

* Dedicated Jetpack Compose Reviewer Mode application for engineers to validate samples,
* record notes & grievances in Room DB, and export the "Airing of Grievances" report.
*/
open class ReviewerActivity : ComponentActivity() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we open this activity from CatalogActivity.kt, still we are calling with old name

onSwitchMode = {
val intent = Intent().setClassName(packageName, "com.example.common_ui.catalog.compose.ReviewerActivity")
startActivity(intent)
}

We need to change this with new ReviewerActivity name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 2ac4cd8f. Updated the intent in CatalogActivity.kt to target com.example.reviewer.compose.ReviewerActivity with a resolveActivity check.

val javaActivity = metadata?.javaActivity
if (javaActivity != null) {
finish()
val intent = Intent().setClassName(packageName, javaActivity).apply {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityNotFoundException Crash When Switching Between Kotlin & Java Samples
Please verify to other similar kind of Intent calling.

Bug: :ApiDemos:kotlin-app (com.example.kotlindemos) and :ApiDemos:java-app (com.example.mapdemo) are two separate APKs. In SamplesBaseActivity.kt, packageName is "com.example.kotlindemos", while metadata.javaActivity is "com.example.mapdemo.". Calling Intent().setClassName(packageName, javaActivity) looks for com.example.mapdemo.* inside the com.example.kotlindemos APK, and SamplesBaseActivity does not wrap startActivity(intent) in a try/catch.

Impact: Selecting "Switch to Java" in kotlin-app or "Switch to Kotlin" in java-app crashes the app with ActivityNotFoundException.

Fix: Resolve the target application package from the target class name:
kotlin
val targetPackage = when {
targetClass.startsWith("com.example.mapdemo.") -> "com.example.mapdemo"
targetClass.startsWith("com.example.kotlindemos.") -> "com.example.kotlindemos"
else -> packageName
}

And guard startActivity(intent) with try/catch before calling finish().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 2ac4cd8f. Wrapped cross-framework intent launches in SampleReviewRepository.launchSample, dynamically resolving targetPackage (com.example.mapdemo vs com.example.kotlindemos), verifying resolveActivity, and catching ActivityNotFoundException with a user-facing Toast instead of crashing.

if (currentSampleMetadata != null && currentSampleMetadata.getKotlinActivity() != null) {
finish();
Intent intent = new Intent();
intent.setClassName(getPackageName(), currentSampleMetadata.getKotlinActivity());

@LoyalAbbas LoyalAbbas Sep 16, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityNotFoundException Crash When Switching Between Kotlin & Java Samples

Please verify to other similar kind of Intent calling.

Bug: :ApiDemos:kotlin-app (com.example.kotlindemos) and :ApiDemos:java-app (com.example.mapdemo) are two separate APKs. In SamplesBaseActivity.kt, packageName is "com.example.kotlindemos", while metadata.javaActivity is "com.example.mapdemo.". Calling Intent().setClassName(packageName, javaActivity) looks for com.example.mapdemo.* inside the com.example.kotlindemos APK, and SamplesBaseActivity does not wrap startActivity(intent) in a try/catch.

Impact: Selecting "Switch to Java" in kotlin-app or "Switch to Kotlin" in java-app crashes the app with ActivityNotFoundException.

Fix: Resolve the target application package from the target class name:
kotlin
val targetPackage = when {
targetClass.startsWith("com.example.mapdemo.") -> "com.example.mapdemo"
targetClass.startsWith("com.example.kotlindemos.") -> "com.example.kotlindemos"
else -> packageName
}

And guard startActivity(intent) with try/catch before calling finish().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 2ac4cd8f. Routed cross-framework launches through SampleReviewRepository.launchSample with package resolution and ActivityNotFoundException protection.

)
}
}
ContextCompat.registerReceiver(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvaluationExportReceiver already handles com.google.maps.EXPORT_EVALUATIONS, do we really need this here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit 2ac4cd8f. Removed the redundant inline BroadcastReceiver registration from ReviewerActivity.onCreate(); EvaluationExportReceiver handles the manifest action.

@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from 92f23bf to f0066ed Compare September 17, 2026 22:33
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from f0066ed to 5c3237d Compare September 30, 2026 22:17
@dkhawk
dkhawk requested review from LoyalAbbas and kikoso September 30, 2026 23:14
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from 5c3237d to eb4f6db Compare September 30, 2026 23:20
…dule

- Extract on-device review tools into dedicated :ApiDemos:reviewer module
- Add Room database persistence (SampleCatalogDatabase, SampleEvaluationDao, SampleEvaluationEntity)
- Implement ReviewerActivity, ReviewEvaluationDialog, AnnotationCanvasView, and GrievanceReportExporter
- Wire reviewer toolbar actions into SamplesBaseActivity (Java & Kotlin)
- Add BasicMapReviewerUiTest for reviewer toolbar validation
- Add verification tooling guides, manual verification dashboard, and generated snippet catalogs
…hronization and dialog polish

- Add opt-in 'Mark as Seen' mode to Catalog viewer, defaulted to disabled for general users
- Add overflow menu actions to toggle seen-tracking mode and clear all seen statuses
- Automatically mark samples as seen upon clicking 'Launch Sample'
- Add 'Hide Seen' filter chip to hide evaluated samples
- Synchronize seen state across Kotlin and Java framework tabs
- Persist seen state and mode preferences in SharedPreferences
- Modernize back-navigation in sample activities with OnBackPressedCallback
- Clean up dialog lifecycle observers, imports, and type-safe navigation in reviewer module
@dkhawk
dkhawk force-pushed the feat/on-device-reviewer-module branch from eb4f6db to 3343a0d Compare September 30, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants