Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cdda826a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Split ZenMode.getIcon() into getIconKey() and ZenIconLoader.getIcon()" into main

parents 5e73c1b5 b15806a5
Loading
Loading
Loading
Loading
+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
+1 −0
Original line number Diff line number Diff line
@@ -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" />
+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);
        }
    }
}
+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() { }
}
+52 −61
Original line number Diff line number Diff line
@@ -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;
@@ -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() {
@@ -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