Skip to content
Open
35 changes: 35 additions & 0 deletions res/values-v31/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Taken from frameworks/base/core/res/res/values/arrays.xml -->
<array name="sim_colors">
<item>@color/Teal_700</item>
<item>@color/Blue_700</item>
<item>@color/Indigo_700</item>
<item>@color/Purple_700</item>
<item>@color/Pink_700</item>
<item>@color/Red_700</item>
<!-- Legacy (Android Q) color palette still exists in Android OS, but starting from
Android S, there's a new color palette available in SIM rename dialog. -->
<item>@color/SIM_color_cyan</item>
<item>@color/SIM_color_blue</item>
<item>@color/SIM_color_green</item>
<item>@color/SIM_color_purple</item>
<item>@color/SIM_color_pink</item>
<item>@color/SIM_color_orange</item>
</array>

<array name="sim_dark_mode_colors">
<item>@color/Teal_300</item>
<item>@color/Blue_300</item>
<item>@color/Indigo_300</item>
<item>@color/Purple_300</item>
<item>@color/Pink_300</item>
<item>@color/Red_300</item>
<item>@color/SIM_dark_mode_color_cyan</item>
<item>@color/SIM_dark_mode_color_blue</item>
<item>@color/SIM_dark_mode_color_green</item>
<item>@color/SIM_dark_mode_color_purple</item>
<item>@color/SIM_dark_mode_color_pink</item>
<item>@color/SIM_dark_mode_color_orange</item>
</array>
</resources>
15 changes: 15 additions & 0 deletions res/values-v31/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,19 @@
<color name="schedule_card_day_selected_background_color">@android:color/system_neutral2_600</color>
<color name="schedule_card_day_selected_text_color">@android:color/system_neutral2_50</color>
<color name="schedule_card_hairline_color">@android:color/transparent</color>

<!-- Multi-sim sim colors. Keep in sync with frameworks/base/core/res/res/values/colors.xml
DO NOT CHANGE HERE! Create a "colors.xml" per SDK when need to sync! -->
<color name="SIM_color_cyan">#ff006D74</color><!-- Material Custom Cyan -->
<color name="SIM_dark_mode_color_cyan">#ff4DD0E1</color><!-- Material Cyan 300 -->
<color name="SIM_color_blue">#ff185ABC</color><!-- Material Blue 800 -->
<color name="SIM_dark_mode_color_blue">#ff8AB4F8</color><!-- Material Blue 300 -->
<color name="SIM_color_green">#ff137333</color><!-- Material Green 800 -->
<color name="SIM_dark_mode_color_green">#ff81C995</color><!-- Material Green 300 -->
<color name="SIM_color_purple">#ff7627bb</color><!-- Material Purple 800 -->
<color name="SIM_dark_mode_color_purple">#ffC58AF9</color><!-- Material Purple 300 -->
<color name="SIM_color_pink">#ffb80672</color><!-- Material Pink 800 -->
<color name="SIM_dark_mode_color_pink">#ffff8bcb</color><!-- Material Pink 300 -->
<color name="SIM_color_orange">#ff995400</color><!-- Material Custom Orange -->
<color name="SIM_dark_mode_color_orange">#fffcad70</color><!-- Material Orange 300 -->
</resources>
22 changes: 22 additions & 0 deletions res/values-v34/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Keep in sync with frameworks/base/core/res/res/values/arrays.xml
DO NOT CHANGE HERE! Create an "arrays.xml" per SDK when need to sync! -->
<array name="sim_colors">
<item>@color/SIM_color_cyan</item>
<item>@color/SIM_color_blue</item>
<item>@color/SIM_color_green</item>
<item>@color/SIM_color_purple</item>
<item>@color/SIM_color_pink</item>
<item>@color/SIM_color_orange</item>
</array>

<array name="sim_dark_mode_colors">
<item>@color/SIM_dark_mode_color_cyan</item>
<item>@color/SIM_dark_mode_color_blue</item>
<item>@color/SIM_dark_mode_color_green</item>
<item>@color/SIM_dark_mode_color_purple</item>
<item>@color/SIM_dark_mode_color_pink</item>
<item>@color/SIM_dark_mode_color_orange</item>
</array>
</resources>
21 changes: 21 additions & 0 deletions res/values/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Taken from frameworks/base/core/res/res/values/arrays.xml -->
<array name="sim_colors">
<item>@color/Teal_700</item>
<item>@color/Blue_700</item>
<item>@color/Indigo_700</item>
<item>@color/Purple_700</item>
<item>@color/Pink_700</item>
<item>@color/Red_700</item>
</array>

