Loading core/res/res/drawable/ic_zen_mode_type_special_dnd.xml 0 → 100644 +25 −0 Original line number Diff line number Diff line <!-- Copyright (C) 2024 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="?android:attr/colorControlNormal" android:viewportHeight="960" android:viewportWidth="960"> <path android:fillColor="@android:color/white" android:pathData="M280,520L680,520L680,440L280,440L280,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> </vector> No newline at end of file core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -5575,6 +5575,7 @@ <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" /> <java-symbol type="drawable" name="ic_zen_mode_type_theater" /> <java-symbol type="drawable" name="ic_zen_mode_type_unknown" /> <java-symbol type="drawable" name="ic_zen_mode_type_special_dnd" /> <!-- System notification for background user sound --> <java-symbol type="string" name="bg_user_sound_notification_title_alarm" /> Loading packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIcon.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.notification.modes; import static com.google.common.base.Preconditions.checkArgument; import android.graphics.drawable.Drawable; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Icon of a Zen Mode, already loaded from the owner's resources (if specified) or from a default. */ public record ZenIcon(@NonNull Key key, @NonNull Drawable drawable) { /** * Key of a Zen Mode Icon. * * <p>{@link #resPackage()} will be null if the resource belongs to the system, and thus can * be loaded with any {@code Context}. */ public record Key(@Nullable String resPackage, @DrawableRes int resId) { public Key { checkArgument(resId != 0, "Resource id must be valid"); } static Key forSystemResource(@DrawableRes int resId) { return new Key(null, resId); } } } packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.notification.modes; import android.app.AutomaticZenRule; import com.android.internal.R; import com.google.common.collect.ImmutableMap; /** * Known icon keys for zen modes that lack a custom {@link AutomaticZenRule#getIconResId()}, based * on their {@link ZenMode.Kind} and {@link ZenMode#getType}. */ class ZenIconKeys { /** The icon for Do Not Disturb mode. */ static final ZenIcon.Key MANUAL_DND = ZenIcon.Key.forSystemResource( R.drawable.ic_zen_mode_type_special_dnd); /** * The default icon for implicit modes (they can also have a specific icon, if the user has * chosen one via Settings). */ static final ZenIcon.Key IMPLICIT_MODE_DEFAULT = ZenIcon.Key.forSystemResource( R.drawable.ic_zen_mode_type_unknown); private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of( AutomaticZenRule.TYPE_UNKNOWN, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown), AutomaticZenRule.TYPE_OTHER, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_other), AutomaticZenRule.TYPE_SCHEDULE_TIME, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_time), AutomaticZenRule.TYPE_SCHEDULE_CALENDAR, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_calendar), AutomaticZenRule.TYPE_BEDTIME, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_bedtime), AutomaticZenRule.TYPE_DRIVING, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_driving), AutomaticZenRule.TYPE_IMMERSIVE, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_immersive), AutomaticZenRule.TYPE_THEATER, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_theater), AutomaticZenRule.TYPE_MANAGED, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_managed) ); private static final ZenIcon.Key FOR_UNEXPECTED_TYPE = ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown); /** Default icon descriptors per mode {@link AutomaticZenRule.Type}. */ static ZenIcon.Key forType(@AutomaticZenRule.Type int ruleType) { return TYPE_DEFAULTS.getOrDefault(ruleType, FOR_UNEXPECTED_TYPE); } private ZenIconKeys() { } } packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java +52 −61 Original line number Diff line number Diff line Loading @@ -16,28 +16,24 @@ package com.android.settingslib.notification.modes; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Objects.requireNonNull; import android.annotation.DrawableRes; import android.annotation.Nullable; import android.app.AutomaticZenRule; import android.content.Context; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.service.notification.SystemZenRules; import android.text.TextUtils; import android.util.Log; import android.util.LruCache; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; Loading @@ -54,7 +50,7 @@ public class ZenIconLoader { @Nullable // Until first usage private static ZenIconLoader sInstance; private final LruCache<String, Drawable> mCache; private final LruCache<ZenIcon.Key, Drawable> mCache; private final ListeningExecutorService mBackgroundExecutor; public static ZenIconLoader getInstance() { Loading @@ -64,90 +60,85 @@ public class ZenIconLoader { return sInstance; } /** Replaces the singleton instance of {@link ZenIconLoader} by the provided one. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) public static void setInstance(@Nullable ZenIconLoader instance) { sInstance = instance; } private ZenIconLoader() { this(Executors.newFixedThreadPool(4)); } @VisibleForTesting ZenIconLoader(ExecutorService backgroundExecutor) { public ZenIconLoader(ExecutorService backgroundExecutor) { mCache = new LruCache<>(50); mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor); } /** * Loads the {@link Drawable} corresponding to a {@link ZenMode} in a background thread, and * caches it for future calls. * * <p>The {@link ZenIcon#drawable()} will always correspond to the resource indicated by * {@link ZenIcon#key()}. In turn, this will match the value of {@link ZenMode#getIconKey()} * for the supplied mode -- except for the rare case where the mode has an apparently valid * drawable resource id that we fail to load for some reason, thus needing a "fallback" icon. */ @NonNull ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) { if (rule.getIconResId() == 0) { return Futures.immediateFuture(getFallbackIcon(context, rule.getType())); public ListenableFuture<ZenIcon> getIcon(@NonNull Context context, @NonNull ZenMode mode) { ZenIcon.Key key = mode.getIconKey(); return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ true)) .transformAsync(drawable -> drawable != null ? immediateFuture(new ZenIcon(key, drawable)) : getFallbackIcon(context, mode), mBackgroundExecutor); } return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId())) .transform(icon -> icon != null ? icon : getFallbackIcon(context, rule.getType()), MoreExecutors.directExecutor()); private ListenableFuture<ZenIcon> getFallbackIcon(Context context, ZenMode mode) { ZenIcon.Key key = ZenIconKeys.forType(mode.getType()); return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ false)) .transform(drawable -> { checkNotNull(drawable, "Couldn't load DEFAULT icon for mode %s!", mode); return new ZenIcon(key, drawable); }, directExecutor()); } @NonNull private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg, int iconResId) { String cacheKey = pkg + ":" + iconResId; private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, ZenIcon.Key key, boolean useMonochromeIfPresent) { synchronized (mCache) { Drawable cachedValue = mCache.get(cacheKey); Drawable cachedValue = mCache.get(key); if (cachedValue != null) { return immediateFuture(cachedValue != MISSING ? cachedValue : null); } } return FluentFuture.from(mBackgroundExecutor.submit(() -> { if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) { return context.getDrawable(iconResId); if (TextUtils.isEmpty(key.resPackage())) { return context.getDrawable(key.resId()); } else { Context appContext = context.createPackageContext(pkg, 0); Drawable appDrawable = appContext.getDrawable(iconResId); return getMonochromeIconIfPresent(appDrawable); Context appContext = context.createPackageContext(key.resPackage(), 0); Drawable appDrawable = appContext.getDrawable(key.resId()); return useMonochromeIfPresent ? getMonochromeIconIfPresent(appDrawable) : appDrawable; } })).catching(Exception.class, ex -> { // If we cannot resolve the icon, then store MISSING in the cache below, so // we don't try again. Log.e(TAG, "Error while loading icon " + cacheKey, ex); Log.e(TAG, "Error while loading mode icon " + key, ex); return null; }, MoreExecutors.directExecutor()).transform(drawable -> { }, directExecutor()).transform(drawable -> { synchronized (mCache) { mCache.put(cacheKey, drawable != null ? drawable : MISSING); mCache.put(key, drawable != null ? drawable : MISSING); } return drawable; }, MoreExecutors.directExecutor()); } private static Drawable getFallbackIcon(Context context, int ruleType) { int iconResIdFromType = getIconResourceIdFromType(ruleType); return requireNonNull(context.getDrawable(iconResIdFromType)); } /** Return the default icon resource associated to a {@link AutomaticZenRule.Type} */ @DrawableRes public static int getIconResourceIdFromType(@AutomaticZenRule.Type int ruleType) { return switch (ruleType) { case AutomaticZenRule.TYPE_UNKNOWN -> com.android.internal.R.drawable.ic_zen_mode_type_unknown; case AutomaticZenRule.TYPE_OTHER -> com.android.internal.R.drawable.ic_zen_mode_type_other; case AutomaticZenRule.TYPE_SCHEDULE_TIME -> com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar; case AutomaticZenRule.TYPE_BEDTIME -> com.android.internal.R.drawable.ic_zen_mode_type_bedtime; case AutomaticZenRule.TYPE_DRIVING -> com.android.internal.R.drawable.ic_zen_mode_type_driving; case AutomaticZenRule.TYPE_IMMERSIVE -> com.android.internal.R.drawable.ic_zen_mode_type_immersive; case AutomaticZenRule.TYPE_THEATER -> com.android.internal.R.drawable.ic_zen_mode_type_theater; case AutomaticZenRule.TYPE_MANAGED -> com.android.internal.R.drawable.ic_zen_mode_type_managed; default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown; }; }, directExecutor()); } private static Drawable getMonochromeIconIfPresent(Drawable icon) { Loading Loading
core/res/res/drawable/ic_zen_mode_type_special_dnd.xml 0 → 100644 +25 −0 Original line number Diff line number Diff line <!-- Copyright (C) 2024 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="?android:attr/colorControlNormal" android:viewportHeight="960" android:viewportWidth="960"> <path android:fillColor="@android:color/white" android:pathData="M280,520L680,520L680,440L280,440L280,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" /> </vector> No newline at end of file
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -5575,6 +5575,7 @@ <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" /> <java-symbol type="drawable" name="ic_zen_mode_type_theater" /> <java-symbol type="drawable" name="ic_zen_mode_type_unknown" /> <java-symbol type="drawable" name="ic_zen_mode_type_special_dnd" /> <!-- System notification for background user sound --> <java-symbol type="string" name="bg_user_sound_notification_title_alarm" /> Loading
packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIcon.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.notification.modes; import static com.google.common.base.Preconditions.checkArgument; import android.graphics.drawable.Drawable; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Icon of a Zen Mode, already loaded from the owner's resources (if specified) or from a default. */ public record ZenIcon(@NonNull Key key, @NonNull Drawable drawable) { /** * Key of a Zen Mode Icon. * * <p>{@link #resPackage()} will be null if the resource belongs to the system, and thus can * be loaded with any {@code Context}. */ public record Key(@Nullable String resPackage, @DrawableRes int resId) { public Key { checkArgument(resId != 0, "Resource id must be valid"); } static Key forSystemResource(@DrawableRes int resId) { return new Key(null, resId); } } }
packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.notification.modes; import android.app.AutomaticZenRule; import com.android.internal.R; import com.google.common.collect.ImmutableMap; /** * Known icon keys for zen modes that lack a custom {@link AutomaticZenRule#getIconResId()}, based * on their {@link ZenMode.Kind} and {@link ZenMode#getType}. */ class ZenIconKeys { /** The icon for Do Not Disturb mode. */ static final ZenIcon.Key MANUAL_DND = ZenIcon.Key.forSystemResource( R.drawable.ic_zen_mode_type_special_dnd); /** * The default icon for implicit modes (they can also have a specific icon, if the user has * chosen one via Settings). */ static final ZenIcon.Key IMPLICIT_MODE_DEFAULT = ZenIcon.Key.forSystemResource( R.drawable.ic_zen_mode_type_unknown); private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of( AutomaticZenRule.TYPE_UNKNOWN, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown), AutomaticZenRule.TYPE_OTHER, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_other), AutomaticZenRule.TYPE_SCHEDULE_TIME, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_time), AutomaticZenRule.TYPE_SCHEDULE_CALENDAR, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_schedule_calendar), AutomaticZenRule.TYPE_BEDTIME, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_bedtime), AutomaticZenRule.TYPE_DRIVING, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_driving), AutomaticZenRule.TYPE_IMMERSIVE, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_immersive), AutomaticZenRule.TYPE_THEATER, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_theater), AutomaticZenRule.TYPE_MANAGED, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_managed) ); private static final ZenIcon.Key FOR_UNEXPECTED_TYPE = ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown); /** Default icon descriptors per mode {@link AutomaticZenRule.Type}. */ static ZenIcon.Key forType(@AutomaticZenRule.Type int ruleType) { return TYPE_DEFAULTS.getOrDefault(ruleType, FOR_UNEXPECTED_TYPE); } private ZenIconKeys() { } }
packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java +52 −61 Original line number Diff line number Diff line Loading @@ -16,28 +16,24 @@ package com.android.settingslib.notification.modes; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Objects.requireNonNull; import android.annotation.DrawableRes; import android.annotation.Nullable; import android.app.AutomaticZenRule; import android.content.Context; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.service.notification.SystemZenRules; import android.text.TextUtils; import android.util.Log; import android.util.LruCache; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; Loading @@ -54,7 +50,7 @@ public class ZenIconLoader { @Nullable // Until first usage private static ZenIconLoader sInstance; private final LruCache<String, Drawable> mCache; private final LruCache<ZenIcon.Key, Drawable> mCache; private final ListeningExecutorService mBackgroundExecutor; public static ZenIconLoader getInstance() { Loading @@ -64,90 +60,85 @@ public class ZenIconLoader { return sInstance; } /** Replaces the singleton instance of {@link ZenIconLoader} by the provided one. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) public static void setInstance(@Nullable ZenIconLoader instance) { sInstance = instance; } private ZenIconLoader() { this(Executors.newFixedThreadPool(4)); } @VisibleForTesting ZenIconLoader(ExecutorService backgroundExecutor) { public ZenIconLoader(ExecutorService backgroundExecutor) { mCache = new LruCache<>(50); mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor); } /** * Loads the {@link Drawable} corresponding to a {@link ZenMode} in a background thread, and * caches it for future calls. * * <p>The {@link ZenIcon#drawable()} will always correspond to the resource indicated by * {@link ZenIcon#key()}. In turn, this will match the value of {@link ZenMode#getIconKey()} * for the supplied mode -- except for the rare case where the mode has an apparently valid * drawable resource id that we fail to load for some reason, thus needing a "fallback" icon. */ @NonNull ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) { if (rule.getIconResId() == 0) { return Futures.immediateFuture(getFallbackIcon(context, rule.getType())); public ListenableFuture<ZenIcon> getIcon(@NonNull Context context, @NonNull ZenMode mode) { ZenIcon.Key key = mode.getIconKey(); return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ true)) .transformAsync(drawable -> drawable != null ? immediateFuture(new ZenIcon(key, drawable)) : getFallbackIcon(context, mode), mBackgroundExecutor); } return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId())) .transform(icon -> icon != null ? icon : getFallbackIcon(context, rule.getType()), MoreExecutors.directExecutor()); private ListenableFuture<ZenIcon> getFallbackIcon(Context context, ZenMode mode) { ZenIcon.Key key = ZenIconKeys.forType(mode.getType()); return FluentFuture.from(loadIcon(context, key, /* useMonochromeIfPresent= */ false)) .transform(drawable -> { checkNotNull(drawable, "Couldn't load DEFAULT icon for mode %s!", mode); return new ZenIcon(key, drawable); }, directExecutor()); } @NonNull private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg, int iconResId) { String cacheKey = pkg + ":" + iconResId; private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, ZenIcon.Key key, boolean useMonochromeIfPresent) { synchronized (mCache) { Drawable cachedValue = mCache.get(cacheKey); Drawable cachedValue = mCache.get(key); if (cachedValue != null) { return immediateFuture(cachedValue != MISSING ? cachedValue : null); } } return FluentFuture.from(mBackgroundExecutor.submit(() -> { if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) { return context.getDrawable(iconResId); if (TextUtils.isEmpty(key.resPackage())) { return context.getDrawable(key.resId()); } else { Context appContext = context.createPackageContext(pkg, 0); Drawable appDrawable = appContext.getDrawable(iconResId); return getMonochromeIconIfPresent(appDrawable); Context appContext = context.createPackageContext(key.resPackage(), 0); Drawable appDrawable = appContext.getDrawable(key.resId()); return useMonochromeIfPresent ? getMonochromeIconIfPresent(appDrawable) : appDrawable; } })).catching(Exception.class, ex -> { // If we cannot resolve the icon, then store MISSING in the cache below, so // we don't try again. Log.e(TAG, "Error while loading icon " + cacheKey, ex); Log.e(TAG, "Error while loading mode icon " + key, ex); return null; }, MoreExecutors.directExecutor()).transform(drawable -> { }, directExecutor()).transform(drawable -> { synchronized (mCache) { mCache.put(cacheKey, drawable != null ? drawable : MISSING); mCache.put(key, drawable != null ? drawable : MISSING); } return drawable; }, MoreExecutors.directExecutor()); } private static Drawable getFallbackIcon(Context context, int ruleType) { int iconResIdFromType = getIconResourceIdFromType(ruleType); return requireNonNull(context.getDrawable(iconResIdFromType)); } /** Return the default icon resource associated to a {@link AutomaticZenRule.Type} */ @DrawableRes public static int getIconResourceIdFromType(@AutomaticZenRule.Type int ruleType) { return switch (ruleType) { case AutomaticZenRule.TYPE_UNKNOWN -> com.android.internal.R.drawable.ic_zen_mode_type_unknown; case AutomaticZenRule.TYPE_OTHER -> com.android.internal.R.drawable.ic_zen_mode_type_other; case AutomaticZenRule.TYPE_SCHEDULE_TIME -> com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR -> com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar; case AutomaticZenRule.TYPE_BEDTIME -> com.android.internal.R.drawable.ic_zen_mode_type_bedtime; case AutomaticZenRule.TYPE_DRIVING -> com.android.internal.R.drawable.ic_zen_mode_type_driving; case AutomaticZenRule.TYPE_IMMERSIVE -> com.android.internal.R.drawable.ic_zen_mode_type_immersive; case AutomaticZenRule.TYPE_THEATER -> com.android.internal.R.drawable.ic_zen_mode_type_theater; case AutomaticZenRule.TYPE_MANAGED -> com.android.internal.R.drawable.ic_zen_mode_type_managed; default -> com.android.internal.R.drawable.ic_zen_mode_type_unknown; }; }, directExecutor()); } private static Drawable getMonochromeIconIfPresent(Drawable icon) { Loading