diff --git a/.githooks/pre-commit b/.githooks/pre-commit index ed8930a0..49906622 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -78,6 +78,16 @@ if [[ -n "$changed_python" ]]; then fi fi +# --------------------------------------------------------------------------------------- +# Java: fully qualified names where an import would do. The whole tree rather than the staged +# files, because whether a long name is needed depends on the other classes in its package. +# Under a second, and it reads source only. +# --------------------------------------------------------------------------------------- +if staged | grep -qE '^app/src/.*\.java$'; then + say "Java changed - checking for fully qualified names an import would do" + "$PYTHON" scripts/check_java_imports.py || failed=1 +fi + # --------------------------------------------------------------------------------------- # Secrets. secrets.properties holds a real Google Maps key and is gitignored; committing it # would publish the key, and a published key cannot be unpublished, only rotated. diff --git a/.github/workflows/static-checks.yml b/.github/workflows/static-checks.yml index 1a707fce..2e16f71c 100644 --- a/.github/workflows/static-checks.yml +++ b/.github/workflows/static-checks.yml @@ -58,12 +58,9 @@ jobs: python -m pip install pytest python -m pytest scripts/test -v - # Reported but not yet blocking: the existing code has about 270 findings, and the cleanup - # (`--fix`) waits until the open pull requests are merged so it does not conflict with them. - # Remove continue-on-error once it is done. See "Fully qualified names in Java" in - # CONTRIBUTING.md. + # A long name only where the short one would clash. See "Fully qualified names in Java" + # in CONTRIBUTING.md; `--fix` rewrites them. - name: Check Java uses imports, not fully qualified names - continue-on-error: true run: python scripts/check_java_imports.py # pyright rather than mypy: the editors used on this project run Pylance, which is diff --git a/AGENTS.md b/AGENTS.md index 11d3f37e..5fe25e58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -992,8 +992,9 @@ locales the first time it was run. It also runs in CI, on every PR and every rel ### Pre-commit hook -`.githooks/pre-commit` runs the translation check, plus flake8 and pyright over whichever -Python files are staged, and refuses a commit that stages `secrets.properties`. Hooks are not +`.githooks/pre-commit` runs the translation check, flake8 and pyright over whichever Python +files are staged, the Java import check when Java is staged, and refuses a commit that stages +`secrets.properties`. Hooks are not versioned, so each clone has to opt in once: ```bash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30a9bfda..5f6c3407 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,8 +62,8 @@ Hooks are not versioned, so this is once per clone: git config core.hooksPath .githooks ``` -`.githooks/pre-commit` runs the translation check, plus flake8 and pyright over whatever -Python you staged, and refuses to commit `secrets.properties`. It deliberately does not +`.githooks/pre-commit` runs the translation check, flake8 and pyright over whatever Python you +staged, the Java import check when you staged Java, and refuses to commit `secrets.properties`. It deliberately does not build or run tests — a hook that takes a minute gets bypassed with `--no-verify` within a day, and a routinely-bypassed hook is worse than none. Use `--no-verify` when you genuinely need to. @@ -689,10 +689,8 @@ python scripts/check_java_imports.py --fix # shorten them and add the imports ``` It reads the source alone, so it needs no Gradle and runs in under a second. It runs in -`static-checks.yml` but **does not fail the build yet**: the existing code has about 270 of these, -and fixing them is being held back until the open pull requests are in, so the cleanup does not -conflict with them. Once it is fixed, the step becomes blocking. - +`static-checks.yml` and fails the build, and the pre-commit hook runs it whenever a Java file is +staged. --- ## Adding user-facing strings (Coding Agents) @@ -877,7 +875,7 @@ run regardless: each AES entry carries a fresh random salt. | Workflow | When | What | | --- | --- | --- | -| `static-checks.yml` | `app/**`, `scripts/**` changes | Translation check, pyright, tooling tests, Java import check (reported, not yet blocking). Seconds, no Android | +| `static-checks.yml` | `app/**`, `scripts/**` changes | Translation check, pyright, tooling tests, Java import check. Seconds, no Android | | `build-debug.yml` | Android build inputs change | Instrumented tests on an emulator, JVM tests, Chaquopy bridge tests, debug APK. Also publishes the generated Room schemas as `room-schemas`, so a version bump made without an Android toolchain can still commit the JSON rule 1 requires | | `build-release.yml` | on release | Translation check, JVM tests, Chaquopy bridge tests, release APK | | `macos-scripts-python.yml` | `python/**` changes | Exporter and shared-package tests across Python 3.10–3.13 on macOS 14 | diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/AcceptingTermsFlowTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/AcceptingTermsFlowTest.java index 2cef8388..68a5c319 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/AcceptingTermsFlowTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/AcceptingTermsFlowTest.java @@ -38,6 +38,10 @@ import dev.wander.android.opentagviewer.python.FakeAppleAuthService; import dev.wander.android.opentagviewer.python.TermsDocument; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import android.view.View; +import android.widget.ScrollView; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.action.ViewActions; import static androidx.test.espresso.intent.Intents.intending; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; @@ -261,7 +265,7 @@ public void thesystemBackButtonNeitherCrashesNorAgrees() { Eventually.check(() -> onView(withId(R.id.login_terms_container)) .check(matches(isDisplayed()))); - androidx.test.espresso.Espresso.pressBack(); + Espresso.pressBack(); // Still here, still on the first document, and nothing sent. Eventually.check(() -> onView(withId(R.id.login_terms_container)) @@ -308,7 +312,7 @@ public void thedocumentScrollsWhenItIsLongerThanTheBox() { for (int attempt = 0; attempt < 30 && scrollY() != previous; attempt++) { previous = scrollY(); onView(withId(R.id.login_terms_scroll)) - .perform(androidx.test.espresso.action.ViewActions.swipeUp()); + .perform(ViewActions.swipeUp()); // Paced so this is watchable when somebody is being shown the flow. Off by default, // so a normal run and CI are unaffected. TestPace.afterAStep(); @@ -342,7 +346,7 @@ private static String aVeryLongDocument() { private int scrollY() { final int[] value = {-1}; this.scenario.onActivity(activity -> - value[0] = ((android.widget.ScrollView) + value[0] = ((ScrollView) activity.findViewById(R.id.login_terms_scroll)).getScrollY()); return value[0]; } @@ -364,7 +368,7 @@ private int contentHeight() { private boolean isShown(final int id) { final boolean[] shown = {false}; this.scenario.onActivity(activity -> - shown[0] = activity.findViewById(id).getVisibility() == android.view.View.VISIBLE); + shown[0] = activity.findViewById(id).getVisibility() == View.VISIBLE); return shown[0]; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/AppleLoginFlowTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/AppleLoginFlowTest.java index 9d312f62..84148fe4 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/AppleLoginFlowTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/AppleLoginFlowTest.java @@ -50,6 +50,8 @@ import dev.wander.android.opentagviewer.python.PythonAuthService.AuthMethodPhone; import dev.wander.android.opentagviewer.util.HowLongToWait; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import android.content.Context; +import java.util.Locale; /** * Signing in, driven end to end with Apple replaced. @@ -335,7 +337,7 @@ public void appleDecliningIsNotBlamedOnThePasswordOrTheNetwork() { launch(); signIn(); - final android.content.Context context = getInstrumentation().getTargetContext(); + final Context context = getInstrumentation().getTargetContext(); Eventually.check(() -> onView(withId(R.id.login_error_message_text)).check(matches( withText(context.getString(R.string.login_failed_apple_declined))))); @@ -370,7 +372,7 @@ public void icloudBeingRefusedIsNotReportedAsTermsOfService() { launch(); signIn(); - final android.content.Context context = getInstrumentation().getTargetContext(); + final Context context = getInstrumentation().getTargetContext(); Eventually.check(() -> onView(withId(R.id.login_error_message_text)).check(matches( withText(context.getString(R.string.login_failed_icloud_refused))))); @@ -402,11 +404,11 @@ public void aWaitAppleNamedIsShownRoundedUp() { launch(); signIn(); - final android.content.Context context = getInstrumentation().getTargetContext(); - final java.util.Locale locale = context.getResources().getConfiguration().getLocales().get(0); + final Context context = getInstrumentation().getTargetContext(); + final Locale locale = context.getResources().getConfiguration().getLocales().get(0); // The device's ICU, not a string this test composed: 90 seconds is two minutes, not one. - assertEquals("2 minutes", HowLongToWait.of(90).describe(java.util.Locale.ENGLISH)); + assertEquals("2 minutes", HowLongToWait.of(90).describe(Locale.ENGLISH)); Eventually.check(() -> onView(withId(R.id.login_error_message_text)).check(matches( withText(context.getString(R.string.login_failed_apple_asked_to_wait, diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudBackPressTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudBackPressTest.java index bb97ed94..e9dcded5 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudBackPressTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudBackPressTest.java @@ -26,6 +26,12 @@ import dev.wander.android.opentagviewer.db.AccountBeaconsForTests; import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; +import android.view.View; +import androidx.test.platform.app.InstrumentationRegistry; +import dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore; +import dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository; +import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import org.junit.Before; /** * What back does on each step of reading the account. @@ -51,7 +57,7 @@ public void putTheRealOneBack() { AccountBeaconsForTests.forgetThemAll(); } - @org.junit.Before + @Before public void forgetAnyStoredMembership() { // Finishing this flow writes real rows into the real database. Cleared here too, // because a test that crashed left its tags behind for whatever runs next. @@ -61,11 +67,11 @@ public void forgetAnyStoredMembership() { // FetchFromICloudMembershipTest stores one; without this, every test here resumes as a // member and never sees the device list. Cleared before rather than only after, because // a test that crashed leaves it behind. - new dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository( - dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore.getInstance( - androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() + new KeychainMembershipRepository( + UserAuthDataStore.getInstance( + InstrumentationRegistry.getInstrumentation() .getTargetContext()), - new dev.wander.android.opentagviewer.util.android.AppCryptographyUtil()) + new AppCryptographyUtil()) .forget().blockingAwait(); } @@ -79,7 +85,7 @@ private void open(final FakeICloudService fake) { private boolean isShown(final int id) { final boolean[] shown = {false}; this.scenario.onActivity(activity -> - shown[0] = activity.findViewById(id).getVisibility() == android.view.View.VISIBLE); + shown[0] = activity.findViewById(id).getVisibility() == View.VISIBLE); return shown[0]; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudErrorsTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudErrorsTest.java index 0178174e..6ffdc26b 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudErrorsTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudErrorsTest.java @@ -25,6 +25,12 @@ import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.python.icloud.ICloudException; import dev.wander.android.opentagviewer.python.icloud.ICloudFailure; +import android.view.View; +import androidx.test.platform.app.InstrumentationRegistry; +import dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore; +import dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository; +import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import org.junit.Before; /** * What the user is told when reading the account does not work. @@ -54,7 +60,7 @@ public void putTheRealOneBack() { AccountBeaconsForTests.forgetThemAll(); } - @org.junit.Before + @Before public void forgetAnyStoredMembership() { // Finishing this flow writes real rows into the real database. Cleared here too, // because a test that crashed left its tags behind for whatever runs next. @@ -64,11 +70,11 @@ public void forgetAnyStoredMembership() { // FetchFromICloudMembershipTest stores one; without this, every test here resumes as a // member and never sees the device list. Cleared before rather than only after, because // a test that crashed leaves it behind. - new dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository( - dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore.getInstance( - androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() + new KeychainMembershipRepository( + UserAuthDataStore.getInstance( + InstrumentationRegistry.getInstrumentation() .getTargetContext()), - new dev.wander.android.opentagviewer.util.android.AppCryptographyUtil()) + new AppCryptographyUtil()) .forget().blockingAwait(); } @@ -81,7 +87,7 @@ private void open(final FakeICloudService fake) { private boolean isShown(final int id) { final boolean[] shown = {false}; this.scenario.onActivity(activity -> - shown[0] = activity.findViewById(id).getVisibility() == android.view.View.VISIBLE); + shown[0] = activity.findViewById(id).getVisibility() == View.VISIBLE); return shown[0]; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudFlowTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudFlowTest.java index 9d78a4e9..eb942c6a 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudFlowTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudFlowTest.java @@ -41,6 +41,13 @@ import dev.wander.android.opentagviewer.ui.compat.TheNavigationBar; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.python.icloud.ICloudService; +import android.content.Context; +import dev.wander.android.opentagviewer.anisette.LocalAnisette; +import dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore; +import dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository; +import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import org.hamcrest.Matchers; +import org.junit.Before; /** * Reading the account, driven end to end with iCloud replaced. @@ -69,7 +76,7 @@ public void putTheRealOneBack() { AccountBeaconsForTests.forgetThemAll(); } - @org.junit.Before + @Before public void forgetAnyStoredMembership() { // Finishing this flow writes real rows into the real database. Cleared here too, // because a test that crashed left its tags behind for whatever runs next. @@ -79,11 +86,11 @@ public void forgetAnyStoredMembership() { // FetchFromICloudMembershipTest stores one; without this, every test here resumes as a // member and never sees the device list. Cleared before rather than only after, because // a test that crashed leaves it behind. - new dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository( - dev.wander.android.opentagviewer.db.datastore.UserAuthDataStore.getInstance( - androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() + new KeychainMembershipRepository( + UserAuthDataStore.getInstance( + InstrumentationRegistry.getInstrumentation() .getTargetContext()), - new dev.wander.android.opentagviewer.util.android.AppCryptographyUtil()) + new AppCryptographyUtil()) .forget().blockingAwait(); } @@ -98,17 +105,17 @@ public void forgetAnyStoredMembership() { */ private static final String THE_STORED_SERIAL = "0PENTAGVK7QX"; - @org.junit.Before + @Before public void giveThisInstallASerialToShow() { - final android.content.Context context = + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); context.getSharedPreferences( - dev.wander.android.opentagviewer.anisette.LocalAnisette.PREFERENCES, - android.content.Context.MODE_PRIVATE) + LocalAnisette.PREFERENCES, + Context.MODE_PRIVATE) .edit() .putString( - dev.wander.android.opentagviewer.anisette.LocalAnisette.KEY_SERIAL, + LocalAnisette.KEY_SERIAL, THE_STORED_SERIAL) .commit(); } @@ -118,10 +125,10 @@ public void giveThisInstallASerialToShow() { public void takeTheSerialBackOut() { InstrumentationRegistry.getInstrumentation().getTargetContext() .getSharedPreferences( - dev.wander.android.opentagviewer.anisette.LocalAnisette.PREFERENCES, - android.content.Context.MODE_PRIVATE) + LocalAnisette.PREFERENCES, + Context.MODE_PRIVATE) .edit() - .remove(dev.wander.android.opentagviewer.anisette.LocalAnisette.KEY_SERIAL) + .remove(LocalAnisette.KEY_SERIAL) .commit(); } @@ -158,7 +165,7 @@ private void typeThePasscode() { private boolean isShown(final int id) { final boolean[] shown = {false}; this.scenario.onActivity(activity -> - shown[0] = activity.findViewById(id).getVisibility() == android.view.View.VISIBLE); + shown[0] = activity.findViewById(id).getVisibility() == View.VISIBLE); return shown[0]; } @@ -407,8 +414,8 @@ public void aserviceHavingABadDayOffersARetryInstead() { public void appleDecliningSaysSoRatherThanShowingTheStatusCode() { this.open(FakeICloudService.whereAppleIsDeclining()); - final android.content.Context context = - androidx.test.platform.app.InstrumentationRegistry + final Context context = + InstrumentationRegistry .getInstrumentation().getTargetContext(); Eventually.check(() -> onView(withId(R.id.icloud_retry_container)) @@ -417,7 +424,7 @@ public void appleDecliningSaysSoRatherThanShowingTheStatusCode() { withText(context.getString(R.string.icloud_apple_declined_body))))); onView(withId(R.id.icloud_retry_body)).check(matches( - not(withText(org.hamcrest.Matchers.containsString("503"))))); + not(withText(Matchers.containsString("503"))))); onView(withId(R.id.icloud_no_tags_container)).check(matches(not(isDisplayed()))); TestPace.afterAStep(); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudMembershipTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudMembershipTest.java index f325a27e..fdfe2898 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudMembershipTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudMembershipTest.java @@ -29,6 +29,7 @@ import dev.wander.android.opentagviewer.python.icloud.EscrowPasscode; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import android.view.View; /** * Asking for the device passcode once, and never again. @@ -82,7 +83,7 @@ private void open(final FakeICloudService fake) { private boolean isShown(final int id) { final boolean[] shown = {false}; this.scenario.onActivity(activity -> - shown[0] = activity.findViewById(id).getVisibility() == android.view.View.VISIBLE); + shown[0] = activity.findViewById(id).getVisibility() == View.VISIBLE); return shown[0]; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudScrollTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudScrollTest.java index 27d5b013..2a3c0afa 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudScrollTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/FetchFromICloudScrollTest.java @@ -27,6 +27,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import java.util.function.Function; /** * A long list of devices, with the button pinned under it. @@ -76,7 +77,7 @@ private void openWithManyDevices() { .check(matches(isDisplayed()))); } - private T fromActivity(final java.util.function.Function read, + private T fromActivity(final Function read, final T fallback) { final Object[] value = {fallback}; this.scenario.onActivity(activity -> value[0] = read.apply(activity)); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/Shot.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/Shot.java index c5b6394d..47418c34 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/Shot.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/Shot.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; /** * A picture of what is on screen, for looking at afterwards. @@ -156,7 +157,7 @@ private static String outputDir() { } private static void write(final Bitmap bitmap, final String dir, final String name) - throws java.io.IOException { + throws IOException { try (FileOutputStream out = new FileOutputStream(new File(new File(dir), name + ".png"))) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/TheWholeAppJourneyTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/TheWholeAppJourneyTest.java index 8079835e..7ad3f18b 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/TheWholeAppJourneyTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/TheWholeAppJourneyTest.java @@ -61,6 +61,8 @@ import dev.wander.android.opentagviewer.ui.maps.MapProviderFactory; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; import dev.wander.android.opentagviewer.util.rx.RefreshPolicy; +import android.view.View; +import org.hamcrest.Matcher; /** * The app, from a signed-out install to a tag's history, without stopping. @@ -490,7 +492,7 @@ private void openTheBikesHistory() { // ------------------------------------------------------------------ helpers /** A control inside the Bike's card specifically - there are two cards on the screen. */ - private static org.hamcrest.Matcher theBikesCard(final int controlId) { + private static Matcher theBikesCard(final int controlId) { return allOf(withId(controlId), isDescendantOfA(allOf( withId(R.id.tag_item_container), hasDescendant(withText(containsString(FakeICloudService.A_BIKE.getName())))))); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/AdiDeviceIdentityTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/AdiDeviceIdentityTest.java index 3cf5a07f..95c9d394 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/AdiDeviceIdentityTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/AdiDeviceIdentityTest.java @@ -17,6 +17,11 @@ import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Locale; +import java.util.UUID; /** * The exact strings this app tells Apple it is. @@ -128,13 +133,13 @@ public void theProfilesAreActuallyDifferentMachines() { */ @Test public void everyProfileSuppliesTheSixFieldsFindMyExpects() throws Exception { - final Set expected = new HashSet<>(java.util.Arrays.asList( + final Set expected = new HashSet<>(Arrays.asList( "model", "os_name", "os_version", "os_build", "cfnetwork", "darwin")); for (final Hardware hardware : Hardware.values()) { final JSONObject json = new JSONObject(hardware.toJson()); final Set actual = new HashSet<>(); - for (final java.util.Iterator keys = json.keys(); keys.hasNext(); ) { + for (final Iterator keys = json.keys(); keys.hasNext(); ) { actual.add(keys.next()); } @@ -170,10 +175,10 @@ public void afreshIdentityHasTheShapesAdiAccepts() { final AdiDeviceIdentity fresh = AdiDeviceIdentity.generate(); assertEquals(16, fresh.adiIdentifier().length()); - assertEquals(fresh.adiIdentifier().toLowerCase(java.util.Locale.ROOT), + assertEquals(fresh.adiIdentifier().toLowerCase(Locale.ROOT), fresh.adiIdentifier()); assertEquals(36, fresh.uniqueDeviceIdentifier().length()); - assertEquals(fresh.uniqueDeviceIdentifier().toUpperCase(java.util.Locale.ROOT), + assertEquals(fresh.uniqueDeviceIdentifier().toUpperCase(Locale.ROOT), fresh.uniqueDeviceIdentifier()); } @@ -186,11 +191,11 @@ public void afreshIdentityHasTheShapesAdiAccepts() { */ @Test public void theiphoneProfilesLocalUserIdIsAUuid() { - final String id = Hardware.IPHONE.newLocalUserId(new java.security.SecureRandom()); + final String id = Hardware.IPHONE.newLocalUserId(new SecureRandom()); assertEquals(36, id.length()); - assertEquals(id.toUpperCase(java.util.Locale.ROOT), id); - java.util.UUID.fromString(id); + assertEquals(id.toUpperCase(Locale.ROOT), id); + UUID.fromString(id); } /** @@ -203,7 +208,7 @@ public void theiphoneProfilesLocalUserIdIsAUuid() { */ @Test public void theiphoneProfileProvisionsUnderWhatFindMyWouldSend() { - final String id = Hardware.IPHONE.newLocalUserId(new java.security.SecureRandom()); + final String id = Hardware.IPHONE.newLocalUserId(new SecureRandom()); final String whatJavaSends = Hardware.IPHONE.localUserHeader(id); final String whatFindMyWillSend = Base64.encodeToString( id.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/StubbedAdiLoadTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/StubbedAdiLoadTest.java index 85fa3e81..60eeccfb 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/StubbedAdiLoadTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/anisette/StubbedAdiLoadTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.ArrayList; /** *

