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

Commit aa6cf998 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Notif redesign: Use launcher app icons for notification rows

This is just the simplest implementation for fetching the icon properly.
AppIconProvider will have a cache, and we'll also need to not show app
icons for certain notifications, but that will be in follow-up CLs. I
also didn't add tests yet since the actual implementation in
AppIconProvider will change soon.

One KI I noticed is this doesn't seem to work for group headers yet.

Bug: 371174789
Test: manually post notifications and see that they have the right icon
Flag: android.app.notifications_redesign_app_icons
Change-Id: Iad9f68877c6c3bc356505d30036ba4dc1166847e
parent b2be1ae5
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -5,6 +5,14 @@ container: "system"
# when appropriate, as it's not currently part of the namespace so it may not be obvious what the
# flag relates to.

flag {
  name: "notifications_redesign_app_icons"
  namespace: "systemui"
  description: "Notifications Redesign: Use app icons in notification rows (not to be confused with"
    " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)."
  bug: "371174789"
}

flag {
  name: "modes_api"
  is_exported: true
+198 −26
Original line number Diff line number Diff line
@@ -39,17 +39,24 @@ import com.android.internal.R;

/**
 * An image view that holds the icon displayed at the start of a notification row.
 * This can generally either display the "small icon" of a notification set via
 * {@link this#setImageIcon(Icon)}, or an app icon controlled and fetched by the provider set
 * through {@link this#setIconProvider(NotificationIconProvider)}.
 */
@RemoteViews.RemoteView
public class NotificationRowIconView extends CachingIconView {
    private NotificationIconProvider mIconProvider;

    private boolean mApplyCircularCrop = false;
    private boolean mShouldShowAppIcon = false;
    private Drawable mAppIcon = null;

    // Padding and background set on the view prior to being changed by setShouldShowAppIcon(true),
    // to be restored if shouldShowAppIcon becomes false again.
    // Padding, background and colors set on the view prior to being overridden when showing the app
    // icon, to be restored if we're showing the small icon again.
    private Rect mOriginalPadding = null;
    private Drawable mOriginalBackground = null;

    private int mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID;
    private int mOriginalIconColor = ColoredIconHelper.COLOR_INVALID;

    public NotificationRowIconView(Context context) {
        super(context);
@@ -81,6 +88,71 @@ public class NotificationRowIconView extends CachingIconView {
        super.onFinishInflate();
    }

    /**
     * Sets the icon provider for this view. This is used to determine whether we should show the
     * app icon instead of the small icon, and to fetch the app icon if needed.
     */
    public void setIconProvider(NotificationIconProvider iconProvider) {
        mIconProvider = iconProvider;
    }

    private Drawable loadAppIcon() {
        if (mIconProvider != null && mIconProvider.shouldShowAppIcon()) {
            return mIconProvider.getAppIcon();
        }
        return null;
    }

    @RemotableViewMethod(asyncImpl = "setImageIconAsync")
    @Override
    public void setImageIcon(Icon icon) {
        if (Flags.notificationsRedesignAppIcons()) {
            if (mAppIcon != null) {
                // We already know that we should be using the app icon, and we already loaded it.
                // We assume that cannot change throughout the lifetime of a notification, so
                // there's nothing to do here.
                return;
            }
            mAppIcon = loadAppIcon();
            if (mAppIcon != null) {
                setImageDrawable(mAppIcon);
                adjustViewForAppIcon();
            } else {
                super.setImageIcon(icon);
                restoreViewForSmallIcon();
            }
            return;
        }
        super.setImageIcon(icon);
    }

    @RemotableViewMethod
    @Override
    public Runnable setImageIconAsync(Icon icon) {
        if (Flags.notificationsRedesignAppIcons()) {
            if (mAppIcon != null) {
                // We already know that we should be using the app icon, and we already loaded it.
                // We assume that cannot change throughout the lifetime of a notification, so
                // there's nothing to do here.
                return () -> {
                };
            }
            mAppIcon = loadAppIcon();
            if (mAppIcon != null) {
                return () -> {
                    setImageDrawable(mAppIcon);
                    adjustViewForAppIcon();
                };
            } else {
                return () -> {
                    super.setImageIcon(icon);
                    restoreViewForSmallIcon();
                };
            }
        }
        return super.setImageIconAsync(icon);
    }

    /** Whether the icon represents the app icon (instead of the small icon). */
    @RemotableViewMethod
    public void setShouldShowAppIcon(boolean shouldShowAppIcon) {
@@ -91,13 +163,68 @@ public class NotificationRowIconView extends CachingIconView {

            mShouldShowAppIcon = shouldShowAppIcon;
            if (mShouldShowAppIcon) {
                if (mOriginalPadding == null && mOriginalBackground == null) {
                adjustViewForAppIcon();
            } else {
                // Restore original padding and background if needed
                restoreViewForSmallIcon();
            }
        }
    }

    /**
     * Override padding and background from the view to display the app icon.
     */
    private void adjustViewForAppIcon() {
        removePadding();

        if (Flags.notificationsUseAppIconInRow()) {
            addWhiteBackground();
        } else {
            // No need to set the background for notification redesign, since the icon
            // factory already does that for us.
            removeBackground();
        }
    }

    /**
     * Restore padding and background overridden by {@link this#adjustViewForAppIcon}.
     * Does nothing if they were not overridden.
     */
    private void restoreViewForSmallIcon() {
        restorePadding();
        restoreBackground();
        restoreColors();
    }

    private void removePadding() {
        if (mOriginalPadding == null) {
            mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(),
                    getPaddingRight(), getPaddingBottom());
        }
        setPadding(0, 0, 0, 0);
    }

    private void restorePadding() {
        if (mOriginalPadding != null) {
            setPadding(mOriginalPadding.left, mOriginalPadding.top,
                    mOriginalPadding.right,
                    mOriginalPadding.bottom);
            mOriginalPadding = null;
        }
    }

    private void removeBackground() {
        if (mOriginalBackground == null) {
            mOriginalBackground = getBackground();
        }

                setPadding(0, 0, 0, 0);
        setBackground(null);
    }

    private void addWhiteBackground() {
        if (mOriginalBackground == null) {
            mOriginalBackground = getBackground();
        }

        // Make the background white in case the icon itself doesn't have one.
        ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE,
@@ -107,17 +234,49 @@ public class NotificationRowIconView extends CachingIconView {
            setBackground(getContext().getDrawable(R.drawable.notification_icon_circle));
        }
        getBackground().mutate().setColorFilter(colorFilter);
            } else {
                // Restore original padding and background if needed
                if (mOriginalPadding != null) {
                    setPadding(mOriginalPadding.left, mOriginalPadding.top, mOriginalPadding.right,
                            mOriginalPadding.bottom);
                    mOriginalPadding = null;
    }

    private void restoreBackground() {
        // NOTE: This will not work if the original background was null, but that's better than
        //  accidentally clearing the background. We expect that there's generally going to be one
        //  anyway unless we manually clear it.
        if (mOriginalBackground != null) {
            setBackground(mOriginalBackground);
            mOriginalBackground = null;
        }
    }

    private void restoreColors() {
        if (mOriginalBackgroundColor != ColoredIconHelper.COLOR_INVALID) {
            super.setBackgroundColor(mOriginalBackgroundColor);
            mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID;
        }
        if (mOriginalIconColor != ColoredIconHelper.COLOR_INVALID) {
            super.setOriginalIconColor(mOriginalIconColor);
            mOriginalIconColor = ColoredIconHelper.COLOR_INVALID;
        }
    }

    @RemotableViewMethod
    @Override
    public void setBackgroundColor(int color) {
        // Ignore color overrides if we're showing the app icon.
        if (mAppIcon == null) {
            super.setBackgroundColor(color);
        } else {
            mOriginalBackgroundColor = color;
        }
    }

    @RemotableViewMethod
    @Override
    public void setOriginalIconColor(int color) {
        // Ignore color overrides if we're showing the app icon.
        if (mAppIcon == null) {
            super.setOriginalIconColor(color);
        } else {
            mOriginalIconColor = color;
        }
    }

    @Nullable
@@ -197,4 +356,17 @@ public class NotificationRowIconView extends CachingIconView {

        return bitmap;
    }

    /**
     * A provider that allows this view to verify whether it should use the app icon instead of the
     * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should
     * primarily be called on the background thread.
     */
    public interface NotificationIconProvider {
        /** Whether this notification should use the app icon instead of the small icon. */
        boolean shouldShowAppIcon();

        /** Get the app icon for this notification. */
        Drawable getAppIcon();
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory
import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
import javax.inject.Provider
@@ -35,6 +36,7 @@ constructor(
    bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
    notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
    notificationRowIconViewInflaterFactory: NotificationRowIconViewInflaterFactory,
) : NotifRemoteViewsFactoryContainer {
    override val factories: Set<NotifRemoteViewsFactory> = buildSet {
        add(precomputedTextViewFactory)
@@ -47,5 +49,8 @@ constructor(
        if (NotificationViewFlipperPausing.isEnabled) {
            add(notificationViewFlipperFactory.get())
        }
        if (android.app.Flags.notificationsRedesignAppIcons()) {
            add(notificationRowIconViewInflaterFactory)
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row;

import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.row.icon.AppIconProviderModule;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;

import dagger.Binds;
@@ -28,7 +29,7 @@ import javax.inject.Provider;
/**
 * Dagger Module containing notification row and view inflation implementations.
 */
@Module
@Module(includes = {AppIconProviderModule.class})
public abstract class NotificationRowModule {

    /**
+81 −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.systemui.statusbar.notification.row.icon

import android.app.Flags
import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import com.android.internal.R
import com.android.launcher3.icons.BaseIconFactory
import com.android.systemui.dagger.SysUISingleton
import dagger.Module
import dagger.Provides
import javax.inject.Inject
import javax.inject.Provider

/** A provider used to cache and fetch app icons used by notifications. */
interface AppIconProvider {
    @Throws(NameNotFoundException::class)
    fun getOrFetchAppIcon(packageName: String, context: Context): Drawable
}

@SysUISingleton
class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider {
    private val iconFactory: BaseIconFactory
        get() =
            BaseIconFactory(
                sysuiContext,
                sysuiContext.resources.configuration.densityDpi,
                sysuiContext.resources.getDimensionPixelSize(R.dimen.notification_icon_circle_size),
            )

    override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
        val icon = context.packageManager.getApplicationIcon(packageName)
        return BitmapDrawable(
            context.resources,
            iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE),
        )
    }
}

class NoOpIconProvider : AppIconProvider {
    companion object {
        const val TAG = "NoOpIconProvider"
    }

    override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable {
        Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
        return ColorDrawable(Color.WHITE)
    }
}

@Module
class AppIconProviderModule {
    @Provides
    @SysUISingleton
    fun provideImpl(realImpl: Provider<AppIconProviderImpl>): AppIconProvider =
        if (Flags.notificationsRedesignAppIcons()) {
            realImpl.get()
        } else {
            NoOpIconProvider()
        }
}
Loading