<array name="sim_dark_mode_colors">
<item>@color/Teal_300</item>
<item>@color/Blue_300</item>
<item>@color/Indigo_300</item>
<item>@color/Purple_300</item>
<item>@color/Pink_300</item>
<item>@color/Red_300</item>
</array>
</resources>
14 changes: 14 additions & 0 deletions res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,18 @@
<color name="schedule_card_day_unselected_stroke_color">?android:attr/colorControlHighlight</color>
<color name="schedule_card_arrow_background_color">@android:color/transparent</color>
<color name="schedule_card_hairline_color">#28000000</color>

<!-- Multi-sim sim colors. Taken from frameworks/base/core/res/res/values/colors.xml -->
<color name="Teal_700">#ff00796b</color>
<color name="Teal_300">#ff4db6ac</color>
<color name="Blue_700">#ff3367d6</color>
<color name="Blue_300">#ff64b5f6</color>
<color name="Indigo_700">#ff303f9f</color>
<color name="Indigo_300">#ff7986cb</color>
<color name="Purple_700">#ff7b1fa2</color>
<color name="Purple_300">#ffba68c8</color>
<color name="Pink_700">#ffc2185b</color>
<color name="Pink_300">#fff06292</color>
<color name="Red_700">#ffc53929</color>
<color name="Red_300">#ffe57373</color>
</resources>
10 changes: 10 additions & 0 deletions src/com/github/iusmac/sevensim/ui/UiUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,16 @@ public static boolean isLandscape(final @NonNull Context context) {
Configuration.ORIENTATION_LANDSCAPE;
}

/**
* Check whether currently the app is displayed in dark mode or not.
*
* @param context The {@link Context} to access resources.
*/
public static boolean isDarkMode(final @NonNull Context context) {
return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
}

/** Do not initialize. */
private UiUtils() {}
}
25 changes: 24 additions & 1 deletion src/com/github/iusmac/sevensim/ui/sim/SimListFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.SparseIntArray;
import android.view.View;

import androidx.collection.SparseArrayCompat;
Expand Down Expand Up @@ -41,6 +42,7 @@ public final class SimListFragment extends Hilt_SimListFragment {
@Inject
Lazy<ApplicationInfo> mApplicationInfoLazy;

private SparseIntArray mLightDarkSimIconColorPalette;
private SimListViewModel mViewModel;

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

private BannerMessagePreference mBackgroundRestrictedBanner;

@Override
public void onAttach(final Context context) {
super.onAttach(context);

mLightDarkSimIconColorPalette = UiUtils.isDarkMode(context) ?
getLightDarkSimIconColorPalette(context) : new SparseIntArray();
}

@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
mViewModel = ((SimListActivity) requireActivity()).getViewModel();
Expand Down Expand Up @@ -160,7 +170,9 @@ private void updateSimPreferenceList(
}
pref.setOrder(i);
pref.setIcon(UiUtils.createTintedDrawable(context, R.drawable.ic_sim,
sub.getIconTint()));
// Whenever possible, use the equivalent dark icon color from the color
// palette defined in Android OS, otherwise default to use the light color
mLightDarkSimIconColorPalette.get(sub.getIconTint(), sub.getIconTint())));
pref.setTitle(sub.getSimName());
pref.setSummary(simEntry.getNextUpcomingScheduleSummary());
pref.setChecked(sub.isSimEnabled());
Expand All @@ -180,4 +192,15 @@ private void updateSimPreferenceList(
// Show a placeholder message if no SIM cards
mNoSimPreference.setVisible(simEntries.isEmpty());
}