libstoreservicescore.so imports symbols from only three libraries - libc++_shared (141), @@ -98,7 +99,7 @@ public static void downloadOnlyWhatIsNeeded() throws Exception { libraryDir = new File(context.getFilesDir(), "adi-test-stubbed/" + abi); nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir); - final java.util.List everything = new java.util.ArrayList<>(FROM_APPLE); + final List everything = new ArrayList<>(FROM_APPLE); everything.add(CORE_ADI); final long downloaded = AdiLibraryFetcher.fetchInto(libraryDir, abi, everything); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/BeaconRepositoryBackfillTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/BeaconRepositoryBackfillTest.java index bbd27324..eba354d3 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/BeaconRepositoryBackfillTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/BeaconRepositoryBackfillTest.java @@ -27,6 +27,8 @@ import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; import dev.wander.android.opentagviewer.python.AccessoryRequest; import dev.wander.android.opentagviewer.python.PlistToAccessoryJsonConverter; +import dev.wander.android.opentagviewer.python.FetchResult; +import java.util.Set; /** * Covers the lazy {@code accessory_json} backfill. @@ -333,12 +335,12 @@ public void storeFetchResultPersistsUpdatedAccessoryJson() { var repo = new BeaconRepository(this.db, new RecordingConverter(null)); final String updated = "{\"type\":\"accessory\",\"alignment_index\":4242}"; - var fetchResult = new dev.wander.android.opentagviewer.python.FetchResult( + var fetchResult = new FetchResult( Collections.emptyMap(), Map.of("beacon-a", updated), // Nothing was searched wide and found empty here; this test is about the // accessory JSON being written back, not about tags that have gone quiet. - java.util.Set.of(), java.util.Set.of() + Set.of(), Set.of() ); repo.storeFetchResult(fetchResult).blockingFirst(); @@ -353,10 +355,10 @@ public void storeFetchResultIgnoresNullUpdates() { insertBeacon("beacon-a", PLIST, CONVERTED_JSON); var repo = new BeaconRepository(this.db, new RecordingConverter(null)); - var updates = new java.util.HashMap(); + var updates = new HashMap(); updates.put("beacon-a", null); - var fetchResult = new dev.wander.android.opentagviewer.python.FetchResult( - Collections.emptyMap(), updates, java.util.Set.of(), java.util.Set.of()); + var fetchResult = new FetchResult( + Collections.emptyMap(), updates, Set.of(), Set.of()); repo.storeFetchResult(fetchResult).blockingFirst(); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/WhichFetchesAreWorthABannerTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/WhichFetchesAreWorthABannerTest.java index 262a4c83..920dd70c 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/WhichFetchesAreWorthABannerTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/repo/WhichFetchesAreWorthABannerTest.java @@ -19,6 +19,7 @@ import dev.wander.android.opentagviewer.db.room.OpenTagViewerDatabase; import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; import dev.wander.android.opentagviewer.python.AccessoryRequest; +import java.util.ArrayList; /** * Which fetches put the "still locating your tags" banner up. @@ -80,7 +81,7 @@ private void givenABeacon(final String id, final String alignmentPlist) { } private boolean wouldBeSlow(final String... beaconIds) { - final List requests = new java.util.ArrayList<>(); + final List requests = new ArrayList<>(); for (final String id : beaconIds) { requests.add(new AccessoryRequest(id, "{\"type\":\"accessory\"}")); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/room/OpenTagViewerDatabaseMigrationTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/room/OpenTagViewerDatabaseMigrationTest.java index 78cb7760..9b7add76 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/db/room/OpenTagViewerDatabaseMigrationTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/db/room/OpenTagViewerDatabaseMigrationTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import android.database.sqlite.SQLiteDatabase; /** * Regression tests for the v1 → v2 database upgrade. @@ -763,7 +764,7 @@ private static void insertOwnedBeaconV5( values.put("is_removed", isRemoved ? 1 : 0); values.put("from_account", 0); values.put("fruitless_scans", 0); - db.insert("OwnedBeacons", android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT, values); + db.insert("OwnedBeacons", SQLiteDatabase.CONFLICT_ABORT, values); } /** @@ -777,7 +778,7 @@ private static void insertUserBeaconOptions( values.put("last_update", 1700000000000L); values.put("ui_name", uiName); values.put("ui_emoji", uiEmoji); - db.insert("UserBeaconOptions", android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT, values); + db.insert("UserBeaconOptions", SQLiteDatabase.CONFLICT_ABORT, values); } private static void insertImport(SupportSQLiteDatabase db, long id) { @@ -788,7 +789,7 @@ private static void insertImport(SupportSQLiteDatabase db, long id) { values.put("exported_at", 1699999999000L); values.put("source_user", "someone@example.com"); values.put("via", "test"); - db.insert("Import", android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT, values); + db.insert("Import", SQLiteDatabase.CONFLICT_ABORT, values); } private static void insertOwnedBeaconV1( @@ -799,7 +800,7 @@ private static void insertOwnedBeaconV1( values.put("content", content); values.put("version", "1.0"); values.put("is_removed", isRemoved ? 1 : 0); - db.insert("OwnedBeacons", android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT, values); + db.insert("OwnedBeacons", SQLiteDatabase.CONFLICT_ABORT, values); } private static void insertLocationReport( @@ -816,6 +817,6 @@ private static void insertLocationReport( values.put("horizontal_accuracy", 10); values.put("status", 0); values.put("last_update", timestamp); - db.insert("LocationReport", android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT, values); + db.insert("LocationReport", SQLiteDatabase.CONFLICT_ABORT, values); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/AccountRefresherTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/AccountRefresherTest.java index af66e431..528285f1 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/AccountRefresherTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/AccountRefresherTest.java @@ -22,6 +22,7 @@ import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import java.util.stream.Collectors; /** * Re-reading the Apple account with nobody watching. @@ -83,7 +84,7 @@ private AccountRefresher aRefresher() { private List liveBeaconIds() { return this.db.ownedBeaconDao().getAll().stream().map(b -> b.id).collect( - java.util.stream.Collectors.toList()); + Collectors.toList()); } /** A linked account is read and the tags land in the database. */ diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/FakeICloudService.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/FakeICloudService.java index f9e0196d..b705f723 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/FakeICloudService.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/python/icloud/FakeICloudService.java @@ -5,6 +5,7 @@ import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Observable; +import java.util.concurrent.TimeUnit; /** * An iCloud that behaves however a test needs it to. @@ -228,7 +229,7 @@ public Observable> recoveryOptions() { // A real account takes a moment. Everything else here is instant, which is right for a // test and useless for looking at the screen somebody sees while they wait. return this.answerDelayMs > 0 - ? answer.delay(this.answerDelayMs, java.util.concurrent.TimeUnit.MILLISECONDS) + ? answer.delay(this.answerDelayMs, TimeUnit.MILLISECONDS) : answer; } @@ -268,7 +269,7 @@ public Completable unlock(final String serial, final String passcode) { return this.unlockDelayMs > 0 ? Completable.complete() - .delay(this.unlockDelayMs, java.util.concurrent.TimeUnit.MILLISECONDS) + .delay(this.unlockDelayMs, TimeUnit.MILLISECONDS) : Completable.complete(); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/BeaconIconTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/BeaconIconTest.java index e2596fe6..006744b0 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/BeaconIconTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/BeaconIconTest.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import android.graphics.Color; /** * Which icon a tag with no emoji gets, and whether that icon actually draws anything. @@ -309,7 +310,7 @@ private static int paintedPixels(final Drawable drawable) { int painted = 0; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { - if (android.graphics.Color.alpha(bitmap.getPixel(x, y)) > 0) { + if (Color.alpha(bitmap.getPixel(x, y)) > 0) { painted++; } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ICloudStepsRenderTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ICloudStepsRenderTest.java index 5b996511..86a5afcd 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ICloudStepsRenderTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ICloudStepsRenderTest.java @@ -40,6 +40,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import androidx.test.platform.app.InstrumentationRegistry; /** * Every step of the iCloud screen, drawn from the running activity. @@ -264,7 +265,7 @@ private int heightOf(final int stepId) { } private static void write(final Bitmap bitmap, final String name) throws IOException { - final String directory = androidx.test.platform.app.InstrumentationRegistry + final String directory = InstrumentationRegistry .getArguments().getString("additionalTestOutputDir"); if (directory == null || bitmap == null) { return; diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/TermsStepLayoutTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/TermsStepLayoutTest.java index e752c151..0f0a2631 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/TermsStepLayoutTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/TermsStepLayoutTest.java @@ -27,6 +27,11 @@ import java.io.IOException; import dev.wander.android.opentagviewer.R; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; +import androidx.core.content.res.ResourcesCompat; +import androidx.test.platform.app.InstrumentationRegistry; /** * The terms step, inflated and drawn without an activity, an account or a network. @@ -149,8 +154,8 @@ public void thebackArrowIsADrawableThatPaints() { // Loaded with the theme, which is how the app loads it - passing null here is the // mistake that makes a theme-attribute vector draw as nothing. - final android.graphics.drawable.Drawable arrow = - androidx.core.content.res.ResourcesCompat.getDrawable( + final Drawable arrow = + ResourcesCompat.getDrawable( themed.getResources(), R.drawable.arrow_back_24px, themed.getTheme()); assertNotNull("the back arrow drawable is missing", arrow); @@ -257,8 +262,8 @@ private static String longDocument() { private static int resolvedBackground(final View from, final View root) { View current = from; while (current != null) { - if (current.getBackground() instanceof android.graphics.drawable.ColorDrawable) { - return ((android.graphics.drawable.ColorDrawable) + if (current.getBackground() instanceof ColorDrawable) { + return ((ColorDrawable) current.getBackground()).getColor(); } if (current == root || !(current.getParent() instanceof View)) { @@ -269,7 +274,7 @@ private static int resolvedBackground(final View from, final View root) { // Nothing painted between here and the root, so what shows through is the window // background - which is what the activity would paint. - final android.util.TypedValue value = new android.util.TypedValue(); + final TypedValue value = new TypedValue(); from.getContext().getTheme() .resolveAttribute(android.R.attr.colorBackground, value, true); return value.data; @@ -298,7 +303,7 @@ private static double channel(final int value) { } private static void write(final Bitmap bitmap, final String name) throws IOException { - final String directory = androidx.test.platform.app.InstrumentationRegistry + final String directory = InstrumentationRegistry .getArguments().getString("additionalTestOutputDir"); if (directory == null) { return; diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ThemedFindMyIconTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ThemedFindMyIconTest.java index 6f86f661..70276d0e 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ThemedFindMyIconTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/ThemedFindMyIconTest.java @@ -29,6 +29,7 @@ import dev.wander.android.opentagviewer.R; import dev.wander.android.opentagviewer.data.model.BeaconInformation; +import androidx.test.platform.app.InstrumentationRegistry; /** * The Find My accessory mark, which is the first icon here that colours itself. @@ -352,7 +353,7 @@ public void picturesOfIt() throws IOException { } private static void write(final Bitmap bitmap, final String name) throws IOException { - final String directory = androidx.test.platform.app.InstrumentationRegistry + final String directory = InstrumentationRegistry .getArguments().getString("additionalTestOutputDir"); if (directory == null) { return; diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/WikiScreenshotsFromTheMapTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/WikiScreenshotsFromTheMapTest.java index 62034589..831680ed 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/WikiScreenshotsFromTheMapTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/WikiScreenshotsFromTheMapTest.java @@ -45,6 +45,7 @@ import dev.wander.android.opentagviewer.Shot; import dev.wander.android.opentagviewer.ui.maps.MapProviderFactory; import dev.wander.android.opentagviewer.ui.maps.AMapWithTagsOnIt; +import android.os.SystemClock; /** * The screens that only exist once somebody is signed in, for the wiki. @@ -160,7 +161,7 @@ public void openTheMap() { // the one place in this repo where sleeping is the honest answer, because what is being // waited for is a third party's tile download and not anything the app decides. if (weArePhotographingTheMap) { - android.os.SystemClock.sleep(TILES_TAKE_A_MOMENT); + SystemClock.sleep(TILES_TAKE_A_MOMENT); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/compat/TheNavigationBar.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/compat/TheNavigationBar.java index 42b1b7e5..a289f674 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/compat/TheNavigationBar.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/compat/TheNavigationBar.java @@ -15,6 +15,12 @@ import java.util.ArrayList; import java.util.List; +import android.widget.HorizontalScrollView; +import android.widget.ListView; +import android.widget.ScrollView; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.RecyclerView; /** * Asks a screen whether it would keep its controls out of a navigation bar. @@ -139,11 +145,11 @@ private static boolean canBeScrolledClear(final View control, final View root) { parent = parent.getParent() instanceof ViewGroup ? (ViewGroup) parent.getParent() : null) { - if (parent instanceof android.widget.ScrollView - || parent instanceof android.widget.HorizontalScrollView - || parent instanceof androidx.core.widget.NestedScrollView - || parent instanceof androidx.recyclerview.widget.RecyclerView - || parent instanceof android.widget.ListView) { + if (parent instanceof ScrollView + || parent instanceof HorizontalScrollView + || parent instanceof NestedScrollView + || parent instanceof RecyclerView + || parent instanceof ListView) { return true; } @@ -157,8 +163,8 @@ private static boolean canBeScrolledClear(final View control, final View root) { // it properly means the behaviour's peek height, which is a bigger change than the // one this test was written for. if (parent.getLayoutParams() - instanceof androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams - && ((androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams) + instanceof CoordinatorLayout.LayoutParams + && ((CoordinatorLayout.LayoutParams) parent.getLayoutParams()).getBehavior() != null) { return true; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/error/TheErrorPageIsReportableTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/error/TheErrorPageIsReportableTest.java index 58cd0c04..203a6a98 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/error/TheErrorPageIsReportableTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/error/TheErrorPageIsReportableTest.java @@ -46,6 +46,10 @@ import dev.wander.android.opentagviewer.R; import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.LogRedactor; +import android.net.Uri; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; /** * The page somebody lands on when the app cannot say what went wrong. @@ -377,15 +381,15 @@ public void reportingIsStillOfferedWithoutALog() { intended(hasAction(Intent.ACTION_VIEW)); } - private static org.hamcrest.Matcher hasToString(final String expected) { - return new org.hamcrest.TypeSafeMatcher<>() { + private static Matcher hasToString(final String expected) { + return new TypeSafeMatcher<>() { @Override - protected boolean matchesSafely(final android.net.Uri uri) { + protected boolean matchesSafely(final Uri uri) { return expected.equals(uri.toString()); } @Override - public void describeTo(final org.hamcrest.Description description) { + public void describeTo(final Description description) { description.appendText("a Uri of " + expected); } }; diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/EveryPathAsksForAFreshSignInTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/EveryPathAsksForAFreshSignInTest.java index d48a171b..bd970407 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/EveryPathAsksForAFreshSignInTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/EveryPathAsksForAFreshSignInTest.java @@ -49,6 +49,10 @@ import dev.wander.android.opentagviewer.python.icloud.AccountRefresher; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import dev.wander.android.opentagviewer.db.repo.BeaconRepository; +import dev.wander.android.opentagviewer.db.repo.UserAuthRepository; +import dev.wander.android.opentagviewer.python.HardwareDescriber; +import dev.wander.android.opentagviewer.python.icloud.KeychainMembership; /** * Every way this app talks to the account, when Apple refuses the stored password. @@ -147,7 +151,7 @@ private void givenTheAccountIsLinked() { new KeychainMembershipRepository( UserAuthDataStore.getInstance(getInstrumentation().getTargetContext()), new AppCryptographyUtil()) - .store(new dev.wander.android.opentagviewer.python.icloud.KeychainMembership( + .store(new KeychainMembership( "{\"peer_id\":\"peer-ours\"}", "ZW50cm9weQ==", "a-passcode", "a-label", 2)) .blockingAwait(); @@ -181,7 +185,7 @@ private void seedAnAccountTag() { */ private static void treatTheTagAsAnAccessory() { AppDependencies.replaceHardwareDescriber( - new dev.wander.android.opentagviewer.python.HardwareDescriber() { + new HardwareDescriber() { @Override public String describe(final String plistXml) { return "AirTag"; @@ -233,7 +237,7 @@ public void btherefusedSessionIsForgotten() { final Context context = getInstrumentation().getTargetContext(); Eventually.check(() -> assertTrue("the refused session was kept", - new dev.wander.android.opentagviewer.db.repo.UserAuthRepository( + new UserAuthRepository( UserAuthDataStore.getInstance(context), new AppCryptographyUtil()) .getUserAuth().blockingFirst().isEmpty())); } @@ -289,7 +293,7 @@ public void dthebackgroundReadLeavesEverythingAlone() { final List held = new AccountRefresher( memberships, - new dev.wander.android.opentagviewer.db.repo.BeaconRepository(this.db)) + new BeaconRepository(this.db)) .refresh() .onErrorReturnItem(List.of()) .blockingFirst(); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/TheDeviceNoteIsShownOnceTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/TheDeviceNoteIsShownOnceTest.java index 2078713f..8e3e7931 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/TheDeviceNoteIsShownOnceTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/icloud/TheDeviceNoteIsShownOnceTest.java @@ -30,6 +30,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import androidx.lifecycle.Lifecycle; /** * The page explaining what this app put on somebody's Apple account, and when it is worth showing. @@ -171,6 +172,6 @@ public void cpressingDoneOnAReadJustLeaves() { onView(withId(R.id.icloud_primary_button)).perform(click()); Eventually.check(() -> assertTrue("the screen should have closed", - this.scenario.getState() == androidx.lifecycle.Lifecycle.State.DESTROYED)); + this.scenario.getState() == Lifecycle.State.DESTROYED)); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/importing/ImportingAZipPutsTagsOnTheMapTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/importing/ImportingAZipPutsTagsOnTheMapTest.java index 02162bd4..af4e9894 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/importing/ImportingAZipPutsTagsOnTheMapTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/importing/ImportingAZipPutsTagsOnTheMapTest.java @@ -46,6 +46,10 @@ import dev.wander.android.opentagviewer.db.room.OpenTagViewerDatabase; import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; import dev.wander.android.opentagviewer.ui.maps.AMapWithTagsOnIt; +import androidx.test.espresso.matcher.ViewMatchers; +import dev.wander.android.opentagviewer.db.room.entity.BeaconNamingRecord; +import dev.wander.android.opentagviewer.python.icloud.FakeICloudService; +import dev.wander.android.opentagviewer.ui.maps.FakeMapProvider; /** * A zip, chosen from the file picker, ends up as tags on the map. @@ -172,7 +176,7 @@ public void andappearOnTheMapWithoutLeavingTheScreen() { 2, this.theMap.map().markerCount())); for (final String beaconId : List.of(BEACON_A, BEACON_B)) { - final List pins = + final List pins = this.theMap.map().markers().stream() .filter(placed -> beaconId.equals(placed.marker.getId())) .collect(Collectors.toList()); @@ -216,7 +220,7 @@ private void chooseTheZipFromTheImportMenu() { // Not wrapped in Eventually: a PopupMenu is up by the time the click returns, and asking // for a platform-popup root that is not there yet is the slow question, not the quick // one. - onView(androidx.test.espresso.matcher.ViewMatchers.withText(R.string.do_import)) + onView(ViewMatchers.withText(R.string.do_import)) .inRoot(isPlatformPopup()).perform(click()); } @@ -273,7 +277,7 @@ private Uri writeAZipWithTwoTagsInIt() { * secret in no sense whatsoever. */ private static String ownedBeaconPlist(final String beaconId) { - return dev.wander.android.opentagviewer.python.icloud.FakeICloudService + return FakeICloudService .AN_OWNED_BEACON_PLIST // The fixture names one identifier; a bundle with two tags needs two. The // importer takes the beacon id from the file name, so this only keeps the @@ -302,7 +306,7 @@ private void forgetTheImportedTags() { for (final String beaconId : List.of(BEACON_A, BEACON_B)) { this.db.ownedBeaconDao().delete(OwnedBeacon.builder().id(beaconId).build()); this.db.beaconNamingRecordDao().delete( - dev.wander.android.opentagviewer.db.room.entity.BeaconNamingRecord.builder() + BeaconNamingRecord.builder() .id(beaconId).build()); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/login/ACodeAppleTookAndThenFailedOnTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/login/ACodeAppleTookAndThenFailedOnTest.java index ad380802..b335aeab 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/login/ACodeAppleTookAndThenFailedOnTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/login/ACodeAppleTookAndThenFailedOnTest.java @@ -41,6 +41,8 @@ import dev.wander.android.opentagviewer.db.AccountBeaconsForTests; import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.FakeAppleAuthService; +import android.view.View; +import androidx.test.espresso.intent.matcher.IntentMatchers; /** * The 503 that arrives after Apple has accepted the code. @@ -84,7 +86,7 @@ public void replaceApple() { AppDependencies.replaceAnisette(settings -> FakeAnisetteSource.ready()); Intents.init(); - Intents.intending(androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent( + Intents.intending(IntentMatchers.hasComponent( MapsActivity.class.getName())) .respondWith(new ActivityResult(Activity.RESULT_OK, null)); @@ -199,7 +201,7 @@ public void goingBackAndFailingAgainDoesNotLeaveASecondCountdownRunning() throws // stays in the hierarchy behind the method list, so matching on the number alone finds // two views the second time round and Espresso refuses. It works the first time only // because that label has not been rendered yet. - final org.hamcrest.Matcher theSmsOption = + final org.hamcrest.Matcher theSmsOption = allOf(withText(containsString(FakeAppleAuthService.PHONE_ONE)), isDisplayed()); onView(withId(R.id.twofactorauthchoice_back_button)).perform(click()); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/EveryButtonOnTheMapGoesSomewhereTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/EveryButtonOnTheMapGoesSomewhereTest.java index 12865bec..328206b8 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/EveryButtonOnTheMapGoesSomewhereTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/EveryButtonOnTheMapGoesSomewhereTest.java @@ -41,6 +41,9 @@ import dev.wander.android.opentagviewer.MyDevicesListActivity; import dev.wander.android.opentagviewer.R; import dev.wander.android.opentagviewer.SettingsActivity; +import android.view.View; +import androidx.test.espresso.matcher.ViewMatchers; +import org.hamcrest.Matcher; /** * Every control on the map goes where it says it goes. @@ -210,10 +213,10 @@ private void waitForTheCards() { } /** A control inside the first tag's card - there is more than one card on screen. */ - private static org.hamcrest.Matcher theFirstCard(final int controlId) { - return allOf(withId(controlId), androidx.test.espresso.matcher.ViewMatchers + private static Matcher theFirstCard(final int controlId) { + return allOf(withId(controlId), ViewMatchers .isDescendantOfA(allOf(withId(R.id.tag_item_container), - androidx.test.espresso.matcher.ViewMatchers + ViewMatchers .hasDescendant(withText("Bike"))))); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/MapContainerTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/MapContainerTest.java index 95a76a8f..29b46d1c 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/MapContainerTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/MapContainerTest.java @@ -15,6 +15,9 @@ import org.junit.runner.RunWith; import dev.wander.android.opentagviewer.ui.TestHostActivity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; /** * Emptying the map container, which is what makes changing map provider take effect. @@ -36,8 +39,8 @@ public class MapContainerTest { /** A fragment standing in for {@code SupportMapFragment}: restored the same way, no Maps. */ public static class AFragmentInTheContainer extends Fragment { @Override - public View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup c, - android.os.Bundle state) { + public View onCreateView(LayoutInflater inflater, ViewGroup c, + Bundle state) { return new TextView(this.requireContext()); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheCarouselFollowsTheArrangementTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheCarouselFollowsTheArrangementTest.java index a0a4c882..4e711ea9 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheCarouselFollowsTheArrangementTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheCarouselFollowsTheArrangementTest.java @@ -21,6 +21,7 @@ import dev.wander.android.opentagviewer.Eventually; import dev.wander.android.opentagviewer.R; +import androidx.test.espresso.Espresso; /** * The map's tag carousel shows the order the user arranged. @@ -141,7 +142,7 @@ private void openTheDeviceList() { } private void goBackToTheMap() { - androidx.test.espresso.Espresso.pressBack(); + Espresso.pressBack(); // The map recreates on the way back, so the carousel is rebuilt from scratch - wait for // it to exist again before asking what is in it, or an empty list reads as a pass. diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheTagCarouselAlwaysSettlesOnOneCardTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheTagCarouselAlwaysSettlesOnOneCardTest.java index ba3f70a3..f10cdaa1 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheTagCarouselAlwaysSettlesOnOneCardTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/maps/TheTagCarouselAlwaysSettlesOnOneCardTest.java @@ -19,6 +19,7 @@ import dev.wander.android.opentagviewer.Eventually; import dev.wander.android.opentagviewer.R; +import android.view.View; /** * However the tag row is dragged, it ends up centred on exactly one card. @@ -142,7 +143,7 @@ public void itsettlesEvenAfterBeingThrownAtTheEndOfTheRow() { /** Where the first card sits, as a cheap way to tell whether the row moved at all. */ private int leftEdgeOfTheFirstCard() { - final List cards = this.theMap.cards(); + final List cards = this.theMap.cards(); if (cards.isEmpty()) { return Integer.MIN_VALUE; } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ArrangingTheTagListTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ArrangingTheTagListTest.java index a0f7ea5c..0a3e769a 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ArrangingTheTagListTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ArrangingTheTagListTest.java @@ -41,6 +41,9 @@ import dev.wander.android.opentagviewer.db.room.entity.Import; import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; import dev.wander.android.opentagviewer.db.room.entity.UserBeaconOptions; +import android.view.View; +import android.widget.TextView; +import androidx.test.espresso.ViewInteraction; /** * Putting the tag list in the order the user wants it. @@ -279,7 +282,7 @@ private void openTheSelectionMenu() { * aimed there passes for a disabled item and proves nothing. The enabled state lives on the * {@code ListMenuItemView} that contains it. */ - private static androidx.test.espresso.ViewInteraction menuItem(final int title) { + private static ViewInteraction menuItem(final int title) { return onView(allOf( withClassName(endsWith("ListMenuItemView")), hasDescendant(withText(title)))) @@ -313,7 +316,7 @@ private List namesOnScreen() { this.scenario.onActivity(activity -> { final RecyclerView list = activity.findViewById(R.id.my_devices_list); for (int i = 0; i < list.getChildCount(); i++) { - names.add(((android.widget.TextView) list.getChildAt(i) + names.add(((TextView) list.getChildAt(i) .findViewById(R.id.list_item_device_name)).getText().toString()); } }); @@ -334,9 +337,9 @@ private int visibleDragHandles() { this.scenario.onActivity(activity -> { final RecyclerView list = activity.findViewById(R.id.my_devices_list); for (int i = 0; i < list.getChildCount(); i++) { - final android.view.View handle = + final View handle = list.getChildAt(i).findViewById(R.id.list_item_drag_handle); - if (handle != null && handle.getVisibility() == android.view.View.VISIBLE) { + if (handle != null && handle.getVisibility() == View.VISIBLE) { count[0]++; } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsReachesTheBugPageTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsReachesTheBugPageTest.java index 6a57137f..76b40d02 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsReachesTheBugPageTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsReachesTheBugPageTest.java @@ -46,6 +46,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.BundleBuilder; import dev.wander.android.opentagviewer.ui.error.ErrorReportActivity; +import dev.wander.android.opentagviewer.Eventually; /** * Exporting tags, driven from the list, when the app cannot do it. @@ -160,15 +161,15 @@ private void forgetIt() { private void exportIt() { this.scenario = ActivityScenario.launch(MyDevicesListActivity.class); - dev.wander.android.opentagviewer.Eventually.check(() -> + Eventually.check(() -> onView(withText(A_NAME)).check(matches(isDisplayed()))); onView(withText(A_NAME)).perform(longClick()); - dev.wander.android.opentagviewer.Eventually.check(() -> + Eventually.check(() -> onView(withId(R.id.selection_menu_button)).check(matches(isDisplayed()))); onView(withId(R.id.selection_menu_button)).perform(click()); - dev.wander.android.opentagviewer.Eventually.check(() -> + Eventually.check(() -> onView(withText(R.string.export_tags)).check(matches(isDisplayed()))); onView(withText(R.string.export_tags)).perform(click()); } @@ -193,7 +194,7 @@ public void athepickerIsAskedForAZipWithAName() { this.exportIt(); - dev.wander.android.opentagviewer.Eventually.check(() -> intended(allOf( + Eventually.check(() -> intended(allOf( hasAction(Intent.ACTION_CREATE_DOCUMENT), hasType("application/zip")))); } @@ -214,7 +215,7 @@ public void abuilderThatCannotRunSendsThemToTheReportPage() { this.exportIt(); - dev.wander.android.opentagviewer.Eventually.check(() -> intended(allOf( + Eventually.check(() -> intended(allOf( hasComponent(ErrorReportActivity.class.getName()), hasExtra(ErrorReportActivity.EXTRA_BODY, R.string.error_report_body_export)))); } @@ -228,7 +229,7 @@ public void bthecauseIsCarriedToThePage() { this.exportIt(); - dev.wander.android.opentagviewer.Eventually.check(() -> intended(hasExtra( + Eventually.check(() -> intended(hasExtra( ErrorReportActivity.EXTRA_CAUSE, "BundleBuildException: Python did not start."))); } @@ -250,7 +251,7 @@ public void caworkingExportShowsTheCodeAndNoBugPage() { this.exportIt(); - dev.wander.android.opentagviewer.Eventually.check(() -> + Eventually.check(() -> onView(withId(R.id.exported_bundle_code)).check(matches(isDisplayed()))); // The two things that make the code usable: it says it cannot be shown again, and it diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsThatGoesWrongTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsThatGoesWrongTest.java index e2cc0a69..2ed5ed3d 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsThatGoesWrongTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/ExportingTagsThatGoesWrongTest.java @@ -38,6 +38,8 @@ import dev.wander.android.opentagviewer.python.BundleBuilder; import dev.wander.android.opentagviewer.ui.error.ErrorReportActivity; import dev.wander.android.opentagviewer.util.export.TagExporter; +import dev.wander.android.opentagviewer.MapsActivity; +import dev.wander.android.opentagviewer.util.parse.BundlePasscode; /** * What an export does when it cannot be done. @@ -66,7 +68,7 @@ public void catchWhatLeavesTheApp() { Intents.init(); intending(anyOf( hasComponent(ErrorReportActivity.class.getName()), - hasComponent(dev.wander.android.opentagviewer.MapsActivity.class.getName()))) + hasComponent(MapsActivity.class.getName()))) .respondWith(new ActivityResult(Activity.RESULT_CANCELED, null)); } @@ -203,8 +205,8 @@ public void asuccessfulExportHandsBackAUsableCode() throws Exception { assertEquals(1, written.getCount()); assertEquals(12, written.getPasscode().length()); assertEquals(written.getPasscode(), - dev.wander.android.opentagviewer.util.parse.BundlePasscode.normalise( - dev.wander.android.opentagviewer.util.parse.BundlePasscode.format( + BundlePasscode.normalise( + BundlePasscode.format( written.getPasscode()))); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RemoveAccountTagTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RemoveAccountTagTest.java index f36e98fa..ba9b0868 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RemoveAccountTagTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RemoveAccountTagTest.java @@ -38,6 +38,14 @@ import dev.wander.android.opentagviewer.db.room.entity.BeaconNamingRecord; import dev.wander.android.opentagviewer.db.room.entity.Import; import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; +import android.view.View; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.platform.app.InstrumentationRegistry; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import org.hamcrest.Matcher; /** * Removing a tag the app does not own. @@ -360,12 +368,12 @@ private void capture(final String name) { *

So this reaches a view inside the window and draws upward from it. Whatever root that * view belongs to is the window it is in, by definition. */ - private void captureWindowShowing(final org.hamcrest.Matcher inside, + private void captureWindowShowing(final Matcher inside, final String name) { final Bitmap[] shot = new Bitmap[1]; - onView(inside).perform(new androidx.test.espresso.ViewAction() { + onView(inside).perform(new ViewAction() { @Override - public org.hamcrest.Matcher getConstraints() { + public Matcher getConstraints() { return isDisplayed(); } @@ -375,8 +383,8 @@ public String getDescription() { } @Override - public void perform(final androidx.test.espresso.UiController controller, - final android.view.View view) { + public void perform(final UiController controller, + final View view) { controller.loopMainThreadUntilIdle(); shot[0] = draw(view.getRootView()); } @@ -384,7 +392,7 @@ public void perform(final androidx.test.espresso.UiController controller, write(shot[0], name); } - private static Bitmap draw(final android.view.View view) { + private static Bitmap draw(final View view) { final Bitmap bitmap = Bitmap.createBitmap( Math.max(view.getWidth(), 1), Math.max(view.getHeight(), 1), Bitmap.Config.ARGB_8888); @@ -393,16 +401,16 @@ private static Bitmap draw(final android.view.View view) { } private static void write(final Bitmap bitmap, final String name) { - final String directory = androidx.test.platform.app.InstrumentationRegistry + final String directory = InstrumentationRegistry .getArguments().getString("additionalTestOutputDir"); if (directory == null || bitmap == null) { return; } - try (java.io.FileOutputStream out = - new java.io.FileOutputStream(new java.io.File(directory, name))) { + try (FileOutputStream out = + new FileOutputStream(new File(directory, name))) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - } catch (final java.io.IOException e) { + } catch (final IOException e) { throw new AssertionError("could not write " + name, e); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RenamingWritesToTheAccountTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RenamingWritesToTheAccountTest.java index 94cc7585..d8c2acf8 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RenamingWritesToTheAccountTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/RenamingWritesToTheAccountTest.java @@ -44,6 +44,7 @@ import dev.wander.android.opentagviewer.python.icloud.ICloudFailure; import dev.wander.android.opentagviewer.python.icloud.KeychainMembership; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import android.widget.TextView; /** * Renaming a tag, which does two completely different things depending on the tag. @@ -283,7 +284,7 @@ public void thebatteryRowSaysWhoEverUpdatesIt() { final String[] shown = {null}; Eventually.check(() -> { this.scenario.onActivity(activity -> shown[0] = - ((android.widget.TextView) activity + ((TextView) activity .findViewById(R.id.settings_debug_naming_record_battery_level) .findViewById(R.id.settings_clickable_item_content)).getText().toString()); assertNotNull(shown[0]); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/TheDeviceListNoticesNewLocationsTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/TheDeviceListNoticesNewLocationsTest.java index 4b279a63..5f09ebbd 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/TheDeviceListNoticesNewLocationsTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/TheDeviceListNoticesNewLocationsTest.java @@ -30,6 +30,8 @@ import dev.wander.android.opentagviewer.db.room.entity.Import; import dev.wander.android.opentagviewer.db.room.entity.LocationReport; import dev.wander.android.opentagviewer.db.room.entity.OwnedBeacon; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; /** * The device list noticing locations that arrived while it was in the background. @@ -200,11 +202,11 @@ public void thereturningRowNamesWhenItWasLastSeen() { withText(containsDays())))))); } - private static org.hamcrest.Matcher containsThe(final String what) { - return org.hamcrest.Matchers.containsString(what); + private static Matcher containsThe(final String what) { + return Matchers.containsString(what); } - private static org.hamcrest.Matcher containsDays() { + private static Matcher containsDays() { return containsThe("day"); } } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/WalkingThroughSharingATagTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/WalkingThroughSharingATagTest.java index bb72ccb1..060762fd 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/WalkingThroughSharingATagTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/mydevices/WalkingThroughSharingATagTest.java @@ -51,6 +51,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.BundleBuilder; import dev.wander.android.opentagviewer.ui.error.ErrorReportActivity; +import android.content.ClipData; /** * Giving somebody a tag, at a pace a person can follow. @@ -227,7 +228,7 @@ private static boolean theClipboardHasACode() { getInstrumentation().runOnMainSync(() -> { final ClipboardManager clipboard = getInstrumentation().getTargetContext() .getSystemService(ClipboardManager.class); - final android.content.ClipData clip = + final ClipData clip = clipboard == null ? null : clipboard.getPrimaryClip(); held[0] = clip == null || clip.getItemCount() == 0 ? null : clip.getItemAt(0).getText(); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/AnisetteUpgradeDialogTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/AnisetteUpgradeDialogTest.java index 18cbe08d..68cf783d 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/AnisetteUpgradeDialogTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/AnisetteUpgradeDialogTest.java @@ -30,6 +30,7 @@ import dev.wander.android.opentagviewer.R; import dev.wander.android.opentagviewer.db.repo.model.UserSettings; import dev.wander.android.opentagviewer.ui.TestHostActivity; +import androidx.test.espresso.ViewInteraction; /** * The one-time offer to move an existing login off a public Anisette server. @@ -215,7 +216,7 @@ private static UserSettings upgradedFromAnEarlierVersion() { * Left to choose, it picks the activity underneath - where none of these views are - and * reports the buttons as missing from a hierarchy they were never in. */ - private static androidx.test.espresso.ViewInteraction inTheDialog(final int stringRes) { + private static ViewInteraction inTheDialog(final int stringRes) { return onView(withText(stringRes)).inRoot(isDialog()); } diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/DebugModeReachesEveryScreenTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/DebugModeReachesEveryScreenTest.java index 40b0df03..edc40e4c 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/DebugModeReachesEveryScreenTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/DebugModeReachesEveryScreenTest.java @@ -40,6 +40,7 @@ import dev.wander.android.opentagviewer.db.datastore.UserSettingsDataStore; import dev.wander.android.opentagviewer.db.repo.UserSettingsRepository; import dev.wander.android.opentagviewer.ui.maps.AMapWithTagsOnIt; +import androidx.recyclerview.widget.RecyclerView; /** * One switch in Settings, and the three unrelated screens that have to obey it. @@ -343,7 +344,7 @@ private boolean historyRowsCarryExtraDetail() { private boolean aHistoryRowExists() { final boolean[] any = {false}; this.screen.onActivity(activity -> { - final androidx.recyclerview.widget.RecyclerView list = + final RecyclerView list = activity.findViewById(R.id.recycler_view_history_items); any[0] = list != null && list.getAdapter() != null && list.getAdapter().getItemCount() > 0; diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/FetchFromAccountSettingTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/FetchFromAccountSettingTest.java index d13d23c9..4fac4a4d 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/FetchFromAccountSettingTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/FetchFromAccountSettingTest.java @@ -40,6 +40,8 @@ import dev.wander.android.opentagviewer.db.repo.KeychainMembershipRepository; import dev.wander.android.opentagviewer.python.icloud.KeychainMembership; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; +import android.content.Intent; +import androidx.test.espresso.Espresso; /** * Reading the Apple account from Settings. @@ -185,7 +187,7 @@ public void tappingItReachesTheAccountScreen() { */ @Test public void whatTheAccountScreenImportedIsPassedBackToWhoeverOpenedSettings() { - final android.content.Intent imported = new android.content.Intent(); + final Intent imported = new Intent(); imported.putExtra(FetchFromICloudActivity.RESULT_IMPORTED, true); intending(hasComponent(FetchFromICloudActivity.class.getName())) .respondWith(new ActivityResult(Activity.RESULT_OK, imported)); @@ -201,7 +203,7 @@ public void whatTheAccountScreenImportedIsPassedBackToWhoeverOpenedSettings() { // which is the method that sets the result at all - so the test reported RESULT_CANCELED // and said the flag had been dropped, for a screen that was never asked to report one. // Espresso's back goes through onBackPressed and therefore through the real exit. - androidx.test.espresso.Espresso.pressBackUnconditionally(); + Espresso.pressBackUnconditionally(); // ActivityScenario.getResult() hands back Instrumentation.ActivityResult, the same type // the stub above is built from. @@ -234,9 +236,9 @@ public void backingOutOfItDoesNotClaimAnImport() { onView(withId(R.id.settings_fetch_from_account)).perform(click()); Eventually.check(() -> intended(hasComponent(FetchFromICloudActivity.class.getName()))); - androidx.test.espresso.Espresso.pressBackUnconditionally(); + Espresso.pressBackUnconditionally(); - final android.content.Intent data = this.scenario.getResult().getResultData(); + final Intent data = this.scenario.getResult().getResultData(); if (data != null) { assertFalse("a cancelled account screen was reported as an import", data.getBooleanExtra(FetchFromICloudActivity.RESULT_IMPORTED, false)); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/TheICloudOfferAppearsOnceTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/TheICloudOfferAppearsOnceTest.java index 7aade8a7..166b84ec 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/TheICloudOfferAppearsOnceTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/settings/TheICloudOfferAppearsOnceTest.java @@ -52,6 +52,11 @@ import dev.wander.android.opentagviewer.python.icloud.KeychainMembership; import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil; import dev.wander.android.opentagviewer.util.rx.RefreshPolicy; +import androidx.datastore.preferences.core.MutablePreferences; +import dev.wander.android.opentagviewer.AppKeyStoreConstants; +import io.reactivex.rxjava3.core.Single; +import java.security.KeyStore; +import java.util.function.Consumer; /** * The one-time offer to read tags out of the Apple account. @@ -342,11 +347,11 @@ private void givenAConnection() { */ private void andThenItsKeystoreKeyDisappears() { try { - final java.security.KeyStore keyStore = - java.security.KeyStore.getInstance("AndroidKeyStore"); + final KeyStore keyStore = + KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); keyStore.deleteEntry( - dev.wander.android.opentagviewer.AppKeyStoreConstants.KEYSTORE_ALIAS_KEYCHAIN); + AppKeyStoreConstants.KEYSTORE_ALIAS_KEYCHAIN); } catch (final Exception e) { throw new IllegalStateException("could not take the keystore key away", e); } @@ -355,12 +360,12 @@ private void andThenItsKeystoreKeyDisappears() { /** Keep the key, ruin the ciphertext: the combination that should not be possible. */ private void butItsStoredBytesAreDamaged() { UserAuthDataStore.getInstance(this.context).updateDataAsync(preferences -> { - final androidx.datastore.preferences.core.MutablePreferences mutable = + final MutablePreferences mutable = preferences.toMutablePreferences(); mutable.set(UserAuthDataStore.KEYCHAIN_MEMBERSHIP, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); - return io.reactivex.rxjava3.core.Single.just(mutable); + return Single.just(mutable); }).blockingGet(); } @@ -437,7 +442,7 @@ public void acceptingOpensTheAccountFetch() { * the Anisette offer already answered. It failed for a reason that had nothing to do with * the behaviour, and would have passed or failed depending on the order tests ran in. */ - private void settingsWhere(final java.util.function.Consumer arrange) { + private void settingsWhere(final Consumer arrange) { final UserSettings settings = this.settingsRepo.getUserSettings(); settings.setIcloudOfferMade(false); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/theme/SystemColorsLayoutTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/theme/SystemColorsLayoutTest.java index c061b942..9817518b 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/theme/SystemColorsLayoutTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/ui/theme/SystemColorsLayoutTest.java @@ -28,6 +28,9 @@ import java.io.IOException; import dev.wander.android.opentagviewer.R; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import androidx.appcompat.content.res.AppCompatResources; /** * Renders the layouts that carried hard-coded palette colours, with and without wallpaper @@ -243,9 +246,9 @@ private Bitmap renderDrawableOn( /** WCAG 2.1 relative luminance. */ private static double relativeLuminance(final int color) { final double[] channel = { - android.graphics.Color.red(color) / 255d, - android.graphics.Color.green(color) / 255d, - android.graphics.Color.blue(color) / 255d, + Color.red(color) / 255d, + Color.green(color) / 255d, + Color.blue(color) / 255d, }; for (int i = 0; i < channel.length; i++) { channel[i] = channel[i] <= 0.04045d @@ -268,8 +271,8 @@ private static double contrastRatio(final int a, final int b) { * attributes inside the vector resolve - which is the entire thing under test. */ private Bitmap renderDrawable(final Context context, final int drawableRes) { - android.graphics.drawable.Drawable drawable = - androidx.appcompat.content.res.AppCompatResources.getDrawable(context, drawableRes); + Drawable drawable = + AppCompatResources.getDrawable(context, drawableRes); if (drawable == null) { throw new AssertionError("could not load drawable " + drawableRes); diff --git a/app/src/androidTest/java/dev/wander/android/opentagviewer/util/export/WhatWeExportWeCanImportTest.java b/app/src/androidTest/java/dev/wander/android/opentagviewer/util/export/WhatWeExportWeCanImportTest.java index 1d9fa6db..4ebe23f8 100644 --- a/app/src/androidTest/java/dev/wander/android/opentagviewer/util/export/WhatWeExportWeCanImportTest.java +++ b/app/src/androidTest/java/dev/wander/android/opentagviewer/util/export/WhatWeExportWeCanImportTest.java @@ -33,6 +33,8 @@ import dev.wander.android.opentagviewer.util.parse.AppleZipImporterUtil; import dev.wander.android.opentagviewer.util.parse.BundlePasscode; import dev.wander.android.opentagviewer.util.parse.ZipImporterException; +import java.io.FileInputStream; +import java.io.InputStream; /** * A bundle this app writes, imported back by this app. @@ -291,7 +293,7 @@ public void ithefileIsNotEmpty() throws Exception { // And the listing is readable without the code, which is a property of the zip format // rather than a choice - worth pinning so nobody assumes the file is opaque. final byte[] head = new byte[2]; - try (java.io.InputStream in = new java.io.FileInputStream(this.bundle)) { + try (InputStream in = new FileInputStream(this.bundle)) { if (in.read(head) != 2) { fail("the bundle is too short to be a zip"); } diff --git a/app/src/main/java/dev/wander/android/opentagviewer/FetchFromICloudActivity.java b/app/src/main/java/dev/wander/android/opentagviewer/FetchFromICloudActivity.java index cf8b6ac0..a4bf3945 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/FetchFromICloudActivity.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/FetchFromICloudActivity.java @@ -752,7 +752,7 @@ private void showFailure(final Throwable error) { @Override public void finish() { if (this.importedSomething) { - final android.content.Intent data = new android.content.Intent(); + final Intent data = new Intent(); data.putExtra(RESULT_IMPORTED, true); this.setResult(RESULT_OK, data); } @@ -786,7 +786,7 @@ private void openExportGuide() { } private void leaveForFileImport() { - final android.content.Intent data = new android.content.Intent(); + final Intent data = new Intent(); data.putExtra(RESULT_WANTS_FILE_IMPORT, true); this.setResult(RESULT_OK, data); this.finish(); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/MapsActivity.java b/app/src/main/java/dev/wander/android/opentagviewer/MapsActivity.java index 05d02607..95df86fc 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/MapsActivity.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/MapsActivity.java @@ -156,6 +156,7 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; import lombok.Data; +import android.graphics.Bitmap; /** * TODO: this whole thing is a bit of a godclass. Decouple it. @@ -2475,7 +2476,7 @@ private synchronized void showBeaconOnMap(final BeaconInformation beacon, final Log.d(TAG, "Going to add new marker for beaconId=" + beaconId); // 创建自定义标记图标 - android.graphics.Bitmap iconBitmap; + Bitmap iconBitmap; // **Resolved from the theme, not read from the colour resource.** The two agree in every // built-in theme, including at night - and stop agreeing the moment system colours are // on, because DynamicColors rewrites the theme attribute and cannot rewrite a fixed diff --git a/app/src/main/java/dev/wander/android/opentagviewer/MyDevicesListActivity.java b/app/src/main/java/dev/wander/android/opentagviewer/MyDevicesListActivity.java index 6db01368..5de4a87d 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/MyDevicesListActivity.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/MyDevicesListActivity.java @@ -76,6 +76,7 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import android.os.Build; public class MyDevicesListActivity extends AppCompatActivity { private static final String TAG = MyDevicesListActivity.class.getSimpleName(); @@ -494,9 +495,9 @@ private void exportTagsForSelection() { * phone a bundle came from, which is the useful half, without naming anybody. */ private static String exportedByLabel() { - return android.os.Build.MODEL == null || android.os.Build.MODEL.isBlank() + return Build.MODEL == null || Build.MODEL.isBlank() ? "OpenTagViewer for Android" - : "OpenTagViewer on " + android.os.Build.MODEL; + : "OpenTagViewer on " + Build.MODEL; } /** Dated, so exporting twice does not ask about overwriting. */ diff --git a/app/src/main/java/dev/wander/android/opentagviewer/OpenAirTagApplication.java b/app/src/main/java/dev/wander/android/opentagviewer/OpenAirTagApplication.java index 7a86797f..cb3d3431 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/OpenAirTagApplication.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/OpenAirTagApplication.java @@ -21,6 +21,8 @@ import java.io.FileInputStream; import java.nio.charset.StandardCharsets; +import android.content.Context; +import java.lang.reflect.Method; public class OpenAirTagApplication extends Application { private static final String TAG = OpenAirTagApplication.class.getSimpleName(); @@ -257,15 +259,15 @@ private void initAMapPrivacyCompliance() { // updatePrivacyShow(Context context, boolean isContains, boolean isShow) // isContains: 隐私权政策是否包含高德开平隐私权政策 // isShow: 隐私权政策是否弹窗展示告知用户 - java.lang.reflect.Method updatePrivacyShowMethod = mapsInitializerClass.getMethod( - "updatePrivacyShow", android.content.Context.class, boolean.class, boolean.class); + Method updatePrivacyShowMethod = mapsInitializerClass.getMethod( + "updatePrivacyShow", Context.class, boolean.class, boolean.class); updatePrivacyShowMethod.invoke(null, this, true, true); // 更新用户同意隐私政策状态 // updatePrivacyAgree(Context context, boolean isAgree) // isAgree: 隐私权政策是否取得用户同意 - java.lang.reflect.Method updatePrivacyAgreeMethod = mapsInitializerClass.getMethod( - "updatePrivacyAgree", android.content.Context.class, boolean.class); + Method updatePrivacyAgreeMethod = mapsInitializerClass.getMethod( + "updatePrivacyAgree", Context.class, boolean.class); updatePrivacyAgreeMethod.invoke(null, this, true); Log.i(TAG, "AMap privacy compliance initialized successfully"); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/SettingsActivity.java b/app/src/main/java/dev/wander/android/opentagviewer/SettingsActivity.java index 62231536..9c3f2daf 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/SettingsActivity.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/SettingsActivity.java @@ -99,6 +99,7 @@ import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import java.util.Objects; public class SettingsActivity extends AppCompatActivity { @@ -702,7 +703,7 @@ private void onClickEditAmapApiKey(final String providerToApplyOnSuccess) { private void updateMapProvider(String provider) { final String currentProvider = this.currentSettings.getMapProvider(); - this.mapProviderChanged = this.mapProviderChanged || !java.util.Objects.equals(currentProvider, provider); + this.mapProviderChanged = this.mapProviderChanged || !Objects.equals(currentProvider, provider); this.currentSettings.setMapProvider(provider); this.binding.setCurrentMapProvider(this.getCurrentMapProviderUiString()); this.saveSettings(); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiLibraryFetcher.java b/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiLibraryFetcher.java index 4d68a9b7..259548df 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiLibraryFetcher.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiLibraryFetcher.java @@ -17,6 +17,8 @@ import java.util.Map; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; /** * Pulls Apple's ADI libraries out of the Apple Music APK on Apple's own CDN, without @@ -223,7 +225,7 @@ private static Map parseCentralDirectory( final int commentLength = buffer.getShort(pos + 32) & 0xFFFF; final long localOffset = buffer.getInt(pos + 42) & 0xFFFFFFFFL; - final String name = new String(cd, pos + 46, nameLength, java.nio.charset.StandardCharsets.UTF_8); + final String name = new String(cd, pos + 46, nameLength, StandardCharsets.UTF_8); if (name.startsWith(prefix)) { final String bare = name.substring(prefix.length()); if (wanted.contains(bare)) { @@ -255,7 +257,7 @@ private static byte[] extract(String apkUrl, Entry entry) throws IOException { } // Raw deflate - no zlib header, hence nowrap. try (InflaterInputStream inflater = new InflaterInputStream( - new java.io.ByteArrayInputStream(raw), new Inflater(true))) { + new ByteArrayInputStream(raw), new Inflater(true))) { return readFully(inflater); } } diff --git a/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiProvisioning.java b/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiProvisioning.java index 0225ae64..07e5a7db 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiProvisioning.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/anisette/AdiProvisioning.java @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.HashMap; /** * The provisioning round trip: teaching Apple about this "machine" so it can produce Anisette @@ -156,7 +157,7 @@ private Map loadUrlBag() throws ProvisioningException { throw new ProvisioningException("the URL bag came back empty"); } - final Map out = new java.util.HashMap<>(); + final Map out = new HashMap<>(); for (final Map.Entry entry : bag.entrySet()) { if (entry.getValue() instanceof String) { out.put(entry.getKey(), (String) entry.getValue()); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnNearbyWatcher.java b/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnNearbyWatcher.java index 138e2d6c..95d628b0 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnNearbyWatcher.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnNearbyWatcher.java @@ -21,6 +21,7 @@ import dev.wander.android.opentagviewer.source.ExtraSourcesSettings; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; +import java.util.Arrays; /** * "Nearby" for Google Find Hub trackers. They carry no Apple keys, so {@link NearbyTagWatcher} @@ -75,7 +76,7 @@ public void onScanResult(final int callbackType, final ScanResult result) { if (data == null || data.length < 21) return; final int frame = data[0] & 0xFF; if (frame != 0x40 && frame != 0x41) return; - final String eid = FmdnRingCommand.hex(java.util.Arrays.copyOfRange(data, 1, 21)); + final String eid = FmdnRingCommand.hex(Arrays.copyOfRange(data, 1, 21)); final String beaconId = beaconByEid.get(eid); if (beaconId != null && !emitter.isDisposed()) { emitter.onNext(new NearbyTagSighting(beaconId, null, result.getRssi(), null, diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnRingTrigger.java b/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnRingTrigger.java index 9ae131d2..19d01b49 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnRingTrigger.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ble/FmdnRingTrigger.java @@ -32,6 +32,7 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; +import java.util.concurrent.atomic.AtomicInteger; /** * Rings a Google Find Hub tracker the way its owner's Find Hub app does: the tracker is @@ -130,7 +131,7 @@ private static Single findByEid(final Context context, final Li return; } final AtomicBoolean found = new AtomicBoolean(false); - final java.util.concurrent.atomic.AtomicInteger seen = new java.util.concurrent.atomic.AtomicInteger(); + final AtomicInteger seen = new AtomicInteger(); Log.i(TAG, "Scanning for an FMDN tracker matching one of " + eids.size() + " EIDs"); final ScanCallback callback = new ScanCallback() { @Override diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyAccessoryScanner.java b/app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyAccessoryScanner.java index eaab8958..e42eb5c5 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyAccessoryScanner.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ble/NearbyAccessoryScanner.java @@ -17,6 +17,7 @@ import io.reactivex.rxjava3.core.Single; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import java.util.concurrent.TimeoutException; /** * Scans for a BLE advertisement whose address matches one of an accessory's currently-expected @@ -87,7 +88,7 @@ public void onScanFailed(final int errorCode) { }) .firstOrError() .timeout(timeoutMs, TimeUnit.MILLISECONDS) - .onErrorResumeNext(error -> error instanceof java.util.concurrent.TimeoutException + .onErrorResumeNext(error -> error instanceof TimeoutException ? Single.error(new NotNearbyException()) : Single.error(error)); } diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ble/WideningSearch.java b/app/src/main/java/dev/wander/android/opentagviewer/ble/WideningSearch.java index 66fd9ded..a20df97f 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ble/WideningSearch.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ble/WideningSearch.java @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit; import dev.wander.android.opentagviewer.python.AccessoryMacResolver; +import java.util.HashSet; /** * Looks further back for a tag nobody has heard, a little at a time, without being asked. @@ -198,7 +199,7 @@ static Set notHeardRecently( final Map lastHeardMsByBeaconId, final long nowMs) { - final Set missing = new java.util.HashSet<>(); + final Set missing = new HashSet<>(); for (final String beaconId : beaconIds) { final Long lastHeard = lastHeardMsByBeaconId.get(beaconId); if (lastHeard == null || nowMs - lastHeard >= HEARD_RECENTLY_MS) { diff --git a/app/src/main/java/dev/wander/android/opentagviewer/data/LoginDataSource.java b/app/src/main/java/dev/wander/android/opentagviewer/data/LoginDataSource.java index fc9654ce..f076baf5 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/data/LoginDataSource.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/data/LoginDataSource.java @@ -3,6 +3,7 @@ import dev.wander.android.opentagviewer.data.model.LoggedInUser; import java.io.IOException; +import java.util.UUID; /** * Class that handles authentication w/ login credentials and retrieves user information. @@ -15,7 +16,7 @@ public Result login(String username, String password) { // TODO: handle loggedInUser authentication LoggedInUser fakeUser = new LoggedInUser( - java.util.UUID.randomUUID().toString(), + UUID.randomUUID().toString(), "Jane Doe"); return new Result.Success<>(fakeUser); } catch (Exception e) { diff --git a/app/src/main/java/dev/wander/android/opentagviewer/db/repo/BeaconRepository.java b/app/src/main/java/dev/wander/android/opentagviewer/db/repo/BeaconRepository.java index 3937427a..81977929 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/db/repo/BeaconRepository.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/db/repo/BeaconRepository.java @@ -45,6 +45,9 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; import lombok.NonNull; +import dev.wander.android.opentagviewer.db.room.dao.OwnedBeaconDao; +import java.util.Collections; +import java.util.Random; public class BeaconRepository { private final static String TAG = BeaconRepository.class.getSimpleName(); @@ -58,7 +61,7 @@ public class BeaconRepository { * across refreshes instead of restarting - a fresh Random each time is a fresh chance to * draw the same order. */ - private final java.util.Random shuffle = new java.util.Random(); + private final Random shuffle = new Random(); public BeaconRepository(OpenTagViewerDatabase db) { this(db, new ChaquopyPlistToAccessoryJsonConverter()); @@ -526,7 +529,7 @@ public Observable aFetchOfTheseWouldBeSlow(final List public Observable> toAccessoryRequests(Map beaconIdToPlistFallback) { return Observable.fromCallable(() -> { if (beaconIdToPlistFallback.isEmpty()) { - return java.util.Collections.emptyList(); + return Collections.emptyList(); } final var dao = db.ownedBeaconDao(); @@ -881,7 +884,7 @@ public Observable>> storeFetchResult(Fetc * Python knows how wide the key window was - see {@code FetchResult#getExhaustedWideSearch}. */ private void recordWhatEachScanFound( - final dev.wander.android.opentagviewer.db.room.dao.OwnedBeaconDao dao, + final OwnedBeaconDao dao, final FetchResult fetchResult) { final long now = System.currentTimeMillis(); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/python/ChaquopyBundleBuilder.java b/app/src/main/java/dev/wander/android/opentagviewer/python/ChaquopyBundleBuilder.java index bf83ad68..33aa4372 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/python/ChaquopyBundleBuilder.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/python/ChaquopyBundleBuilder.java @@ -12,6 +12,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Iterator; /** * {@link BundleBuilder} over {@code main.buildExportBundle}, which calls the same @@ -90,7 +91,7 @@ private static Built read(final String reply) throws BundleBuildException { // whatever order a hash gives. Nothing depends on it; a reproducible file is simply // easier to reason about when two of them differ. final Map files = new LinkedHashMap<>(); - for (final java.util.Iterator names = entries.keys(); names.hasNext(); ) { + for (final Iterator names = entries.keys(); names.hasNext(); ) { final String name = names.next(); files.put(name, Base64.decode(entries.getString(name), Base64.DEFAULT)); } diff --git a/app/src/main/java/dev/wander/android/opentagviewer/python/FetchResult.java b/app/src/main/java/dev/wander/android/opentagviewer/python/FetchResult.java index b86711d2..7314966b 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/python/FetchResult.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/python/FetchResult.java @@ -6,6 +6,7 @@ import dev.wander.android.opentagviewer.data.model.BeaconLocationReport; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Set; /** * Wraps the result of a {@link PythonAppleService} fetch call. With FindMy 0.9.x @@ -28,7 +29,7 @@ public class FetchResult { * little - it may not have been near an iPhone this week. Only a wide search that stayed * wide says the tag has been silent for months, and Python is where the width is known. */ - private final java.util.Set exhaustedWideSearch; + private final Set exhaustedWideSearch; /** * The accessories whose search was an expensive one - a wide key window. @@ -39,5 +40,5 @@ public class FetchResult { * failure made healthy tags accrue strikes and start being asked less often, which is the * opposite of what the backoff is for. */ - private final java.util.Set wideSearch; + private final Set wideSearch; } diff --git a/app/src/main/java/dev/wander/android/opentagviewer/python/PythonAppleService.java b/app/src/main/java/dev/wander/android/opentagviewer/python/PythonAppleService.java index 4e1396a0..6ce38090 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/python/PythonAppleService.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/python/PythonAppleService.java @@ -16,6 +16,8 @@ import dev.wander.android.opentagviewer.data.model.BeaconLocationReport; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; +import java.util.HashSet; +import java.util.Set; public class PythonAppleService { private static final String TAG = PythonAppleService.class.getSimpleName(); @@ -172,7 +174,7 @@ public Observable getReportsBetween(final List re private static FetchResult emptyResult() { return new FetchResult(Collections.emptyMap(), Collections.emptyMap(), - java.util.Set.of(), java.util.Set.of()); + Set.of(), Set.of()); } /** @@ -185,8 +187,8 @@ private static FetchResult emptyResult() { private static FetchResult mapResults(final PyObject locationReportsResult) { Map> results = new HashMap<>(); Map updatedAccessoryJson = new HashMap<>(); - java.util.Set exhaustedWideSearch = new java.util.HashSet<>(); - java.util.Set wideSearch = new java.util.HashSet<>(); + Set exhaustedWideSearch = new HashSet<>(); + Set wideSearch = new HashSet<>(); var mapBeaconIdToResult = locationReportsResult.asMap(); for (var key : mapBeaconIdToResult.keySet()) { diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/BeaconIcon.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/BeaconIcon.java index b11015d8..d79f7b0f 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/BeaconIcon.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/BeaconIcon.java @@ -11,6 +11,7 @@ import dev.wander.android.opentagviewer.R; import dev.wander.android.opentagviewer.data.model.BeaconInformation; +import android.content.Context; /** * Which icon stands in for a tag that has no emoji. @@ -142,7 +143,7 @@ public static void applyTo(final MaterialButton button, final BeaconInformation * including a context forced to night, which is how this gets rendered both ways in a test * without touching the device. */ - private static ColorStateList tintFor(final android.content.Context context, + private static ColorStateList tintFor(final Context context, @DrawableRes final int icon) { if (icon == R.drawable.findmy_accessory) { return null; diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/error/ErrorReportActivity.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/error/ErrorReportActivity.java index 46aa8bce..e2aabf02 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/error/ErrorReportActivity.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/error/ErrorReportActivity.java @@ -38,6 +38,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; +import android.view.View; /** * "This one is a bug" - the screen for a failure nobody here can fix. @@ -199,7 +200,7 @@ private void prepareTheEvidence() { this.log = evidence.log; note.setText(this.getString( R.string.error_report_log_cleaned, evidence.log.getSummary())); - share.setVisibility(android.view.View.VISIBLE); + share.setVisibility(View.VISIBLE); share.setOnClickListener(v -> this.offerTheLog()); }, error -> { diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/history/HistoryItemsAdapter.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/history/HistoryItemsAdapter.java index 133c7bea..acebd5a4 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/history/HistoryItemsAdapter.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/history/HistoryItemsAdapter.java @@ -39,6 +39,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers; import lombok.AllArgsConstructor; import lombok.Getter; +import androidx.annotation.VisibleForTesting; public class HistoryItemsAdapter extends RecyclerView.Adapter { private static final String TAG = HistoryItemsAdapter.class.getSimpleName(); @@ -52,7 +53,7 @@ public class HistoryItemsAdapter extends RecyclerView.Adapter result) { GEOCODING_CACHE.put(cacheKey(latitude, longitude), result); @@ -208,7 +209,7 @@ private static String cacheKey(final double latitude, final double longitude) { return String.format(Locale.ROOT, "%.4f,%.4f", latitude, longitude); } - @androidx.annotation.VisibleForTesting + @VisibleForTesting static List

getCachedGeocode(double latitude, double longitude) { final String key = cacheKey(latitude, longitude); var cached = GEOCODING_CACHE.get(key); @@ -227,7 +228,7 @@ static List
getCachedGeocode(double latitude, double longitude) { * Belt and braces here is cheap, and the failure it prevents is a crash on a screen the user * is only scrolling. */ - @androidx.annotation.VisibleForTesting + @VisibleForTesting static String convertLocationToUIName(@lombok.NonNull List
geocodeData) { if (geocodeData.isEmpty()) { return null; diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/AMapProvider.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/AMapProvider.java index a4d2dc93..177ad07f 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/AMapProvider.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/AMapProvider.java @@ -10,6 +10,12 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import android.content.Context; +import android.os.Bundle; +import android.view.ViewGroup; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; /** * 高德地图提供商实现 @@ -70,7 +76,7 @@ public void initialize(Activity activity, int containerViewId, OnMapReadyCallbac // 高德地图3D地图使用 TextureMapView 或 MapView // TextureMapView 性能更好,推荐使用 Class mapViewClass = Class.forName("com.amap.api.maps.TextureMapView"); - Object mapView = mapViewClass.getConstructor(android.content.Context.class) + Object mapView = mapViewClass.getConstructor(Context.class) .newInstance(activity); this.mapView = (View) mapView; @@ -78,21 +84,21 @@ public void initialize(Activity activity, int containerViewId, OnMapReadyCallbac // 获取AMap对象 // com.amap.api.maps.AMap Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method getMapMethod = mapViewClass.getMethod("getMap"); + Method getMapMethod = mapViewClass.getMethod("getMap"); this.aMap = getMapMethod.invoke(mapView); // 调用 onCreate 生命周期方法(高德地图必需) // TextureMapView.onCreate(Bundle savedInstanceState) - java.lang.reflect.Method onCreateMethod = mapViewClass.getMethod("onCreate", android.os.Bundle.class); - onCreateMethod.invoke(mapView, (android.os.Bundle) null); + Method onCreateMethod = mapViewClass.getMethod("onCreate", Bundle.class); + onCreateMethod.invoke(mapView, (Bundle) null); Log.d(TAG, "AMap TextureMapView onCreate() called"); // 添加到容器,并设置 LayoutParams 确保 View 填充整个容器 - android.view.ViewGroup container = activity.findViewById(containerViewId); + ViewGroup container = activity.findViewById(containerViewId); if (container != null) { - android.view.ViewGroup.LayoutParams layoutParams = new android.view.ViewGroup.LayoutParams( - android.view.ViewGroup.LayoutParams.MATCH_PARENT, - android.view.ViewGroup.LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT ); container.addView((View) mapView, layoutParams); Log.d(TAG, "AMap view added to container with MATCH_PARENT layout params"); @@ -121,7 +127,7 @@ public void setMapStyle(MapStyle mapStyle) { try { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method setMapTypeMethod = aMapClass.getMethod("setMapType", int.class); + Method setMapTypeMethod = aMapClass.getMethod("setMapType", int.class); int normalMapType = aMapClass.getField("MAP_TYPE_NORMAL").getInt(null); int nightMapType = aMapClass.getField("MAP_TYPE_NIGHT").getInt(null); @@ -170,16 +176,16 @@ public String addMarker(MapMarker marker) { Object latLng = latLngClass.getConstructor(double.class, double.class) .newInstance(gcj02[0], gcj02[1]); - java.lang.reflect.Method positionMethod = markerOptionsClass.getMethod("position", latLngClass); + Method positionMethod = markerOptionsClass.getMethod("position", latLngClass); positionMethod.invoke(markerOptions, latLng); // 设置标题和片段 if (marker.getTitle() != null) { - java.lang.reflect.Method titleMethod = markerOptionsClass.getMethod("title", String.class); + Method titleMethod = markerOptionsClass.getMethod("title", String.class); titleMethod.invoke(markerOptions, marker.getTitle()); } if (marker.getSnippet() != null) { - java.lang.reflect.Method snippetMethod = markerOptionsClass.getMethod("snippet", String.class); + Method snippetMethod = markerOptionsClass.getMethod("snippet", String.class); snippetMethod.invoke(markerOptions, marker.getSnippet()); } @@ -187,10 +193,10 @@ public String addMarker(MapMarker marker) { if (marker.getIconBitmap() != null) { Class bitmapDescriptorClass = Class.forName("com.amap.api.maps.model.BitmapDescriptor"); Class bitmapDescriptorFactoryClass = Class.forName("com.amap.api.maps.model.BitmapDescriptorFactory"); - java.lang.reflect.Method fromBitmapMethod = bitmapDescriptorFactoryClass.getMethod("fromBitmap", Bitmap.class); + Method fromBitmapMethod = bitmapDescriptorFactoryClass.getMethod("fromBitmap", Bitmap.class); Object bitmapDescriptor = fromBitmapMethod.invoke(null, marker.getIconBitmap()); - java.lang.reflect.Method iconMethod = markerOptionsClass.getMethod("icon", bitmapDescriptorClass); + Method iconMethod = markerOptionsClass.getMethod("icon", bitmapDescriptorClass); iconMethod.invoke(markerOptions, bitmapDescriptor); } @@ -199,7 +205,7 @@ public String addMarker(MapMarker marker) { // class talks to the SDK reflectively, so a rename in a future AMap version has to // degrade to an unordered marker rather than no marker at all. try { - java.lang.reflect.Method zIndexMethod = markerOptionsClass.getMethod("zIndex", float.class); + Method zIndexMethod = markerOptionsClass.getMethod("zIndex", float.class); zIndexMethod.invoke(markerOptions, marker.getZIndex()); } catch (Exception e) { Log.w(TAG, "Could not set the marker draw order; overlapping tags may not raise on selection", e); @@ -207,7 +213,7 @@ public String addMarker(MapMarker marker) { // 添加标记 Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method addMarkerMethod = aMapClass.getMethod("addMarker", markerOptionsClass); + Method addMarkerMethod = aMapClass.getMethod("addMarker", markerOptionsClass); Object amapMarker = addMarkerMethod.invoke(aMap, markerOptions); markers.put(marker.getId(), amapMarker); @@ -224,7 +230,7 @@ public void removeMarker(String markerId) { Object marker = markers.remove(markerId); if (marker != null) { try { - java.lang.reflect.Method removeMethod = marker.getClass().getMethod("remove"); + Method removeMethod = marker.getClass().getMethod("remove"); removeMethod.invoke(marker); } catch (Exception e) { Log.e(TAG, "Failed to remove marker", e); @@ -240,7 +246,7 @@ public void setMarkerZIndex(String markerId, float zIndex) { return; } try { - java.lang.reflect.Method setZIndexMethod = marker.getClass().getMethod("setZIndex", float.class); + Method setZIndexMethod = marker.getClass().getMethod("setZIndex", float.class); setZIndexMethod.invoke(marker, zIndex); } catch (Exception e) { // Not fatal: the marker stays where it is, just possibly behind another one. @@ -252,7 +258,7 @@ public void setMarkerZIndex(String markerId, float zIndex) { public void clearMarkers() { for (Object marker : markers.values()) { try { - java.lang.reflect.Method removeMethod = marker.getClass().getMethod("remove"); + Method removeMethod = marker.getClass().getMethod("remove"); removeMethod.invoke(marker); } catch (Exception e) { Log.e(TAG, "Failed to remove marker", e); @@ -285,7 +291,7 @@ public String addPolyline(MapPolyline polyline) { // 添加点 // com.amap.api.maps.model.LatLng Class latLngClass = Class.forName("com.amap.api.maps.model.LatLng"); - java.lang.reflect.Method addMethod = polylineOptionsClass.getMethod("add", latLngClass); + Method addMethod = polylineOptionsClass.getMethod("add", latLngClass); for (MapPolyline.LatLng point : gcj02Points) { Object latLng = latLngClass.getConstructor(double.class, double.class) @@ -294,15 +300,15 @@ public String addPolyline(MapPolyline polyline) { } // 设置颜色和宽度 - java.lang.reflect.Method colorMethod = polylineOptionsClass.getMethod("color", int.class); + Method colorMethod = polylineOptionsClass.getMethod("color", int.class); colorMethod.invoke(polylineOptions, polyline.getColor()); - java.lang.reflect.Method widthMethod = polylineOptionsClass.getMethod("width", float.class); + Method widthMethod = polylineOptionsClass.getMethod("width", float.class); widthMethod.invoke(polylineOptions, polyline.getWidth()); // 添加路径线 Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method addPolylineMethod = aMapClass.getMethod("addPolyline", polylineOptionsClass); + Method addPolylineMethod = aMapClass.getMethod("addPolyline", polylineOptionsClass); Object amapPolyline = addPolylineMethod.invoke(aMap, polylineOptions); polylines.put(polyline.getId(), amapPolyline); @@ -319,7 +325,7 @@ public void removePolyline(String polylineId) { Object polyline = polylines.remove(polylineId); if (polyline != null) { try { - java.lang.reflect.Method removeMethod = polyline.getClass().getMethod("remove"); + Method removeMethod = polyline.getClass().getMethod("remove"); removeMethod.invoke(polyline); } catch (Exception e) { Log.e(TAG, "Failed to remove polyline", e); @@ -331,7 +337,7 @@ public void removePolyline(String polylineId) { public void clearPolylines() { for (Object polyline : polylines.values()) { try { - java.lang.reflect.Method removeMethod = polyline.getClass().getMethod("remove"); + Method removeMethod = polyline.getClass().getMethod("remove"); removeMethod.invoke(polyline); } catch (Exception e) { Log.e(TAG, "Failed to remove polyline", e); @@ -355,12 +361,12 @@ public void moveCamera(double latitude, double longitude, float zoom) { // com.amap.api.maps.CameraUpdateFactory Class cameraUpdateFactoryClass = Class.forName("com.amap.api.maps.CameraUpdateFactory"); - java.lang.reflect.Method newLatLngZoomMethod = cameraUpdateFactoryClass.getMethod( + Method newLatLngZoomMethod = cameraUpdateFactoryClass.getMethod( "newLatLngZoom", latLngClass, float.class); Object cameraUpdate = newLatLngZoomMethod.invoke(null, latLng, zoom); Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method moveCameraMethod = aMapClass.getMethod("moveCamera", + Method moveCameraMethod = aMapClass.getMethod("moveCamera", Class.forName("com.amap.api.maps.CameraUpdate")); moveCameraMethod.invoke(aMap, cameraUpdate); } catch (Exception e) { @@ -383,7 +389,7 @@ public void animateCamera(double latitude, double longitude, float zoom, Runnabl // com.amap.api.maps.CameraUpdateFactory Class cameraUpdateFactoryClass = Class.forName("com.amap.api.maps.CameraUpdateFactory"); - java.lang.reflect.Method newLatLngZoomMethod = cameraUpdateFactoryClass.getMethod( + Method newLatLngZoomMethod = cameraUpdateFactoryClass.getMethod( "newLatLngZoom", latLngClass, float.class); Object cameraUpdate = newLatLngZoomMethod.invoke(null, latLng, zoom); @@ -393,7 +399,7 @@ public void animateCamera(double latitude, double longitude, float zoom, Runnabl // 创建回调 // com.amap.api.maps.AMap.CancelableCallback Class cancelableCallbackClass = Class.forName("com.amap.api.maps.AMap$CancelableCallback"); - Object callbackObj = java.lang.reflect.Proxy.newProxyInstance( + Object callbackObj = Proxy.newProxyInstance( cancelableCallbackClass.getClassLoader(), new Class[]{cancelableCallbackClass}, (proxy, method, args) -> { @@ -404,13 +410,13 @@ public void animateCamera(double latitude, double longitude, float zoom, Runnabl } ); - java.lang.reflect.Method animateCameraMethod = aMapClass.getMethod("animateCamera", + Method animateCameraMethod = aMapClass.getMethod("animateCamera", Class.forName("com.amap.api.maps.CameraUpdate"), long.class, cancelableCallbackClass); animateCameraMethod.invoke(aMap, cameraUpdate, 1000L, callbackObj); } else { - java.lang.reflect.Method animateCameraMethod = aMapClass.getMethod("animateCamera", + Method animateCameraMethod = aMapClass.getMethod("animateCamera", Class.forName("com.amap.api.maps.CameraUpdate")); animateCameraMethod.invoke(aMap, cameraUpdate); } @@ -428,15 +434,15 @@ public void setOnMapClickListener(OnMapClickListener listener) { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); Class onMapClickListenerClass = Class.forName("com.amap.api.maps.AMap$OnMapClickListener"); - Object listenerObj = java.lang.reflect.Proxy.newProxyInstance( + Object listenerObj = Proxy.newProxyInstance( onMapClickListenerClass.getClassLoader(), new Class[]{onMapClickListenerClass}, (proxy, method, args) -> { if (method.getName().equals("onMapClick")) { Object latLng = args[0]; // 3D SDK中,LatLng的latitude和longitude是public字段 - java.lang.reflect.Field latitudeField = latLng.getClass().getField("latitude"); - java.lang.reflect.Field longitudeField = latLng.getClass().getField("longitude"); + Field latitudeField = latLng.getClass().getField("latitude"); + Field longitudeField = latLng.getClass().getField("longitude"); double lat = latitudeField.getDouble(latLng); double lon = longitudeField.getDouble(latLng); @@ -450,7 +456,7 @@ public void setOnMapClickListener(OnMapClickListener listener) { } ); - java.lang.reflect.Method setOnMapClickListenerMethod = aMapClass.getMethod("setOnMapClickListener", onMapClickListenerClass); + Method setOnMapClickListenerMethod = aMapClass.getMethod("setOnMapClickListener", onMapClickListenerClass); setOnMapClickListenerMethod.invoke(aMap, listenerObj); } catch (Exception e) { Log.e(TAG, "Failed to set map click listener", e); @@ -466,7 +472,7 @@ public void setOnMarkerClickListener(OnMarkerClickListener listener) { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); Class onMarkerClickListenerClass = Class.forName("com.amap.api.maps.AMap$OnMarkerClickListener"); - Object listenerObj = java.lang.reflect.Proxy.newProxyInstance( + Object listenerObj = Proxy.newProxyInstance( onMarkerClickListenerClass.getClassLoader(), new Class[]{onMarkerClickListenerClass}, (proxy, method, args) -> { @@ -481,7 +487,7 @@ public void setOnMarkerClickListener(OnMarkerClickListener listener) { } ); - java.lang.reflect.Method setOnMarkerClickListenerMethod = aMapClass.getMethod("setOnMarkerClickListener", onMarkerClickListenerClass); + Method setOnMarkerClickListenerMethod = aMapClass.getMethod("setOnMarkerClickListener", onMarkerClickListenerClass); setOnMarkerClickListenerMethod.invoke(aMap, listenerObj); } catch (Exception e) { Log.e(TAG, "Failed to set marker click listener", e); @@ -510,22 +516,22 @@ public CameraPosition getCameraPosition() { try { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method getCameraPositionMethod = aMapClass.getMethod("getCameraPosition"); + Method getCameraPositionMethod = aMapClass.getMethod("getCameraPosition"); Object cameraPosition = getCameraPositionMethod.invoke(aMap); // com.amap.api.maps.model.CameraPosition // 3D SDK中,target是public字段 - java.lang.reflect.Field targetField = cameraPosition.getClass().getField("target"); + Field targetField = cameraPosition.getClass().getField("target"); Object target = targetField.get(cameraPosition); // LatLng的latitude和longitude是public字段 - java.lang.reflect.Field latitudeField = target.getClass().getField("latitude"); - java.lang.reflect.Field longitudeField = target.getClass().getField("longitude"); + Field latitudeField = target.getClass().getField("latitude"); + Field longitudeField = target.getClass().getField("longitude"); double lat = latitudeField.getDouble(target); double lon = longitudeField.getDouble(target); // zoom也是public字段 - java.lang.reflect.Field zoomField = cameraPosition.getClass().getField("zoom"); + Field zoomField = cameraPosition.getClass().getField("zoom"); float zoom = zoomField.getFloat(cameraPosition); // 坐标转换:GCJ02 -> WGS84 @@ -544,10 +550,10 @@ public void setMyLocationButtonEnabled(boolean enabled) { try { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); Class uiSettingsClass = Class.forName("com.amap.api.maps.UiSettings"); - java.lang.reflect.Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); + Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); Object uiSettings = getUiSettingsMethod.invoke(aMap); - java.lang.reflect.Method setMyLocationButtonEnabledMethod = uiSettingsClass.getMethod("setMyLocationButtonEnabled", boolean.class); + Method setMyLocationButtonEnabledMethod = uiSettingsClass.getMethod("setMyLocationButtonEnabled", boolean.class); setMyLocationButtonEnabledMethod.invoke(uiSettings, enabled); } catch (Exception e) { Log.e(TAG, "Failed to set my location button enabled", e); @@ -561,7 +567,7 @@ public void setMyLocationEnabled(boolean enabled) { // Reflection, like everything else here: AMap is loaded at runtime only when the user // supplies a key, so this class must compile and run without its types on the path. Class aMapClass = Class.forName("com.amap.api.maps.AMap"); - java.lang.reflect.Method setMyLocationEnabled = + Method setMyLocationEnabled = aMapClass.getMethod("setMyLocationEnabled", boolean.class); setMyLocationEnabled.invoke(aMap, enabled); } catch (Exception e) { @@ -577,10 +583,10 @@ public void setRotateGesturesEnabled(boolean enabled) { try { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); Class uiSettingsClass = Class.forName("com.amap.api.maps.UiSettings"); - java.lang.reflect.Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); + Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); Object uiSettings = getUiSettingsMethod.invoke(aMap); - java.lang.reflect.Method setRotateGesturesEnabledMethod = uiSettingsClass.getMethod("setRotateGesturesEnabled", boolean.class); + Method setRotateGesturesEnabledMethod = uiSettingsClass.getMethod("setRotateGesturesEnabled", boolean.class); setRotateGesturesEnabledMethod.invoke(uiSettings, enabled); } catch (Exception e) { Log.e(TAG, "Failed to set rotate gestures enabled", e); @@ -593,10 +599,10 @@ public void setCompassEnabled(boolean enabled) { try { Class aMapClass = Class.forName("com.amap.api.maps.AMap"); Class uiSettingsClass = Class.forName("com.amap.api.maps.UiSettings"); - java.lang.reflect.Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); + Method getUiSettingsMethod = aMapClass.getMethod("getUiSettings"); Object uiSettings = getUiSettingsMethod.invoke(aMap); - java.lang.reflect.Method setCompassEnabledMethod = uiSettingsClass.getMethod("setCompassEnabled", boolean.class); + Method setCompassEnabledMethod = uiSettingsClass.getMethod("setCompassEnabled", boolean.class); setCompassEnabledMethod.invoke(uiSettings, enabled); } catch (Exception e) { Log.e(TAG, "Failed to set compass enabled", e); @@ -627,7 +633,7 @@ public View getMapView() { public void onResume() { if (mapView != null) { try { - java.lang.reflect.Method onResumeMethod = mapView.getClass().getMethod("onResume"); + Method onResumeMethod = mapView.getClass().getMethod("onResume"); onResumeMethod.invoke(mapView); } catch (Exception e) { Log.e(TAG, "Failed to call onResume on map view", e); @@ -642,7 +648,7 @@ public void onResume() { public void onPause() { if (mapView != null) { try { - java.lang.reflect.Method onPauseMethod = mapView.getClass().getMethod("onPause"); + Method onPauseMethod = mapView.getClass().getMethod("onPause"); onPauseMethod.invoke(mapView); } catch (Exception e) { Log.e(TAG, "Failed to call onPause on map view", e); @@ -657,7 +663,7 @@ public void onPause() { public void onDestroy() { if (mapView != null) { try { - java.lang.reflect.Method onDestroyMethod = mapView.getClass().getMethod("onDestroy"); + Method onDestroyMethod = mapView.getClass().getMethod("onDestroy"); onDestroyMethod.invoke(mapView); } catch (Exception e) { Log.e(TAG, "Failed to call onDestroy on map view", e); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/GoogleMapProvider.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/GoogleMapProvider.java index 2fdf904b..46abe72f 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/GoogleMapProvider.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/GoogleMapProvider.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import dev.wander.android.opentagviewer.R; +import androidx.fragment.app.FragmentActivity; /** * Google Maps地图提供商实现 @@ -53,11 +54,11 @@ public void initialize(Activity activity, int containerViewId, IMapProvider.OnMa this.activity = activity; this.callback = callback; - if (!(activity instanceof androidx.fragment.app.FragmentActivity)) { + if (!(activity instanceof FragmentActivity)) { throw new IllegalArgumentException("Activity must be a FragmentActivity to use Google Maps"); } - FragmentManager fragmentManager = ((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager(); + FragmentManager fragmentManager = ((FragmentActivity) activity).getSupportFragmentManager(); // 查找或创建SupportMapFragment mapFragment = (SupportMapFragment) fragmentManager.findFragmentById(containerViewId); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/OsmMapProvider.java b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/OsmMapProvider.java index fe7c74b5..b8161ef7 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/OsmMapProvider.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/ui/maps/OsmMapProvider.java @@ -28,6 +28,8 @@ import org.osmdroid.views.overlay.gestures.RotationGestureOverlay; import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; +import org.osmdroid.config.Configuration; +import org.osmdroid.views.CustomZoomButtonsController; /** * OpenStreetMap provider, via osmdroid. @@ -77,10 +79,10 @@ public void initialize(Activity activity, int containerViewId, OnMapReadyCallbac // Required by OSM's tile usage policy (https://operations.osmfoundation.org/policies/tiles/): // a User-Agent that identifies the app, and a place on disk for the tile cache rather // than re-fetching what was already drawn a moment ago. - org.osmdroid.config.Configuration.getInstance().setUserAgentValue(activity.getPackageName()); + Configuration.getInstance().setUserAgentValue(activity.getPackageName()); File cacheDir = new File(activity.getCacheDir(), "osmdroid"); - org.osmdroid.config.Configuration.getInstance().setOsmdroidBasePath(cacheDir); - org.osmdroid.config.Configuration.getInstance().setOsmdroidTileCache(new File(cacheDir, "tiles")); + Configuration.getInstance().setOsmdroidBasePath(cacheDir); + Configuration.getInstance().setOsmdroidTileCache(new File(cacheDir, "tiles")); this.mapView = new MapView(activity); this.mapView.setTileSource(TileSourceFactory.MAPNIK); @@ -91,7 +93,7 @@ public void initialize(Activity activity, int containerViewId, OnMapReadyCallbac // cards - visible, overlapping, and not reachable to tap. Google and AMap are configured // without their equivalents too, so turning these off is what makes the providers agree. this.mapView.getZoomController().setVisibility( - org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER); + CustomZoomButtonsController.Visibility.NEVER); this.controller = this.mapView.getController(); this.copyrightOverlay = new CopyrightOverlay(activity); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriter.java b/app/src/main/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriter.java index 119ef929..5243748e 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriter.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriter.java @@ -11,6 +11,7 @@ import dev.wander.android.opentagviewer.data.model.BeaconLocationReport; import dev.wander.android.opentagviewer.db.room.entity.LocationReport; +import java.util.Locale; /** * Writes a tag's location history as CSV. @@ -124,8 +125,8 @@ private String toRow(final String beaconId, final BeaconLocationReport report) { Long.toString(report.getTimestamp()), // Plain decimal rather than the default locale's formatting: a comma decimal // separator would silently split the row into extra columns. - String.format(java.util.Locale.ROOT, "%.7f", report.getLatitude()), - String.format(java.util.Locale.ROOT, "%.7f", report.getLongitude()), + String.format(Locale.ROOT, "%.7f", report.getLatitude()), + String.format(Locale.ROOT, "%.7f", report.getLongitude()), Long.toString(report.getHorizontalAccuracy()), Long.toString(report.getConfidence()), Long.toString(report.getStatus()), diff --git a/app/src/main/java/dev/wander/android/opentagviewer/util/export/TagExporter.java b/app/src/main/java/dev/wander/android/opentagviewer/util/export/TagExporter.java index 5e169d67..b0a7e800 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/util/export/TagExporter.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/util/export/TagExporter.java @@ -9,6 +9,7 @@ import dev.wander.android.opentagviewer.python.AppDependencies; import dev.wander.android.opentagviewer.python.BundleBuilder; import dev.wander.android.opentagviewer.util.parse.BundlePasscode; +import java.io.IOException; /** * Turning a selection of tags into a locked zip somebody else can import. @@ -86,7 +87,7 @@ public static Exported writeTo( final String sourceUser, final long exportedAtMs) throws NothingToExportException, BundleBuilder.BundleBuildException, - java.io.IOException { + IOException { if (selection.isEmpty()) { throw new NothingToExportException("Nothing was selected."); diff --git a/app/src/main/java/dev/wander/android/opentagviewer/util/parse/AppleZipImporterUtil.java b/app/src/main/java/dev/wander/android/opentagviewer/util/parse/AppleZipImporterUtil.java index a86afee7..a9cc1705 100644 --- a/app/src/main/java/dev/wander/android/opentagviewer/util/parse/AppleZipImporterUtil.java +++ b/app/src/main/java/dev/wander/android/opentagviewer/util/parse/AppleZipImporterUtil.java @@ -687,7 +687,7 @@ private static List makeCustomAccessories( private static String plistToAccessoryJsonOrNull( final String plistXml, final String alignmentPlistXml) { try { - var py = com.chaquo.python.Python.getInstance(); + var py = Python.getInstance(); var module = py.getModule("main"); var result = module.callAttr("convertPlistToJson", plistXml, alignmentPlistXml); return result == null ? null : result.toString(); diff --git a/app/src/test/java/dev/wander/android/opentagviewer/ble/DerivedAddressStoreTest.java b/app/src/test/java/dev/wander/android/opentagviewer/ble/DerivedAddressStoreTest.java index 62b3e4b2..724c7270 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/ble/DerivedAddressStoreTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/ble/DerivedAddressStoreTest.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.nio.file.Files; /** * What the derived-address cache has to guarantee, which is less than it looks. @@ -147,7 +148,7 @@ public void aTruncatedFileIsTreatedAsNothingHeld() throws IOException { BEACON + ".bin"); assertTrue(file.isFile()); - final byte[] whole = java.nio.file.Files.readAllBytes(file.toPath()); + final byte[] whole = Files.readAllBytes(file.toPath()); try (FileOutputStream out = new FileOutputStream(file)) { out.write(whole, 0, whole.length - 3); } @@ -162,7 +163,7 @@ public void aFileFromAnotherFormatIsDiscarded() throws IOException { final File file = new File(new File(this.files.getRoot(), "derived-addresses"), BEACON + ".bin"); - final byte[] whole = java.nio.file.Files.readAllBytes(file.toPath()); + final byte[] whole = Files.readAllBytes(file.toPath()); whole[3] = (byte) 99; try (FileOutputStream out = new FileOutputStream(file)) { out.write(whole); diff --git a/app/src/test/java/dev/wander/android/opentagviewer/ble/FmdnRingCommandTest.java b/app/src/test/java/dev/wander/android/opentagviewer/ble/FmdnRingCommandTest.java index 917a486e..f2f10a8e 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/ble/FmdnRingCommandTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/ble/FmdnRingCommandTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import java.util.List; +import java.util.Arrays; public class FmdnRingCommandTest { private static final byte[] RING_KEY = {1, 2, 3, 4, 5, 6, 7, 8}; @@ -29,8 +30,8 @@ public void ringWriteLayout() { assertEquals(14, write.length); assertEquals(0x05, write[0]); assertEquals(12, write[1]); - assertEquals("43e3b0b455361e86", FmdnRingCommand.hex(java.util.Arrays.copyOfRange(write, 2, 10))); - assertArrayEquals(new byte[]{(byte) 0xff, 0x01, 0x2c, 0x00}, java.util.Arrays.copyOfRange(write, 10, 14)); + assertEquals("43e3b0b455361e86", FmdnRingCommand.hex(Arrays.copyOfRange(write, 2, 10))); + assertArrayEquals(new byte[]{(byte) 0xff, 0x01, 0x2c, 0x00}, Arrays.copyOfRange(write, 10, 14)); } @Test diff --git a/app/src/test/java/dev/wander/android/opentagviewer/ble/NearbyTagIndexTest.java b/app/src/test/java/dev/wander/android/opentagviewer/ble/NearbyTagIndexTest.java index c1746057..35e06d08 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/ble/NearbyTagIndexTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/ble/NearbyTagIndexTest.java @@ -12,6 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger; import dev.wander.android.opentagviewer.python.AccessoryMacResolver; +import java.util.concurrent.TimeUnit; /** A JVM test: {@link NearbyTagIndex} has no Android and no Bluetooth in it, deliberately. */ public class NearbyTagIndexTest { @@ -99,7 +100,7 @@ public void staysFreshInsideTheWindowAndExpiresAtIt() { @Test public void expiryIsShorterThanTheRolloverInterval() { assertTrue("an index older than a rollover predicts addresses nothing is sending any more", - NearbyTagIndex.MAX_AGE_MS < java.util.concurrent.TimeUnit.MINUTES.toMillis(15)); + NearbyTagIndex.MAX_AGE_MS < TimeUnit.MINUTES.toMillis(15)); } // --- rebuilding --------------------------------------------------------------------------- diff --git a/app/src/test/java/dev/wander/android/opentagviewer/ble/WideningSearchTest.java b/app/src/test/java/dev/wander/android/opentagviewer/ble/WideningSearchTest.java index 8b373a35..ea5ff14a 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/ble/WideningSearchTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/ble/WideningSearchTest.java @@ -19,6 +19,7 @@ import java.util.Map; import dev.wander.android.opentagviewer.python.AccessoryMacResolver; +import java.util.Set; /** * The rule for looking further back, without a radio, a phone or a key derivation. @@ -248,8 +249,8 @@ public void theTagsWorthWideningAreTheOnesNotHeardRecently() { heard.put(NEAR, 1_000_000L); heard.put(MISSING, 1_000_000L - WideningSearch.HEARD_RECENTLY_MS); - assertEquals(java.util.Set.of(MISSING), + assertEquals(Set.of(MISSING), WideningSearch.notHeardRecently( - java.util.Set.of(NEAR, MISSING), heard, 1_000_000L)); + Set.of(NEAR, MISSING), heard, 1_000_000L)); } } diff --git a/app/src/test/java/dev/wander/android/opentagviewer/source/ReportSourcesTest.java b/app/src/test/java/dev/wander/android/opentagviewer/source/ReportSourcesTest.java index 839e6b26..5c926324 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/source/ReportSourcesTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/source/ReportSourcesTest.java @@ -20,6 +20,7 @@ import dev.wander.android.opentagviewer.python.AccessoryRequest; import dev.wander.android.opentagviewer.python.FetchResult; import io.reactivex.rxjava3.core.Observable; +import java.io.IOException; public class ReportSourcesTest { private static final long NOW = 1_800_000_000_000L; @@ -50,7 +51,7 @@ public void locate(final String googleId, final int waitS) { @Override public List reports(final String googleId, final long sinceMs) throws Exception { if (this.failing) { - throw new java.io.IOException("service down"); + throw new IOException("service down"); } return new ArrayList<>(this.reports); } diff --git a/app/src/test/java/dev/wander/android/opentagviewer/util/LoadedOnceTest.java b/app/src/test/java/dev/wander/android/opentagviewer/util/LoadedOnceTest.java index f80077c2..c45d0afd 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/util/LoadedOnceTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/util/LoadedOnceTest.java @@ -12,6 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; +import java.util.concurrent.TimeUnit; /** * Building something at most once, however many threads want it. @@ -92,7 +93,7 @@ public void twentyThreadsAtOnceStillBuildItOnce() throws Exception { } go.countDown(); - assertTrue("threads did not finish", done.await(30, java.util.concurrent.TimeUnit.SECONDS)); + assertTrue("threads did not finish", done.await(30, TimeUnit.SECONDS)); assertEquals("it was built more than once under contention", 1, builds.get()); assertEquals(20, got.size()); diff --git a/app/src/test/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriterTest.java b/app/src/test/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriterTest.java index 5798b51a..8342063b 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriterTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/util/export/HistoryCsvWriterTest.java @@ -14,6 +14,7 @@ import dev.wander.android.opentagviewer.data.model.BeaconLocationReport; import dev.wander.android.opentagviewer.db.room.entity.LocationReport; +import java.time.Instant; /** * The CSV a user opens in a spreadsheet. @@ -36,7 +37,7 @@ public class HistoryCsvWriterTest { * then asserts against a date nobody intended. */ private static final String RECORDED_AT_UTC = "2026-08-15T12:34:56Z"; - private static final long RECORDED_AT = java.time.Instant.parse(RECORDED_AT_UTC).toEpochMilli(); + private static final long RECORDED_AT = Instant.parse(RECORDED_AT_UTC).toEpochMilli(); private static BeaconLocationReport.BeaconLocationReportBuilder report() { return BeaconLocationReport.builder() diff --git a/app/src/test/java/dev/wander/android/opentagviewer/util/rx/MarkerFocusTest.java b/app/src/test/java/dev/wander/android/opentagviewer/util/rx/MarkerFocusTest.java index ea018b03..cc4a6988 100644 --- a/app/src/test/java/dev/wander/android/opentagviewer/util/rx/MarkerFocusTest.java +++ b/app/src/test/java/dev/wander/android/opentagviewer/util/rx/MarkerFocusTest.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.Assert; /** * Tests for keeping the selected tag's marker on top of the ones it overlaps. @@ -181,7 +182,7 @@ public void anUnselectedTagIsBuiltAtTheDefaultLevel() { @Test public void theRaisedLevelIsAboveTheDefaultOne() { // Sounds trivial; it is the entire behaviour, and a swap would be silent. - org.junit.Assert.assertTrue(MarkerFocus.ZINDEX_TOP > MarkerFocus.ZINDEX_DEFAULT); + Assert.assertTrue(MarkerFocus.ZINDEX_TOP > MarkerFocus.ZINDEX_DEFAULT); } @Test diff --git a/scripts/check_java_imports.py b/scripts/check_java_imports.py index 1c98c745..e9d0e3ae 100644 --- a/scripts/check_java_imports.py +++ b/scripts/check_java_imports.py @@ -3,7 +3,8 @@ `android.content.Context context` where `import android.content.Context;` would do is the pattern this catches. A fully qualified name is allowed only where the short name would clash with something else in the file: an import of a different type, a type the file declares, a -class in the same package, or a `java.lang` type an import would shadow. That is the one case +class in the same package, a type nested in a supertype the project declares, or a +`java.lang` type an import would shadow. That is the one case where Java needs the long form, and it is decided here from the source alone, so this runs in well under a second with no Gradle and no classpath. @@ -56,6 +57,9 @@ class in the same package, or a `java.lang` type an import would shadow. That is PACKAGE_RE = re.compile(r"^\s*package\s+([\w.]+)\s*;", re.MULTILINE) IMPORT_RE = re.compile(r"^\s*import\s+(static\s+)?([\w.]+?)(\.\*)?\s*;", re.MULTILINE) DECLARED_RE = re.compile(r"\b(?:class|interface|enum|record)\s+([A-Z]\w*)") +# A type declaration up to its opening brace, which is where `extends` and `implements` live. +HEADER_RE = re.compile(r"\b(?:class|interface|enum|record)\s+[A-Z]\w*([^{;]*)\{") +GENERICS_RE = re.compile(r"<[^<>]*>") # A run of lowercase segments followed by the first capitalised one: the type. Anything after it # (`Map.Entry`, `View.VISIBLE`) stays as written and resolves through the import. QUALIFIED_RE = re.compile(r"(? JavaFile: has_wildcard=any(wildcard and not static for static, _, wildcard in found), ) + @property + def qualified(self) -> str: + return f"{self.package}.{self.path.stem}" if self.package else self.path.stem + + def supertypes(self) -> set[str]: + """Every type named after `extends` or `implements`, generics stripped, as written.""" + named: set[str] = set() + for header in HEADER_RE.findall(self.code): + while GENERICS_RE.search(header): + header = GENERICS_RE.sub("", header) + for clause in re.split(r"\b(?:extends|implements|permits)\b", header)[1:]: + named.update(part.strip() for part in clause.split(",") if part.strip()) + return {name for name in named if re.fullmatch(r"[\w.]+", name)} + def collect(paths: list[Path]) -> list[Path]: files: list[Path] = [] @@ -170,6 +188,39 @@ def classes_by_package(files: list[JavaFile]) -> dict[str, set[str]]: return found +def inherited_types(java: JavaFile, by_name: dict[str, JavaFile]) -> set[str]: + """Types nested in this file's supertypes, transitively, as far as the scanned sources go. + + Inside a class body a member type inherited from a supertype shadows a single-type import of + the same name - JLS 6.4.1 - so `import com.google...CameraPosition;` in a class implementing + an interface that nests its own `CameraPosition` compiles to the wrong one. Supertypes outside + the scanned sources (`View.OnClickListener` in a `View` subclass) cannot be seen from here; + the compiler catches those. + """ + found: set[str] = set() + seen: set[str] = set() + pending = [java] + while pending: + current = pending.pop() + for name in current.supertypes(): + target = _resolve(name, current, by_name) + if target is None or target.qualified in seen: + continue + seen.add(target.qualified) + found.update(target.declared) + pending.append(target) + return found + + +def _resolve(name: str, java: JavaFile, by_name: dict[str, JavaFile]) -> JavaFile | None: + top = name.split(".", 1)[0] + if "." in name and top[:1].islower(): + return by_name.get(name) + if top in java.imports: + return by_name.get(java.imports[top]) + return by_name.get(f"{java.package}.{top}" if java.package else top) + + def package_roots(files: list[JavaFile]) -> frozenset[str]: roots = set(BASE_ROOTS) for java in files: @@ -177,7 +228,8 @@ def package_roots(files: list[JavaFile]) -> frozenset[str]: return frozenset(roots) -def findings_in(java: JavaFile, same_package: set[str], roots: frozenset[str]) -> list[Finding]: +def findings_in(java: JavaFile, same_package: set[str], roots: frozenset[str], + inherited: set[str] | None = None) -> list[Finding]: candidates = list(_qualified_names(java, roots)) targets_by_simple: dict[str, set[str]] = {} for finding in candidates: @@ -185,7 +237,7 @@ def findings_in(java: JavaFile, same_package: set[str], roots: frozenset[str]) - return [ finding for finding in candidates - if not _must_stay_qualified(finding, java, same_package, targets_by_simple) + if not _must_stay_qualified(finding, java, same_package | (inherited or set()), targets_by_simple) ] @@ -255,9 +307,11 @@ def scan(paths: list[Path]) -> list[tuple[JavaFile, list[Finding]]]: files = [JavaFile.read(path) for path in collect(paths)] by_package = classes_by_package(files) roots = package_roots(files) + by_name = {java.qualified: java for java in files} result = [] for java in files: - found = findings_in(java, by_package.get(java.package, set()), roots) + found = findings_in(java, by_package.get(java.package, set()), roots, + inherited_types(java, by_name)) if found: result.append((java, found)) return result diff --git a/scripts/test/test_check_java_imports.py b/scripts/test/test_check_java_imports.py index 5ebaddad..536a4d8f 100644 --- a/scripts/test/test_check_java_imports.py +++ b/scripts/test/test_check_java_imports.py @@ -51,6 +51,33 @@ def test_reports_the_line_and_column(tmp_path: Path): assert (finding.line, finding.column) == (4, 5) +def test_keeps_a_name_a_supertype_nests(tmp_path: Path): + # GoogleMapProvider implements IMapProvider, which nests its own CameraPosition. A member type + # inherited from a supertype beats a single-type import inside the class body, so importing + # Google's CameraPosition compiles to the wrong type. + java(tmp_path, "interface Provider {\n final class Position {}\n}\n", name="Provider") + java(tmp_path, ( + "class Thing implements Runnable, Provider {\n" + " com.google.maps.Position position;\n" + "}\n" + )) + assert flagged(tmp_path) == [] + + +def test_keeps_a_name_nested_further_up_or_in_another_package(tmp_path: Path): + java(tmp_path, "public class Base {\n public enum Mode {}\n}\n", + name="Base", package="dev.base") + java(tmp_path, "import dev.base.Base;\n\nclass Middle extends Base {}\n", name="Middle") + java(tmp_path, "class Thing extends Middle {\n com.vendor.Mode mode;\n}\n") + assert flagged(tmp_path) == [] + + +def test_a_supertype_nesting_something_else_changes_nothing(tmp_path: Path): + java(tmp_path, "interface Provider {\n final class Position {}\n}\n", name="Provider") + java(tmp_path, "class Thing implements Provider {\n android.content.Context context;\n}\n") + assert flagged(tmp_path) == ["android.content.Context"] + + # --- what it ignores ------------------------------------------------------------------------- def test_ignores_comments_strings_and_text_blocks(tmp_path: Path):