diff --git a/android_env/apps/java/com/google/androidenv/MODULE.bazel b/android_env/apps/java/com/google/androidenv/MODULE.bazel
new file mode 100644
index 00000000..b2ed3c31
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/MODULE.bazel
@@ -0,0 +1,43 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+module(
+ name = "catch",
+ version = "0.1.0",
+)
+
+bazel_dep(name = "rules_kotlin", version = "2.1.8")
+bazel_dep(name = "rules_jvm_external", version = "6.8")
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+maven.install(
+ artifacts = [
+ "joda-time:joda-time:2.12.7",
+ ],
+ repositories = [
+ "https://repo1.maven.org/maven2",
+ ],
+)
+use_repo(maven, "maven")
+
+bazel_dep(name = "rules_android", version = "0.6.6")
+
+remote_android_extensions = use_extension(
+ "@rules_android//bzlmod_extensions:android_extensions.bzl",
+ "remote_android_tools_extensions",
+)
+use_repo(remote_android_extensions, "android_tools")
+
+android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
+use_repo(android_sdk_repository_extension, "androidsdk")
diff --git a/android_env/apps/java/com/google/androidenv/MODULE.bazel.lock b/android_env/apps/java/com/google/androidenv/MODULE.bazel.lock
new file mode 100644
index 00000000..b3c81a88
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/MODULE.bazel.lock
@@ -0,0 +1,401 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+{
+ "lockFileVersion": 18,
+ "registryFileHashes": {
+ "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
+ "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
+ "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
+ "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
+ "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
+ "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
+ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
+ "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
+ "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16",
+ "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5",
+ "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/source.json": "1b996859f840d8efc7c720efc61dcf2a84b1261cb3974cbbe9b6666ebf567775",
+ "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a",
+ "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b",
+ "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896",
+ "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85",
+ "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b",
+ "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b",
+ "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
+ "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
+ "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df",
+ "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
+ "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
+ "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
+ "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
+ "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
+ "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a",
+ "https://bcr.bazel.build/modules/bazel_features/1.25.0/MODULE.bazel": "e2e60a10a6da64bbf533f15ca652bf61a033e41c2ed734d79a9a08ba87f68c1a",
+ "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
+ "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
+ "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
+ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
+ "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
+ "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
+ "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd",
+ "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e",
+ "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/source.json": "d353c410d47a8b65d09fa98e83d57ebec257a2c2b9c6e42d6fda1cb25e5464a5",
+ "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db",
+ "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/source.json": "a2d30458fd86cf022c2b6331e652526fa08e17573b2f5034a9dbcacdf9c2583c",
+ "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
+ "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
+ "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8",
+ "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350",
+ "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a",
+ "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0",
+ "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146",
+ "https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a",
+ "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
+ "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
+ "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
+ "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
+ "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108",
+ "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470",
+ "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
+ "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0",
+ "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000",
+ "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
+ "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
+ "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
+ "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
+ "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
+ "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
+ "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
+ "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
+ "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
+ "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
+ "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
+ "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
+ "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
+ "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a",
+ "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12",
+ "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
+ "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
+ "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295",
+ "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
+ "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
+ "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e",
+ "https://bcr.bazel.build/modules/protobuf/29.3/MODULE.bazel": "77480eea5fb5541903e49683f24dc3e09f4a79e0eea247414887bb9fc0066e94",
+ "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
+ "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573",
+ "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
+ "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e",
+ "https://bcr.bazel.build/modules/protobuf/31.1/source.json": "25af5d0219da0c0fc4d1191a24ce438e6ca7f49d2e1a94f354efeba6ef10426f",
+ "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
+ "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34",
+ "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680",
+ "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
+ "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a",
+ "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4",
+ "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa",
+ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
+ "https://bcr.bazel.build/modules/rules_android/0.6.4/MODULE.bazel": "b4cde12d506dd65d82b2be39761f49f5797303343a3d5b4ee191c0cdf9ef387c",
+ "https://bcr.bazel.build/modules/rules_android/0.6.6/MODULE.bazel": "b0fb569752aab65ab1a9db0a8f6cfaf5aa1754965e17e95dcf0e4d88e192a68d",
+ "https://bcr.bazel.build/modules/rules_android/0.6.6/source.json": "a9d8dc2d5a102dc03269a94acc886a4cab82cdcb9ccbc77b0f665d6d17a6ae09",
+ "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a",
+ "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
+ "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
+ "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
+ "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
+ "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
+ "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
+ "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8",
+ "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270",
+ "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd",
+ "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0",
+ "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/MODULE.bazel": "edfc3a9cea7bedb0eaaff37b0d7817c1a4bf72b3c615580b0ffcee6c52690fd4",
+ "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/source.json": "6b5cd0b3da2bd0e6949580851db990a04af0a285f072b9a0f059424457cd8cc9",
+ "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
+ "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
+ "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39",
+ "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963",
+ "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6",
+ "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
+ "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64",
+ "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
+ "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
+ "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
+ "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
+ "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934",
+ "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
+ "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
+ "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e",
+ "https://bcr.bazel.build/modules/rules_java/8.13.0/source.json": "4605c0f676b87dd9d1fabd4d743b71f04d97503bd1a79aad53f87399fb5396de",
+ "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
+ "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
+ "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71",
+ "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2",
+ "https://bcr.bazel.build/modules/rules_java/8.6.3/MODULE.bazel": "e90505b7a931d194245ffcfb6ff4ca8ef9d46b4e830d12e64817752e0198e2ed",
+ "https://bcr.bazel.build/modules/rules_java/8.9.0/MODULE.bazel": "e17c876cb53dcd817b7b7f0d2985b710610169729e8c371b2221cacdcd3dce4a",
+ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
+ "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
+ "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
+ "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.8/MODULE.bazel": "b5afe861e867e4c8e5b88e401cb7955bd35924258f97b1862cc966cbcf4f1a62",
+ "https://bcr.bazel.build/modules/rules_jvm_external/6.8/source.json": "c85e553d5ac17f7825cd85b9cceb500c64f9e44f0e93c7887469e430c4ae9eff",
+ "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
+ "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025",
+ "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
+ "https://bcr.bazel.build/modules/rules_kotlin/2.1.8/MODULE.bazel": "e832456b08f4bdf23a466ae4ec424ce700d4ccaa2c9e83912d32791ade43b77d",
+ "https://bcr.bazel.build/modules/rules_kotlin/2.1.8/source.json": "4ccee7dee20c21546ae4ea3d26d4f9c859fc906fa4c8e317d571b62d8048ae10",
+ "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
+ "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
+ "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
+ "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
+ "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
+ "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
+ "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
+ "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
+ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
+ "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
+ "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f",
+ "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
+ "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2",
+ "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1",
+ "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
+ "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
+ "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
+ "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
+ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
+ "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937",
+ "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500",
+ "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a",
+ "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
+ "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
+ "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43",
+ "https://bcr.bazel.build/modules/rules_python/1.0.0/source.json": "b0162a65c6312e45e7912e39abd1a7f8856c2c7e41ecc9b6dc688a6f6400a917",
+ "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251",
+ "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea",
+ "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc",
+ "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
+ "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
+ "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
+ "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca",
+ "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046",
+ "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186",
+ "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
+ "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
+ "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef",
+ "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
+ "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
+ "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
+ "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
+ "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
+ "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
+ "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
+ "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
+ "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9",
+ "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
+ "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
+ "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
+ "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
+ "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
+ },
+ "selectedYankedVersions": {},
+ "moduleExtensions": {
+ "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
+ "general": {
+ "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=",
+ "usagesDigest": "EJfdUgbJdIRboG70S9dz8HhGVkL2/s3qFQ9xht8y2Rs=",
+ "recordedFileInputs": {},
+ "recordedDirentsInputs": {},
+ "envVariables": {},
+ "generatedRepoSpecs": {
+ "local_config_apple_cc_toolchains": {
+ "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains",
+ "attributes": {}
+ },
+ "local_config_apple_cc": {
+ "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf",
+ "attributes": {}
+ }
+ },
+ "recordedRepoMappingEntries": [
+ [
+ "apple_support+",
+ "bazel_tools",
+ "bazel_tools"
+ ],
+ [
+ "bazel_tools",
+ "rules_cc",
+ "rules_cc+"
+ ]
+ ]
+ }
+ },
+ "@@rules_android+//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": {
+ "general": {
+ "bzlTransitiveDigest": "aDSxoLkIA0GyiHP6VPgxLuAWEmulTMkYgxvB/P0Q42g=",
+ "usagesDigest": "toF8IFMu98H/VU2p1sfVC5fVXVYJunpbbmtM6tOsQXY=",
+ "recordedFileInputs": {},
+ "recordedDirentsInputs": {},
+ "envVariables": {},
+ "generatedRepoSpecs": {
+ "com_android_dex": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
+ "attributes": {
+ "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz",
+ "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD"
+ }
+ }
+ },
+ "recordedRepoMappingEntries": [
+ [
+ "rules_android+",
+ "bazel_tools",
+ "bazel_tools"
+ ]
+ ]
+ }
+ },
+ "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": {
+ "general": {
+ "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=",
+ "usagesDigest": "et4mxrOHd+gUFXmyNrWCFDGIV7L95LyTpnGVEy2wvDE=",
+ "recordedFileInputs": {},
+ "recordedDirentsInputs": {},
+ "envVariables": {},
+ "generatedRepoSpecs": {
+ "androidsdk": {
+ "repoRuleId": "@@rules_android+//rules/android_sdk_repository:rule.bzl%_android_sdk_repository",
+ "attributes": {}
+ }
+ },
+ "recordedRepoMappingEntries": []
+ }
+ },
+ "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
+ "general": {
+ "bzlTransitiveDigest": "CgSFQ7VRhs6G8nojJKNB6szAhYnHEblrCU/AruTOxtw=",
+ "usagesDigest": "QPppUlwb7NSBhcaYae+JZPqTEmJKCkOXKFPXQS7aAJE=",
+ "recordedFileInputs": {},
+ "recordedDirentsInputs": {},
+ "envVariables": {},
+ "generatedRepoSpecs": {
+ "com_github_jetbrains_kotlin_git": {
+ "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository",
+ "attributes": {
+ "urls": [
+ "https://github.com/JetBrains/kotlin/releases/download/v2.1.21/kotlin-compiler-2.1.21.zip"
+ ],
+ "sha256": "1ba08a8b45da99339a0601134cc037b54cf85e9bc0edbe76dcbd27c2d684a977"
+ }
+ },
+ "com_github_jetbrains_kotlin": {
+ "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository",
+ "attributes": {
+ "git_repository_name": "com_github_jetbrains_kotlin_git",
+ "compiler_version": "2.1.21"
+ }
+ },
+ "com_github_google_ksp": {
+ "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository",
+ "attributes": {
+ "urls": [
+ "https://github.com/google/ksp/releases/download/2.1.21-2.0.1/artifacts.zip"
+ ],
+ "sha256": "44e965bb067b2bb5cd9184dab2c3dea6e3eab747d341c07645bb4c88f09e49c8",
+ "strip_version": "2.1.21-2.0.1"
+ }
+ },
+ "com_github_pinterest_ktlint": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file",
+ "attributes": {
+ "sha256": "5ba1ac917a06b0f02daaa60d10abbedd2220d60216af670c67a45b91c74cf8bb",
+ "urls": [
+ "https://github.com/pinterest/ktlint/releases/download/1.6.0/ktlint"
+ ],
+ "executable": true
+ }
+ },
+ "kotlinx_serialization_core_jvm": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
+ "attributes": {
+ "sha256": "3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3",
+ "urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar"
+ ]
+ }
+ },
+ "kotlinx_serialization_json": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
+ "attributes": {
+ "sha256": "58adf3358a0f99dd8d66a550fbe19064d395e0d5f7f1e46515cd3470a56fbbb0",
+ "urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json/1.8.1/kotlinx-serialization-json-1.8.1.jar"
+ ]
+ }
+ },
+ "kotlinx_serialization_json_jvm": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
+ "attributes": {
+ "sha256": "8769e5647557e3700919c32d508f5c5dad53c5d8234cd10846354fbcff14aa24",
+ "urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1.jar"
+ ]
+ }
+ },
+ "kotlin_build_tools_impl": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
+ "attributes": {
+ "sha256": "6e94896e321603e3bfe89fef02478e44d1d64a3d25d49d0694892ffc01c60acf",
+ "urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-build-tools-impl/2.1.20/kotlin-build-tools-impl-2.1.20.jar"
+ ]
+ }
+ }
+ },
+ "recordedRepoMappingEntries": [
+ [
+ "rules_kotlin+",
+ "bazel_tools",
+ "bazel_tools"
+ ]
+ ]
+ }
+ }
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/AndroidManifest.xml b/android_env/apps/java/com/google/androidenv/catch/AndroidManifest.xml
new file mode 100644
index 00000000..4ded8b50
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/AndroidManifest.xml
@@ -0,0 +1,38 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android_env/apps/java/com/google/androidenv/catch/BUILD.bazel b/android_env/apps/java/com/google/androidenv/catch/BUILD.bazel
new file mode 100644
index 00000000..3c071963
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/BUILD.bazel
@@ -0,0 +1,78 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+# Classic RL task implemented as an Android app.
+
+load("@rules_android//rules:rules.bzl", "android_binary")
+load("@rules_kotlin//kotlin:android.bzl", "kt_android_library")
+
+package(
+ default_applicable_licenses = ["//third_party/py/android_env:license"],
+ default_visibility = ["//catch:__subpackages__"],
+)
+
+licenses(["notice"])
+
+# Build targets will slowly be added to this project and ultimately we'll have
+# an android_binary() target that'll contain the app.
+kt_android_library(
+ name = "GameLogic",
+ srcs = ["GameLogic.kt"],
+ deps = [
+ "//catch/sprite:Background",
+ "//catch/sprite:Ball",
+ "//catch/sprite:LineSegment",
+ "//catch/sprite:Paddle",
+ "@maven//:joda_time_joda_time",
+ ],
+)
+
+kt_android_library(
+ name = "RenderThread",
+ srcs = ["RenderThread.kt"],
+ deps = [
+ ":GameLogic",
+ "@maven//:joda_time_joda_time",
+ ],
+)
+
+kt_android_library(
+ name = "MainActivity",
+ srcs = ["MainActivity.kt"],
+ manifest = "AndroidManifest.xml",
+ resource_files = glob(["res/**"]),
+ deps = [
+ ":GameLogic",
+ ":GameLogicThread",
+ ":RenderThread",
+ "//catch/sprite:Background",
+ "//catch/sprite:Ball",
+ "//catch/sprite:Paddle",
+ ],
+)
+
+android_binary(
+ name = "app",
+ manifest = "AndroidManifest.xml",
+ multidex = "native",
+ deps = [":MainActivity"],
+)
+
+kt_android_library(
+ name = "GameLogicThread",
+ srcs = ["GameLogicThread.kt"],
+ deps = [
+ ":GameLogic",
+ ],
+)
diff --git a/android_env/apps/java/com/google/androidenv/catch/GameLogic.kt b/android_env/apps/java/com/google/androidenv/catch/GameLogic.kt
new file mode 100644
index 00000000..7b299f1d
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/GameLogic.kt
@@ -0,0 +1,76 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch
+
+import android.graphics.Canvas
+import android.view.MotionEvent
+import com.google.androidenv.catch.sprite.Background
+import com.google.androidenv.catch.sprite.Ball
+import com.google.androidenv.catch.sprite.LineSegment
+import com.google.androidenv.catch.sprite.Paddle
+import kotlin.random.Random
+import org.joda.time.Duration
+import org.joda.time.Instant
+
+/** The class that contains the game logic. */
+open class GameLogic(
+ // Expected number of frames per second.
+ fps: Int = 60,
+ // Pseudo random number generator.
+ private val rand: Random = Random.Default,
+ // Width and height of the game in pixels.
+ private val width: Int,
+ private val height: Int,
+ // UI objects in the game.
+ private var background: Background = Background(),
+ private var ball: Ball = Ball(maxX = width, maxY = height, rand = rand),
+ private var paddle: Paddle = Paddle(maxX = width, y = height),
+) {
+
+ private val sleepTime: Duration = Duration.millis((1000.0 / fps).toLong())
+
+ /** Reinitializes the state of the game. */
+ // Need to make this open to allow for testing.
+ open fun reset() {
+ this.ball.reset()
+ }
+
+ /** Runs one "throw" of a [ball] that needs to be caught by the [paddle]. */
+ // Need to make this open to allow for testing.
+ open fun run(): Boolean {
+ var lastTimestamp = Instant.now()
+ do {
+ Thread.sleep(sleepTime.millis)
+ val now = Instant.now()
+ val interval = Duration(lastTimestamp, now)
+ lastTimestamp = now
+ ball.update(interval)
+ } while (!ball.isOutOfBounds())
+
+ return ball.intersects(LineSegment(paddle.topLeft(), paddle.topRight()))
+ }
+
+ /** Processes a user event (e.g. a touchscreen event) and updates the [paddle] accordingly. */
+ fun handleTouch(event: MotionEvent) {
+ paddle.x = event.x.toInt()
+ }
+
+ /** Renders the game on [c]. */
+ open fun render(c: Canvas) {
+ background.draw(c)
+ ball.draw(c)
+ paddle.draw(c)
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/GameLogicThread.kt b/android_env/apps/java/com/google/androidenv/catch/GameLogicThread.kt
new file mode 100644
index 00000000..231d481b
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/GameLogicThread.kt
@@ -0,0 +1,45 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch
+
+import android.util.Log
+
+/** A thread that continuously runs the game logic, resetting after each internal [run()]. */
+class GameLogicThread(private val game: GameLogic, private val loggingTag: String) : Thread() {
+
+ /** Whether this thread should continuously run. */
+ private var shouldRun: Boolean = true
+ /** A counter of game runs. */
+ private var counter: Int = 0
+
+ /**
+ * Lets the current [run()] iteration complete then break exit this [Thread].
+ *
+ * Notice that [shouldRun] cannot have a private getter with a public setter (please see
+ * https://youtrack.jetbrains.com/issue/KT-3110 for details), hence this public function. Also
+ * notice that we cannot call this function [stop()] since it would shadow [Thread.stop()].
+ */
+ public fun finish() {
+ shouldRun = false
+ }
+
+ /** Continuously runs the [game] until [finish()] is called. */
+ public override fun run() {
+ while (shouldRun) {
+ game.reset()
+ Log.i(loggingTag, "${counter++} - ${game.run()}")
+ }
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/MainActivity.kt b/android_env/apps/java/com/google/androidenv/catch/MainActivity.kt
new file mode 100644
index 00000000..41eb3b25
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/MainActivity.kt
@@ -0,0 +1,152 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.view.View
+import android.view.Window
+import com.google.androidenv.catch.sprite.Background
+import com.google.androidenv.catch.sprite.Ball
+import com.google.androidenv.catch.sprite.Paddle
+
+/** The activity that allows users to play the RL game of Catch. */
+class MainActivity() : Activity(), SurfaceHolder.Callback {
+
+ private var surfaceView: SurfaceView? = null
+ private var renderThread: RenderThread? = null
+ private var gameLogicThread: GameLogicThread? = null
+
+ private val fps: Int = 60
+ private var gameCounter: Int = 0
+ private var width: Int = -1
+ private var height: Int = -1
+
+ private var extras: Bundle? = null
+
+ // [Activity] overrides.
+
+ /** Initializes the Android [View] and sets up callbacks. */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.i(TAG, "MainActivity::onCreate()")
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+ setContentView(R.layout.main)
+ val surface: SurfaceView? = findViewById(R.id.surfaceView)
+ if (surface == null) throw Exception("Could not create SurfaceView. Aborting...")
+
+ surface.visibility = View.VISIBLE
+ surface.holder.addCallback(this)
+ surfaceView = surface
+ extras = intent?.extras
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ Log.i(TAG, "MainActivity::onNewIntent()")
+ extras = intent?.extras
+ startGame()
+ }
+
+ // [SurfaceHolder.Callback] overrides.
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ Log.i(TAG, "MainActivity::surfaceCreated()")
+ renderThread = RenderThread(surfaceHolder = holder, fps = fps).also { it.start() }
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ Log.i(TAG, "MainActivity::surfaceChanged()")
+ this.width = width
+ this.height = height
+ startGame()
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ Log.i(TAG, "MainActivity::surfaceDestroyed()")
+ renderThread?.finish()
+ renderThread?.join()
+ gameLogicThread?.finish()
+ gameLogicThread?.join()
+ }
+
+ private fun startGame() {
+ Log.i(TAG, "MainActivity::startGame()")
+ if (width <= 0 || height <= 0) {
+ Log.e(TAG, "MainActivity::startGame() - Width or height not initialized yet.")
+ return
+ }
+ val backgroundColor = Color.parseColor(extras?.getString("backgroundColor") ?: "BLACK")
+ val ballColor = Color.parseColor(extras?.getString("ballColor") ?: "WHITE")
+ val ballRadius = extras?.getFloat("ballRadius", 10.0f) ?: 10.0f
+ val ballSpeed = extras?.getFloat("ballSpeed", 0.2f) ?: 0.2f
+ val paddleColor = Color.parseColor(extras?.getString("paddleColor") ?: "WHITE")
+ val paddleWidth = extras?.getInt("paddleWidth", 80) ?: 80
+ val paddleHeight = extras?.getInt("paddleHeight", 10) ?: 10
+ Log.i(TAG, "MainActivity::startGame() - extras bundle: $extras")
+ val game =
+ GameLogic(
+ width = width,
+ height = height,
+ fps = fps,
+ background = Background(color = backgroundColor),
+ ball =
+ Ball(
+ maxX = width,
+ maxY = height,
+ color = ballColor,
+ radius = ballRadius,
+ speed = ballSpeed,
+ ),
+ paddle =
+ Paddle(
+ color = paddleColor,
+ width = paddleWidth,
+ height = paddleHeight,
+ maxX = width,
+ y = (height - paddleHeight / 2),
+ ),
+ )
+
+ // Stop the previous game logic thread if it's running.
+ gameLogicThread?.finish()
+ gameLogicThread?.join()
+
+ // Create and start the new GameLogicThread, passing the game instance.
+ gameLogicThread = GameLogicThread(game, TAG).also { it.start() }
+
+ // Pass the same game instance to the render thread.
+ renderThread?.game = game
+
+ surfaceView?.setOnTouchListener(
+ // Suppress warning for ClickableViewAccessibility since click handling
+ // is not within an OnTouchListener.
+ @SuppressWarnings("ClickableViewAccessibility")
+ View.OnTouchListener { _, motionEvent ->
+ game.handleTouch(motionEvent)
+ true
+ }
+ )
+ }
+
+ companion object {
+ private const val TAG = "AndroidRLTask"
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/RenderThread.kt b/android_env/apps/java/com/google/androidenv/catch/RenderThread.kt
new file mode 100644
index 00000000..089b2e41
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/RenderThread.kt
@@ -0,0 +1,53 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch
+
+import android.graphics.Canvas
+import android.view.SurfaceHolder
+import org.joda.time.Duration
+
+/** A thread that continuously renders the game logic onto a surface. */
+class RenderThread(private val surfaceHolder: SurfaceHolder, private val fps: Int = 60) : Thread() {
+
+ /** Whether this thread should continuously run. */
+ private var shouldRun: Boolean = true
+ /** How long to sleep at each [run()] iteration. */
+ private val sleepTime: Duration = Duration.millis((1000.0 / fps).toLong())
+ /** The class responsible for issuing rendering commands to the canvas. */
+ var game: GameLogic? = null
+
+ /**
+ * Runs the current game logic [run()] to completion.
+ *
+ * Notice that [shouldRun] cannot have a private getter with a public setter (please see
+ * https://youtrack.jetbrains.com/issue/KT-3110 for details), hence this public function. Also
+ * notice that we cannot call this function [stop()] since it would shadow [Thread.stop()].
+ */
+ public fun finish() {
+ shouldRun = false
+ }
+
+ /** Continuously renders the [game] onto [surfaceHolder]. */
+ public override fun run() {
+ while (shouldRun) {
+ if (surfaceHolder.surface?.isValid() ?: false) {
+ val c: Canvas = surfaceHolder.lockCanvas()
+ game?.render(c)
+ surfaceHolder.unlockCanvasAndPost(c)
+ }
+ Thread.sleep(sleepTime.millis)
+ }
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/res/layout/main.xml b/android_env/apps/java/com/google/androidenv/catch/res/layout/main.xml
new file mode 100644
index 00000000..9eb935bd
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/res/layout/main.xml
@@ -0,0 +1,26 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+
+
+
+
diff --git a/android_env/apps/java/com/google/androidenv/catch/res/values/strings.xml b/android_env/apps/java/com/google/androidenv/catch/res/values/strings.xml
new file mode 100644
index 00000000..bf118719
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/res/values/strings.xml
@@ -0,0 +1,17 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+
+ Catch
+
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/BUILD.bazel b/android_env/apps/java/com/google/androidenv/catch/sprite/BUILD.bazel
new file mode 100644
index 00000000..3bed75c6
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/BUILD.bazel
@@ -0,0 +1,66 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.
+
+load("@rules_kotlin//kotlin:android.bzl", "kt_android_library")
+
+# Sprites for the app.
+
+package(
+ default_applicable_licenses = ["//third_party/py/android_env:license"],
+ default_visibility = ["//catch:__subpackages__"],
+)
+
+licenses(["notice"])
+
+kt_android_library(
+ name = "Background",
+ srcs = ["Background.kt"],
+ deps = [":Sprite"],
+)
+
+kt_android_library(
+ name = "Ball",
+ srcs = ["Ball.kt"],
+ deps = [
+ ":LineSegment",
+ ":Point",
+ ":Sprite",
+ "@maven//:joda_time_joda_time",
+ ],
+)
+
+kt_android_library(
+ name = "LineSegment",
+ srcs = ["LineSegment.kt"],
+ deps = [":Point"],
+)
+
+kt_android_library(
+ name = "Paddle",
+ srcs = ["Paddle.kt"],
+ deps = [
+ ":Point",
+ ":Sprite",
+ ],
+)
+
+kt_android_library(
+ name = "Point",
+ srcs = ["Point.kt"],
+)
+
+kt_android_library(
+ name = "Sprite",
+ srcs = ["Sprite.kt"],
+)
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/Background.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/Background.kt
new file mode 100644
index 00000000..a985b3e4
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/Background.kt
@@ -0,0 +1,26 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+import android.graphics.Canvas
+import android.graphics.Color
+
+/** Represents the static background behind all objects. */
+open class Background(private val color: Int = Color.BLACK) : Sprite() {
+ /** Paints the canvas with the color given in the constructor. */
+ override fun draw(c: Canvas) {
+ c.drawColor(color)
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/Ball.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/Ball.kt
new file mode 100644
index 00000000..6396c2dc
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/Ball.kt
@@ -0,0 +1,108 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import kotlin.math.ceil
+import kotlin.math.sqrt
+import kotlin.random.Random
+import org.joda.time.Duration
+
+/** Represents a ball that travels down in space with constant speed. */
+open class Ball(
+ private val maxX: Int,
+ private val maxY: Int,
+ private val color: Int = Color.WHITE,
+ private val radius: Float = 10.0f,
+ // `speed`'s unit is in pixels/ms.
+ private val speed: Float = 1.0f,
+ private val rand: Random = Random.Default,
+) : Sprite() {
+
+ // `x` and `y` represent the position of the center of this ball.
+ //
+ // Valid range [0, maxX]. 0==left, maxX==right.
+ private var x: Int = rand.nextInt(maxX)
+ // Valid range [0, maxY]. 0==top, maxY==bottom.
+ private var y: Int = ceil(radius).toInt()
+
+ private val paint: Paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = (this@Ball).color
+ }
+
+ /** Returns `true` if this ball intersects the given line [segment]. */
+ fun intersects(segment: LineSegment): Boolean {
+
+ /** A vector with two components. */
+ data class Vector2D(val u: Int, val v: Int) {
+ /** Returns the dot product between two 2D vectors. */
+ fun dot(other: Vector2D): Int = u * other.u + v * other.v
+ }
+
+ /** Returns the vector representing [p] minus [q]. */
+ fun pointDiff(p: Point, q: Point): Vector2D = Vector2D(p.x - q.x, p.y - q.y)
+
+ val direction = pointDiff(segment.p1, segment.p0) // p0 -> p1.
+ val centerToP = pointDiff(segment.p0, Point(x, y)) // Ball center -> p0.
+
+ // The `(centerToP + m * direction)` function models all the points in the line segment where
+ // the independent variable `m` is a real number in [0,1]. Putting this function into the
+ // formula for the circle (x ^ 2 + y ^ 2 = radius ^ 2) gives a quadratic equation
+ // (am^2 + bm + c = 0) where:
+ // [a] = direction · direction
+ // [b] = 2 centerToP · direction
+ // [c] = centerToP · centerToP - radius ^ 2
+ val a = direction.dot(direction)
+ val b = 2 * centerToP.dot(direction)
+ val c = centerToP.dot(centerToP) - radius * radius
+
+ val delta = b * b - 4 * a * c
+ if (delta < 0)
+ return false // No real roots means the (infinite) line does not intersect the ball.
+
+ val d = sqrt(delta)
+ val m1 = (-b - d) / (2 * a)
+ val m2 = (-b + d) / (2 * a)
+
+ // If a root is in [0,1], the line segment intersects the circumference.
+ // If [m1] < 0 and [m2] > 1, the line segment is "within" the circle meaning the circle
+ // intersects the infinite line, but not the line segment. In this case, we consider that it
+ // touched the ball.
+ return (m1 >= 0 && m1 <= 1) || (m2 >= 0 && m2 <= 1) || (m1 < 0 && m2 > 1)
+ }
+
+ /** Places the ball at the top of the screen at a random x-coordinate. */
+ fun reset() {
+ x = rand.nextInt(maxX)
+ y = ceil(radius).toInt()
+ }
+
+ /** Moves the ball down by [timeDeltaMs]. */
+ open fun update(timeDelta: Duration) {
+ y += (speed * timeDelta.millis).toInt()
+ }
+
+ /** Returns whether the ball is over [maxY]. */
+ fun isOutOfBounds(): Boolean = y + radius > maxY || y - radius < 0
+
+ /** Draws this ball in `c`. */
+ override fun draw(c: Canvas) {
+ c.drawCircle(x.toFloat(), y.toFloat(), radius, paint)
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/LineSegment.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/LineSegment.kt
new file mode 100644
index 00000000..54807cb3
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/LineSegment.kt
@@ -0,0 +1,18 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+/** Represents a finite line segment in 2D connected by two points [p0] and [p1]. */
+data class LineSegment(val p0: Point, val p1: Point)
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/Paddle.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/Paddle.kt
new file mode 100644
index 00000000..fb57001a
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/Paddle.kt
@@ -0,0 +1,70 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import kotlin.ranges.coerceIn
+
+/** Represents a paddle to hit/catch a falling ball. */
+open class Paddle(
+ private val color: Int = Color.WHITE,
+ // Width and height in pixels.
+ private val width: Int = 80,
+ private val height: Int = 10,
+ // maxX is the maximum X value for the center of the paddle.
+ private val maxX: Int = 100,
+ // The vertical position of the center of this paddle in pixels.
+ val y: Int = 100,
+) : Sprite() {
+
+ // Memoize a few things to make [draw()] a bit faster.
+ private val halfH = height / 2
+ private val halfW = width / 2
+ private val paint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = (this@Paddle).color
+ }
+
+ // The horizontal center of the paddle.
+ var x: Int = maxX / 2 // Start in the middle.
+ set(value) {
+ field = value.coerceIn(0, maxX)
+ }
+
+ /** Returns the (x,y) coordinates of the top-left corner. */
+ fun topLeft(): Point = Point(x - halfW, y - halfH)
+
+ /** Returns the (x,y) coordinates of the top-right corner. */
+ fun topRight(): Point = Point(x + halfW, y - halfH)
+
+ fun move(deltaX: Int) {
+ x += deltaX
+ }
+
+ override fun draw(c: Canvas) {
+ val rect =
+ Rect().apply {
+ bottom = y + halfH
+ top = y - halfH
+ left = x - halfW
+ right = x + halfW
+ }
+ c.drawRect(rect, paint)
+ }
+}
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/Point.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/Point.kt
new file mode 100644
index 00000000..adb59443
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/Point.kt
@@ -0,0 +1,18 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+/** Represents a cartesian point in 2D. */
+data class Point(val x: Int, val y: Int)
diff --git a/android_env/apps/java/com/google/androidenv/catch/sprite/Sprite.kt b/android_env/apps/java/com/google/androidenv/catch/sprite/Sprite.kt
new file mode 100644
index 00000000..09520281
--- /dev/null
+++ b/android_env/apps/java/com/google/androidenv/catch/sprite/Sprite.kt
@@ -0,0 +1,24 @@
+// Copyright 2025 DeepMind Technologies Limited.
+//
+// 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
+//
+// http://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.androidenv.catch.sprite
+
+import android.graphics.Canvas
+
+/** Represents something that can be drawn on the screen. */
+open class Sprite {
+
+ /** Draws the Sprite in the given canvas. */
+ open fun draw(c: Canvas) {}
+}