private static SparseIntArray getLightDarkSimIconColorPalette(final Context context) {
final var res = context.getResources();
final var lightColorInts = res.getIntArray(R.array.sim_colors);
final var darkColorInts = res.getIntArray(R.array.sim_dark_mode_colors);
final var palette = new SparseIntArray(lightColorInts.length);
for (int i = 0; i < lightColorInts.length; i++) {
palette.put(lightColorInts[i], darkColorInts[i]);
}
return palette;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
@HiltAndroidTest
@RunWith(RobolectricTestRunner.class)
public class SubscriptionSchedulerSummaryBuilderTest extends MockitoHiltAndroidTestBase {
// Increase await timeout in CI environments to account for server load, which may increase the
// time needed to pick up a new thread
private static final Duration DEFAULT_AWAIT_TIMEOUT_DURATION =
Duration.ofSeconds(Optional.ofNullable(System.getenv("CI"))
.filter((v) -> v.equals("true"))
.map((v) -> 5L)
.orElse(3L));
private static final LocalDateTime NOW = LocalDateTime.of(2007, 1, 1, 13, 0);

private final Subscription mSubscription = new Subscription();
Expand Down Expand Up @@ -304,7 +311,7 @@ private CharSequence buildNextUpcomingSubscriptionScheduleSummary(
await()
.dontCatchUncaughtExceptions()
.pollInSameThread()
.atMost(Duration.ofSeconds(3))
.atMost(DEFAULT_AWAIT_TIMEOUT_DURATION)
.pollInterval(Duration.ofMillis(50))
.until(future::isDone);

Expand Down
11 changes: 11 additions & 0 deletions tests/test/java/com/github/iusmac/sevensim/ui/UiUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,17 @@ public void test_isLandscape_WhenLandscape() {
assertTrue(UiUtils.isLandscape(mApplicationContext));
}

@Test
public void test_isDarkMode_WhenLightMode() {
assertFalse(UiUtils.isDarkMode(mApplicationContext));
}

@Test
@Config(qualifiers = "night")
public void test_isDarkMode_WhenDarkMode() {
assertTrue(UiUtils.isDarkMode(mApplicationContext));
}

private static RecyclerView.Adapter<? extends RecyclerView.ViewHolder>
buildAdapterWithItemCount(final int itemCount) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,85 @@ class SimListActivityTest {
}
}

@Test
@Config(minSdk = Q)
fun `test dark sim color array size matches light sim color array size`() {
with(mApplicationContext.getResources()) {
val simColorInts = getIntArray(R.array.sim_colors)
val simDarkModeColorInts = getIntArray(R.array.sim_dark_mode_colors)
assertThat(simDarkModeColorInts.toTypedArray(), arrayWithSize(simColorInts.size))
}
}

@Test
fun `test sim icon color palette matches system color palette`() {
val simColorInts = mApplicationContext.getResources().getIntArray(R.array.sim_colors)
assertThat(systemSimColorInts, `is`(simColorInts))
}

@Test
@Config(minSdk = Q)
fun `test should use dark sim color palette whenever possible`() {
shadowOf(mSubscriptionManager).setAvailableSubscriptionInfos(
SubscriptionInfoBuilder.newBuilder().apply {
setId(1)
setSimSlotIndex(0)
setDisplayName("SIM 1")
setIconTint(Color.BLUE)
}.buildSubscriptionInfo(),
SubscriptionInfoBuilder.newBuilder().apply {
setId(2)
setSimSlotIndex(1)
setDisplayName("SIM 2")
setIconTint(systemSimColorInts.first())
}.buildSubscriptionInfo(),
SubscriptionInfoBuilder.newBuilder().apply {
setId(3)
setSimSlotIndex(2)
setDisplayName("SIM 3")
setIconTint(systemSimColorInts.last())
}.buildSubscriptionInfo()
)
shadowOf(mTelephonyManager).apply {
setActiveModemCount(3)
setPhoneCount(3)
}
val captureRoboImages = { ->
// Ensure ViewModel finished updating UI
waitActivityWorkerThreadUntilIdle()
shadowOf(Looper.getMainLooper()).idle()
onSimEntryAt(0).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
onSimEntryAt(1).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
onSimEntryAt(2).captureRoboImage(idleFor = SCROLL_BAR_FADE_DURATION)
}
onActivity {
captureRoboImages()
// Switch to dark mode (activity will be recreated)
RuntimeEnvironment.setQualifiers("+night")
captureRoboImages()
}
}

private val systemSimColorInts by lazy(LazyThreadSafetyMode.NONE) {
with(mApplicationContext) {
getResources().run {
// NOTE: since we're compiling against Robolectric's android-all.jar, we can
// statically access its generated internal resource Ids only when the emulated
// Android SDK level matches targetSdk version, otherwise (almost always) we'll
// get a Resources.NotFoundException. Sometimes, the generated resource Id
// exists but getIntArray returns an empty array or even a wrong array due a
// collision with a resource different from our resource
if (getApplicationInfo().targetSdkVersion === Build.VERSION.SDK_INT) {
getIntArray(com.android.internal.R.array.sim_colors)
} else { // silver bullet (slower) via reflection to cover all other cases
val androidInternalR = Class.forName("com.android.internal.R\$array")
val resId = androidInternalR.getField("sim_colors").getInt(null)
getIntArray(resId)
}
}
}
}

@After
fun tearDown() {
// Wait for the ViewModel to complete before exiting the test, otherwise the database
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.