Skip to content

Commit d6bf017

Browse files
authored
feat(ui/sim): change SIM icon colors in dark mode (#96)
* Merge December 2025 QPR2 Patches (android-16.0.0_r4) (#91) * SettingsLib: bump to `android-16.0.0_r4` tag * SettingsLib: bump `minSdk` version to 23 https://android.googlesource.com/platform/frameworks/base/+/40ba4786d08b0618fe77ce7435116dbd41f2fb12 ("Update SettingsLib/DataStore min_sdk_version to 23.") * CollapsingToolbarBaseActivity: add APIs for action button https://android.googlesource.com/platform/frameworks/base/+/7e207744232a1a9f22ba6edafe26d0e575290090 ("Added button in CollapsingToolbar") * CollapsingToolbarBaseActivity: add APIs for trailing buttons (primary/secondary/action) https://android.googlesource.com/platform/frameworks/base/+/5deb69739d62f225dd8ba15f0987250d107a1145 ("[Expressive design] Add trailing buttons to CollapsingToolbar") * CollapsingToolbarBaseActivity: add API to toggle Toolbar trailing buttons Now we have a trailing buttons view holding the action, primary & secondary buttons not just the action button, so refactoring is needed. * CollapsingToolbarBaseActivity: collapse toolbar when hosting multiple fragments https://android.googlesource.com/platform/frameworks/base/+/e0bf8cb02e2bbab0dea2ec1d1153b1b8818b6677 ("Collapse toolbar when hosting multiple fragments") * SettingsLib/CollapsingToolbarBaseActivity: fix compilation issues * ScrollableToolbarItemLayout is a Kotlin class. --- /home/runner/work/7SIM/7SIM/SettingsLib/fwb/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java:34: error: cannot find symbol import com.android.settingslib.collapsingtoolbar.widget.ScrollableToolbarItemLayout; ^ symbol: class ScrollableToolbarItemLayout location: package com.android.settingslib.collapsingtoolbar.widget * Bump `Room` to `v2.8.0-alpha01` (`android-16.0.0_r4` tag) * Bump `Material3` to `v1.14.0-alpha02` (`android-16.0.0_r4` tag) * Bump AndroidX deps to `android-16.0.0_r4` tag * CollapsingToolbarBaseActivity: fix compilations errors /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/ui/components/CollapsingToolbarBaseActivity.java:87: error: cannot find symbol .map((v) -> (View) v.getParent()); ^ symbol: method getParent() location: variable v of type Object * SimPinFeederTest: increase task timeout in CI environment to 5 seconds GitHub Actions runners are very slow sometimes, which timeouts our tests, probably due to JVM waiting for resources from the OS such as processor. We already faced a similar issue in 1b353b0 and 6946b97, so increasing timeout a bit should resolve the issue. To maintain speed when testing locally, we still want to timeout after 3 seconds. org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in com.github.iusmac.sevensim.telephony.SimPinFeederTest was not fulfilled within 3 seconds. at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167) at org.awaitility.core.CallableCondition.await(CallableCondition.java:78) at org.awaitility.core.CallableCondition.await(CallableCondition.java:26) at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160) at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129) at com.github.iusmac.sevensim.telephony.SimPinFeederTest.assertTaskInState(SimPinFeederTest.java:879) at com.github.iusmac.sevensim.telephony.SimPinFeederTest.test_ShouldUnlockWhenAttemptsRemainingIsAtLeastThree_SinceS(SimPinFeederTest.java:274) --- Another symptom that GitHub Actions runners are very slow, is that we even got a NPE in the SimPinFeeder thread that runs in parallel, because the main/test thread already terminated (due to a fixed timeout), and recycled objects that aren't reachable anymore. Exception in thread "Thread-71" java.lang.NullPointerException: Cannot invoke "android.telephony.TelephonyManager.getSimState()" because "this.mTelephonyManager" is null at com.github.iusmac.sevensim.telephony.SimPinFeeder$SimCard.toString(SimPinFeeder.java:325) at java.base/java.util.Formatter$FormatSpecifier.printString(Formatter.java:3158) at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3036) at java.base/java.util.Formatter.format(Formatter.java:2791) at java.base/java.util.Formatter.format(Formatter.java:2728) at java.base/java.lang.String.format(String.java:4431) at com.github.iusmac.sevensim.Logger.format(Logger.java:112) at com.github.iusmac.sevensim.Logger.v(Logger.java:56) at com.github.iusmac.sevensim.telephony.SimPinFeeder.run(SimPinFeeder.java:99) * SimPinFeederTest: fix compilation errors /home/runner/work/7SIM/7SIM/tests/test/java/com/github/iusmac/sevensim/telephony/SimPinFeederTest.java:120: error: element value must be a constant expression @test(timeout = TASK_WAIT_TIMEOUT_MILLIS) * Revert "fix(ui/PrimarySwitchPreference): address extra divider empty space [Expressive Design] (#79)" This reverts commit 60c5164. * fix(ui/PrimarySwitchPreference): address extra divider empty space [Expressive Design] In the expressive theme introduced in Android 16 (Baklava), the switch preference used in the SIM list activity contains additional space at the beginning of the parent layout causing the title (e.g., SIM name) and summary (e.g., next upcoming schedule) text views that precede it to wrap earlier. * Update golden screenshots for Roborazzi tests * test(SimListActivityTest): preemptively make background restricted banner visible (#94) We make the "Background usage required" banner preference visible in SimListFragment's onResume() method, but *sometimes* we get an ArrayIndexOutOfBoundsException from SettingsPreferenceGroupAdapter, which serves as a preferences decorator that updates their drawable's background round corners radius when expressive theme in enabled. The exception raises because the update preference list runnable is posted on the main thread, so that RecyclerView runs first. This is right to do and this issue cannot be reproduced when running on a real device, but it happens only in Robolectric tests, probably due to the paused looper and how Activity lifecycle chain is processed. (Could this be a bug in Robolectric?) As a workaround, we'll preemptively make the banner visible during setup in SimListFragment's onViewCreated() method, so that it's present in the preference's adapter upon binding its view holder, which will pass through the SettingsPreferenceGroupAdapter decorator first and avoid the ArrayIndexOutOfBoundsException. --- java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 6 at com.android.settingslib.widget.SettingsPreferenceGroupAdapter.updateBackground(SettingsPreferenceGroupAdapter.kt:185) at com.android.settingslib.widget.SettingsPreferenceGroupAdapter.onBindViewHolder(SettingsPreferenceGroupAdapter.kt:96) at com.android.settingslib.widget.SettingsPreferenceGroupAdapter.onBindViewHolder(SettingsPreferenceGroupAdapter.kt:46) at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7846) at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7953) at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6742) at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:7013) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6853) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6849) at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2422) at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1722) at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1682) at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:747) at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4737) at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4459) at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:5011) [...] at org.robolectric.shadows.ShadowPausedLooper$IdlingRunnable.doRun(ShadowPausedLooper.java:677) at org.robolectric.shadows.ShadowPausedLooper$ControlRunnable.run(ShadowPausedLooper.java:599) at org.robolectric.shadows.ShadowPausedLooper$HandlerExecutor.execute(ShadowPausedLooper.java:849) at org.robolectric.shadows.ShadowPausedLooper.executeOnLooper(ShadowPausedLooper.java:765) at org.robolectric.shadows.ShadowPausedLooper.idle(ShadowPausedLooper.java:109) at org.robolectric.shadows.ShadowPausedLooper.idleIfPaused(ShadowPausedLooper.java:193) at org.robolectric.android.controller.ActivityController.visible(ActivityController.java:233) at org.robolectric.android.internal.RoboMonitoringInstrumentation.lambda$startActivitySyncInternal$0(RoboMonitoringInstrumentation.java:143) at org.robolectric.shadows.ShadowInstrumentation.runOnMainSyncNoIdle(ShadowInstrumentation.java:1183) at org.robolectric.android.internal.RoboMonitoringInstrumentation.startActivitySyncInternal(RoboMonitoringInstrumentation.java:125) at org.robolectric.android.internal.LocalActivityInvoker.startActivity(LocalActivityInvoker.java:38) at org.robolectric.android.internal.LocalActivityInvoker.startActivity(LocalActivityInvoker.java:43) at androidx.test.core.app.ActivityScenario.launchInternal(ActivityScenario.java:367) at androidx.test.core.app.ActivityScenario.launch(ActivityScenario.java:237) at com.github.iusmac.sevensim.ui.sim.SimListActivityTestKt.onActivity(SimListActivityTest.kt:992) at com.github.iusmac.sevensim.ui.sim.SimListActivityTestKt.onActivity$default(SimListActivityTest.kt:942) at com.github.iusmac.sevensim.ui.sim.SimListActivityTest$BackgroundRestrictedBanner.test full view when background usage restricted(SimListActivityTest.kt:218) * l10n(korean): fix formatting argument types incomplete or inconsistent lint warning (#95) Addressing the incomplete commit 0419351 ("chore(SubscriptionSchedulerSummaryBuilder): add support for custom date-time separator"). --- /home/runner/work/7SIM/7SIM/res/values-ko-rKR/strings.xml:28: Warning: Inconsistent number of arguments in formatting string scheduler_start_time_custom_summary; found both 1 here and 2 in values/strings.xml [StringFormatCount] <string name="scheduler_start_time_custom_summary">"<![CDATA[<b><xliff:g name="date_time" example="today, 6 AM">%1$s</xliff:g></b>]]>에 자동으로 켜짐"</string> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/runner/work/7SIM/7SIM/res/values/strings.xml:54: Conflicting number of arguments (2) here /home/runner/work/7SIM/7SIM/res/values-ko-rKR/strings.xml:29: Warning: Inconsistent number of arguments in formatting string scheduler_end_time_custom_summary; found both 1 here and 2 in values/strings.xml [StringFormatCount] <string name="scheduler_end_time_custom_summary">"<![CDATA[<b><xliff:g name="date_time" example="today, 10 PM">%1$s</xliff:g></b>]]>에 자동으로 꺼짐"</string> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/runner/work/7SIM/7SIM/res/values/strings.xml:55: Conflicting number of arguments (2) here Explanation for issues of type "StringFormatCount": When a formatted string takes arguments, it usually needs to reference the same arguments in all translations (or all arguments if there are no translations. There are cases where this is not the case, so this issue is a warning rather than an error by default. However, this usually happens when a language is not translated or updated correctly. Signed-off-by: iusmac <iusico.maxim@libero.it> * build(deps): bump Gradle and update deps (#93) * Bump Gradle to `v8.14.3` * Bump AGP plugin to `v8.13.2` * Extract AGP version from Toml file for `com.android.settings` plugin Unfortunately, we cannot use "alias()" in the "plugins {}" block to reuse the AGP version from gradle/libs.versions.toml file. Getting an error instead (happens only in settings.gradle script): only alias(libs.plugins.someAlias) plugin identifiers where libs is a valid version catalog * Suppress false positive `GradlePluginVersion` lint check /home/runner/work/7SIM/7SIM/settings.gradle:22: Error: You must use a newer version of the Android Gradle plugin. The minimum supported version is 4.0.0 and the recommended version is 8.13.2 [GradlePluginVersion] id 'com.android.settings' version "${apgVersion}" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Explanation for issues of type "GradlePluginVersion": Not all versions of the Android Gradle plugin are compatible with all versions of the SDK. If you update your tools, or if you are trying to open a project that was built with an old version of the tools, you may need to update your plugin version number. * Bump Roborazzi to `v1.54.0` * Downgrade AGP to `v8.11.2` The tests fail because our static BroadcastReceiver (AlarmReceiver) is not listed anymore in ShadowApplication.getRegisteredReceivers(). java.util.NoSuchElementException: No value present at java.base/java.util.Optional.get(Optional.java:143) at com.github.iusmac.sevensim.scheduler.AlarmReceiverTest.setUp(AlarmReceiverTest.java:97) After manual investigation, it seems that Robolectric can't load static <receiver> components from our AndroidManifest.xml file starting from AGP 8.12 or newer. * Fix typo in `settings.gradle` * Bump Mockito to `v5.21.0` * Bump Kover coverage library to `v0.9.4` * Bump `Error Prone` library to `v2.45.0` * Resolve `Error Prone` warnings/errors /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/Logger.java:111: warning: [AnnotateFormatMethod] This method uses a pair of parameters as a format string and its arguments, but the enclosing method wasn't annotated. Doing so gives compile-time rather than run-time protection against malformed format strings.     private static String format(String message, Object... args) {                           ^     (see https://errorprone.info/bugpattern/AnnotateFormatMethod) /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/SystemBroadcastReceiver.java:51: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d("onReceive() : intent=" + intent); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("onReceive() : intent=%s", intent);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/DirectBootAwareBroadcastReceiver.java:46: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d("onReceive() : intent=" + intent); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("onReceive() : intent=%s", intent);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/Subscriptions.java:76: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.v("onReceive() : intent=" + intent); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.v("onReceive() : intent=%s", intent);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/SubscriptionController.java:64: error: [FormatStringAnnotation] Local format string variables must only be assigned to compile time constant values. Invalid format string assignment: final String logPrefix = String.format(Locale.getDefault(), "setUiccApplicationsEnabled(subId=%d,enabled=%s)", subId, enabled) mLogger.d(logPrefix); ^ (see https://errorprone.info/bugpattern/FormatStringAnnotation) /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/SubscriptionController.java:69: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.e(logPrefix + " Aborting due to missing subscription."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.e("%s Aborting due to missing subscription.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:116: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.w(logPrefix + "Aborting due to Airplane mode."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.w("%sAborting due to Airplane mode.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:125: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d(logPrefix + "In sync block."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("%sIn sync block.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:131: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.e(logPrefix + "Aborting due to missing subscription."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.e("%sAborting due to missing subscription.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:137: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.w(logPrefix + "Already in state."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.w("%sAlready in state.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:202: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.w(logPrefix + "Acquire wait interrupted."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.w("%sAcquire wait interrupted.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:297: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. default -> mLogger.e(logPrefix + ". Unexpected resCode."); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'default -> mLogger.e("%s. Unexpected resCode.", logPrefix);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/telephony/TelephonyController.java:359: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d(logPrefix + ", requestFailed=%s,shouldNotifyAllListeners=%s", requestFailed, ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("%s, requestFailed=%s,shouldNotifyAllListeners=%s", logPrefix, requestFailed, shouldNotifyAllListeners);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/ui/scheduler/SchedulerViewModel.java:428: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d("onReceive() : intent=" + intent); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("onReceive() : intent=%s", intent);'? /home/runner/work/7SIM/7SIM/src/com/github/iusmac/sevensim/ui/sim/SimListActivity.java:154: warning: [FormatStringShouldUsePlaceholders] Using a format string avoids string concatenation in the common case. mLogger.d("onReceive() : intent=" + intent); ^ (see https://errorprone.info/bugpattern/FormatStringShouldUsePlaceholders) Did you mean 'mLogger.d("onReceive() : intent=%s", intent);'? * feat(ui/sim): change SIM icon colors in dark mode Add a color palette to map SIM icon colors in light mode to dark mode as in SIM rename dialog within builtin Settings app. Note that, Android 10/11 (SDK 29/30) didn't offer support for colors in dark mode, so we'd added them (material 300 code). Starting from Android 12 (SDK 31), we got support for colors in dark mode and a new color palette in SIM rename dialog within builtin Settings, but the Android OS framework still used the legacy colors until Android 14 (SDK 34), so we want to support them both. * UiUtils: add missing tests * SimListActivityTest: test dark sim color array size matches light sim color array size * SimListActivityTest: test sim icon color palette matches system color palette * SimListActivityTest: test should use dark sim color palette whenever possible * Update golden screenshots for Roborazzi * SimListActivityTest: use system SIM color color palette directly For better test robustness, it's better to rely on framework constants than on our copy of the those constants. Note that, now that we use Android OS internal sim_colors array, we need to reorder our SIM color int array copy for SDK 34 (Android S+), so that the int values match 1-1 in order for the tests to pass. * SimListActivityTest: fix test java.util.NoSuchElementException: Array is empty. at kotlin.collections.ArraysKt___ArraysKt.first(_Arrays.kt:1073) at com.github.iusmac.sevensim.ui.sim.SimListActivityTest$SimEntries.test should use dark sim color palette whenever possible(SimListActivityTest.kt:837) * Update golden screenshots for Roborazzi * SubscriptionSchedulerSummaryBuilderTest: increase task timeout in CI environment to 5 seconds See 3b44045 for reference. --- org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in com.github.iusmac.sevensim.scheduler.SubscriptionSchedulerSummaryBuilderTest was not fulfilled within 3 seconds. at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167) at org.awaitility.core.CallableCondition.await(CallableCondition.java:78) at org.awaitility.core.CallableCondition.await(CallableCondition.java:26) at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160) at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129) at com.github.iusmac.sevensim.scheduler.SubscriptionSchedulerSummaryBuilderTest.buildNextUpcomingSubscriptionScheduleSummary(SubscriptionSchedulerSummaryBuilderTest.java:309) at com.github.iusmac.sevensim.scheduler.SubscriptionSchedulerSummaryBuilderTest.buildNextUpcomingSubscriptionScheduleSummary(SubscriptionSchedulerSummaryBuilderTest.java:295) at com.github.iusmac.sevensim.scheduler.SubscriptionSchedulerSummaryBuilderTest.buildNextUpcomingSubscriptionScheduleSummary_LocaleChange(SubscriptionSchedulerSummaryBuilderTest.java:216) --------- Signed-off-by: iusmac <iusico.maxim@libero.it>
1 parent 4eea8dc commit d6bf017

File tree

58 files changed

+239
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+239
-2
lines changed

res/values-v31/arrays.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Taken from frameworks/base/core/res/res/values/arrays.xml -->
4+
<array name="sim_colors">
5+
<item>@color/Teal_700</item>
6+
<item>@color/Blue_700</item>
7+
<item>@color/Indigo_700</item>
8+
<item>@color/Purple_700</item>
9+
<item>@color/Pink_700</item>
10+
<item>@color/Red_700</item>
11+
<!-- Legacy (Android Q) color palette still exists in Android OS, but starting from
12+
Android S, there's a new color palette available in SIM rename dialog. -->
13+
<item>@color/SIM_color_cyan</item>
14+
<item>@color/SIM_color_blue</item>
15+
<item>@color/SIM_color_green</item>
16+
<item>@color/SIM_color_purple</item>
17+
<item>@color/SIM_color_pink</item>
18+
<item>@color/SIM_color_orange</item>
19+
</array>
20+
21+
<array name="sim_dark_mode_colors">
22+
<item>@color/Teal_300</item>
23+
<item>@color/Blue_300</item>
24+
<item>@color/Indigo_300</item>
25+
<item>@color/Purple_300</item>
26+
<item>@color/Pink_300</item>
27+
<item>@color/Red_300</item>
28+
<item>@color/SIM_dark_mode_color_cyan</item>
29+
<item>@color/SIM_dark_mode_color_blue</item>
30+
<item>@color/SIM_dark_mode_color_green</item>
31+
<item>@color/SIM_dark_mode_color_purple</item>
32+
<item>@color/SIM_dark_mode_color_pink</item>
33+
<item>@color/SIM_dark_mode_color_orange</item>
34+
</array>
35+
</resources>

res/values-v31/colors.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,19 @@
77
<color name="schedule_card_day_selected_background_color">@android:color/system_neutral2_600</color>
88
<color name="schedule_card_day_selected_text_color">@android:color/system_neutral2_50</color>
99
<color name="schedule_card_hairline_color">@android:color/transparent</color>
10+
11+
<!-- Multi-sim sim colors. Keep in sync with frameworks/base/core/res/res/values/colors.xml
12+
DO NOT CHANGE HERE! Create a "colors.xml" per SDK when need to sync! -->
13+
<color name="SIM_color_cyan">#ff006D74</color><!-- Material Custom Cyan -->
14+
<color name="SIM_dark_mode_color_cyan">#ff4DD0E1</color><!-- Material Cyan 300 -->
15+
<color name="SIM_color_blue">#ff185ABC</color><!-- Material Blue 800 -->
16+
<color name="SIM_dark_mode_color_blue">#ff8AB4F8</color><!-- Material Blue 300 -->
17+
<color name="SIM_color_green">#ff137333</color><!-- Material Green 800 -->
18+
<color name="SIM_dark_mode_color_green">#ff81C995</color><!-- Material Green 300 -->
19+
<color name="SIM_color_purple">#ff7627bb</color><!-- Material Purple 800 -->
20+
<color name="SIM_dark_mode_color_purple">#ffC58AF9</color><!-- Material Purple 300 -->
21+
<color name="SIM_color_pink">#ffb80672</color><!-- Material Pink 800 -->
22+
<color name="SIM_dark_mode_color_pink">#ffff8bcb</color><!-- Material Pink 300 -->
23+
<color name="SIM_color_orange">#ff995400</color><!-- Material Custom Orange -->
24+
<color name="SIM_dark_mode_color_orange">#fffcad70</color><!-- Material Orange 300 -->
1025
</resources>

res/values-v34/arrays.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Keep in sync with frameworks/base/core/res/res/values/arrays.xml
4+
DO NOT CHANGE HERE! Create an "arrays.xml" per SDK when need to sync! -->
5+
<array name="sim_colors">
6+
<item>@color/SIM_color_cyan</item>
7+
<item>@color/SIM_color_blue</item>
8+
<item>@color/SIM_color_green</item>
9+
<item>@color/SIM_color_purple</item>
10+
<item>@color/SIM_color_pink</item>
11+
<item>@color/SIM_color_orange</item>
12+
</array>
13+
14+
<array name="sim_dark_mode_colors">
15+
<item>@color/SIM_dark_mode_color_cyan</item>
16+
<item>@color/SIM_dark_mode_color_blue</item>
17+
<item>@color/SIM_dark_mode_color_green</item>
18+
<item>@color/SIM_dark_mode_color_purple</item>
19+
<item>@color/SIM_dark_mode_color_pink</item>
20+
<item>@color/SIM_dark_mode_color_orange</item>
21+
</array>
22+
</resources>

res/values/arrays.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Taken from frameworks/base/core/res/res/values/arrays.xml -->
4+
<array name="sim_colors">
5+
<item>@color/Teal_700</item>
6+
<item>@color/Blue_700</item>
7+
<item>@color/Indigo_700</item>
8+
<item>@color/Purple_700</item>
9+
<item>@color/Pink_700</item>
10+
<item>@color/Red_700</item>
11+
</array>
12+
13+
<array name="sim_dark_mode_colors">
14+
<item>@color/Teal_300</item>
15+
<item>@color/Blue_300</item>
16+
<item>@color/Indigo_300</item>
17+
<item>@color/Purple_300</item>
18+
<item>@color/Pink_300</item>
19+
<item>@color/Red_300</item>
20+
</array>
21+
</resources>

res/values/colors.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,18 @@
1313
<color name="schedule_card_day_unselected_stroke_color">?android:attr/colorControlHighlight</color>
1414
<color name="schedule_card_arrow_background_color">@android:color/transparent</color>
1515
<color name="schedule_card_hairline_color">#28000000</color>
16+
17+
<!-- Multi-sim sim colors. Taken from frameworks/base/core/res/res/values/colors.xml -->
18+
<color name="Teal_700">#ff00796b</color>
19+
<color name="Teal_300">#ff4db6ac</color>
20+
<color name="Blue_700">#ff3367d6</color>
21+
<color name="Blue_300">#ff64b5f6</color>
22+
<color name="Indigo_700">#ff303f9f</color>
23+
<color name="Indigo_300">#ff7986cb</color>
24+
<color name="Purple_700">#ff7b1fa2</color>
25+
<color name="Purple_300">#ffba68c8</color>
26+
<color name="Pink_700">#ffc2185b</color>
27+
<color name="Pink_300">#fff06292</color>
28+
<color name="Red_700">#ffc53929</color>
29+
<color name="Red_300">#ffe57373</color>
1630
</resources>

src/com/github/iusmac/sevensim/ui/UiUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ public static boolean isLandscape(final @NonNull Context context) {
342342
Configuration.ORIENTATION_LANDSCAPE;
343343
}
344344

345+
/**
346+
* Check whether currently the app is displayed in dark mode or not.
347+
*
348+
* @param context The {@link Context} to access resources.
349+
*/
350+
public static boolean isDarkMode(final @NonNull Context context) {
351+
return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
352+
== Configuration.UI_MODE_NIGHT_YES;
353+
}
354+
345355
/** Do not initialize. */
346356
private UiUtils() {}
347357
}

src/com/github/iusmac/sevensim/ui/sim/SimListFragment.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.Intent;
66
import android.content.SharedPreferences;
77
import android.os.Bundle;
8+
import android.util.SparseIntArray;
89
import android.view.View;
910

1011
import androidx.collection.SparseArrayCompat;
@@ -41,6 +42,7 @@ public final class SimListFragment extends Hilt_SimListFragment {
4142
@Inject
4243
Lazy<ApplicationInfo> mApplicationInfoLazy;
4344

45+
private SparseIntArray mLightDarkSimIconColorPalette;
4446
private SimListViewModel mViewModel;
4547

4648
private PreferenceCategory mSimPreferenceCategory;
@@ -49,6 +51,14 @@ public final class SimListFragment extends Hilt_SimListFragment {
4951

5052
private BannerMessagePreference mBackgroundRestrictedBanner;
5153

54+
@Override
55+
public void onAttach(final Context context) {
56+
super.onAttach(context);
57+
58+
mLightDarkSimIconColorPalette = UiUtils.isDarkMode(context) ?
59+
getLightDarkSimIconColorPalette(context) : new SparseIntArray();
60+
}
61+
5262
@Override
5363
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
5464
mViewModel = ((SimListActivity) requireActivity()).getViewModel();
@@ -160,7 +170,9 @@ private void updateSimPreferenceList(
160170
}
161171
pref.setOrder(i);
162172
pref.setIcon(UiUtils.createTintedDrawable(context, R.drawable.ic_sim,
163-
sub.getIconTint()));
173+
// Whenever possible, use the equivalent dark icon color from the color
174+
// palette defined in Android OS, otherwise default to use the light color
175+
mLightDarkSimIconColorPalette.get(sub.getIconTint(), sub.getIconTint())));
164176
pref.setTitle(sub.getSimName());
165177
pref.setSummary(simEntry.getNextUpcomingScheduleSummary());
166178
pref.setChecked(sub.isSimEnabled());
@@ -180,4 +192,15 @@ private void updateSimPreferenceList(
180192
// Show a placeholder message if no SIM cards
181193
mNoSimPreference.setVisible(simEntries.isEmpty());
182194
}
195+
196+
private static SparseIntArray getLightDarkSimIconColorPalette(final Context context) {
197+
final var res = context.getResources();
198+
final var lightColorInts = res.getIntArray(R.array.sim_colors);
199+
final var darkColorInts = res.getIntArray(R.array.sim_dark_mode_colors);
200+
final var palette = new SparseIntArray(lightColorInts.length);
201+
for (int i = 0; i < lightColorInts.length; i++) {
202+
palette.put(lightColorInts[i], darkColorInts[i]);
203+
}
204+
return palette;
205+
}
183206
}

tests/test/java/com/github/iusmac/sevensim/scheduler/SubscriptionSchedulerSummaryBuilderTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
@HiltAndroidTest
3939
@RunWith(RobolectricTestRunner.class)
4040
public class SubscriptionSchedulerSummaryBuilderTest extends MockitoHiltAndroidTestBase {
41+
// Increase await timeout in CI environments to account for server load, which may increase the
42+
// time needed to pick up a new thread
43+
private static final Duration DEFAULT_AWAIT_TIMEOUT_DURATION =
44+
Duration.ofSeconds(Optional.ofNullable(System.getenv("CI"))
45+
.filter((v) -> v.equals("true"))
46+
.map((v) -> 5L)
47+
.orElse(3L));
4148
private static final LocalDateTime NOW = LocalDateTime.of(2007, 1, 1, 13, 0);
4249

4350
private final Subscription mSubscription = new Subscription();
@@ -304,7 +311,7 @@ private CharSequence buildNextUpcomingSubscriptionScheduleSummary(
304311
await()
305312
.dontCatchUncaughtExceptions()
306313
.pollInSameThread()
307-
.atMost(Duration.ofSeconds(3))
314+
.atMost(DEFAULT_AWAIT_TIMEOUT_DURATION)
308315
.pollInterval(Duration.ofMillis(50))
309316
.until(future::isDone);
310317

tests/test/java/com/github/iusmac/sevensim/ui/UiUtilsTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ public void test_isLandscape_WhenLandscape() {
379379
assertTrue(UiUtils.isLandscape(mApplicationContext));
380380
}
381381

382+
@Test
383+
public void test_isDarkMode_WhenLightMode() {
384+
assertFalse(UiUtils.isDarkMode(mApplicationContext));
385+
}
386+
387+
@Test
388+
@Config(qualifiers = "night")
389+
public void test_isDarkMode_WhenDarkMode() {
390+
assertTrue(UiUtils.isDarkMode(mApplicationContext));
391+
}
392+
382393
private static RecyclerView.Adapter<? extends RecyclerView.ViewHolder>
383394
buildAdapterWithItemCount(final int itemCount) {
384395

tests/test/java/com/github/iusmac/sevensim/ui/sim/SimListActivityTest.kt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,85 @@ class SimListActivityTest {
803803
}
804804
}
805805

806+
@Test
807+
@Config(minSdk = Q)
808+
fun `test dark sim color array size matches light sim color array size`() {
809+
with(mApplicationContext.getResources()) {
810+
val simColorInts = getIntArray(R.array.sim_colors)
811+
val simDarkModeColorInts = getIntArray(R.array.sim_dark_mode_colors)
812+
assertThat(simDarkModeColorInts.toTypedArray(), arrayWithSize(simColorInts.size))
813+
}
814+
}
815+
816+
@Test
817+
fun `test sim icon color palette matches system color palette`() {
818+
val simColorInts = mApplicationContext.getResources().getIntArray(R.array.sim_colors)
819+
assertThat(systemSimColorInts, `is`(simColorInts))
820+
}
821+
822+
@Test
823+
@Config(minSdk = Q)
824+
fun `test should use dark sim color palette whenever possible`() {
825+
shadowOf(mSubscriptionManager).setAvailableSubscriptionInfos(
826+
SubscriptionInfoBuilder.newBuilder().apply {
827+
setId(1)
828+
setSimSlotIndex(0)
829+
setDisplayName("SIM 1")
830+
setIconTint(Color.BLUE)
831+
}.buildSubscriptionInfo(),
832+
SubscriptionInfoBuilder.newBuilder().apply {
833+
setId(2)
834+
setSimSlotIndex(1)
835+
setDisplayName("SIM 2")
836+
setIconTint(systemSimColorInts.first())
837+
}.buildSubscriptionInfo(),
838+
SubscriptionInfoBuilder.newBuilder().apply {
839+
setId(3)
840+
setSimSlotIndex(2)
841+
setDisplayName("SIM 3")
842+
setIconTint(systemSimColorInts.last())
843+
}.buildSubscriptionInfo()
844+
)
845+
shadowOf(mTelephonyManager).apply {
846+
setActiveModemCount(3)
847+
setPhoneCount(3)
848+
}
849+
val captureRoboImages = { ->
850+
// Ensure ViewModel finished updating UI
851+
waitActivityWorkerThreadUntilIdle()
852+
shadowOf(Looper.getMainLooper()).idle()
853+
onSimEntryAt(0).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
854+
onSimEntryAt(1).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
855+
onSimEntryAt(2).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
856+
}
857+
onActivity {
858+
captureRoboImages()
859+
// Switch to dark mode (activity will be recreated)
860+
RuntimeEnvironment.setQualifiers("+night")
861+
captureRoboImages()
862+
}
863+
}
864+
865+
private val systemSimColorInts by lazy(LazyThreadSafetyMode.NONE) {
866+
with(mApplicationContext) {
867+
getResources().run {
868+
// NOTE: since we're compiling against Robolectric's android-all.jar, we can
869+
// statically access its generated internal resource Ids only when the emulated
870+
// Android SDK level matches targetSdk version, otherwise (almost always) we'll
871+
// get a Resources.NotFoundException. Sometimes, the generated resource Id
872+
// exists but getIntArray returns an empty array or even a wrong array due a
873+
// collision with a resource different from our resource
874+
if (getApplicationInfo().targetSdkVersion === Build.VERSION.SDK_INT) {
875+
getIntArray(com.android.internal.R.array.sim_colors)
876+
} else { // silver bullet (slower) via reflection to cover all other cases
877+
val androidInternalR = Class.forName("com.android.internal.R\$array")
878+
val resId = androidInternalR.getField("sim_colors").getInt(null)
879+
getIntArray(resId)
880+
}
881+
}
882+
}
883+
}
884+
806885
@After
807886
fun tearDown() {
808887
// Wait for the ViewModel to complete before exiting the test, otherwise the database

0 commit comments

Comments
 (0)