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

Commit fb020458 authored by Matías Hernández's avatar Matías Hernández
Browse files

(Almost) display active priority mode icon in status bar

This updates StatusBarIconController, StatusBarIcon, and StatusBarIconView to support:
* resources from non-system packages.
* preloaded drawables for said resources (in case an existing cache exists, as in the case of modes).

Some things are pending:
* Icons are not the correct ones for "special" modes (Do Not Disturb shows the TYPE_OTHER icon). This needs some (relatively minor) changes in ZenIconLoader & friends.
* Preloaded icons are not tinted correctly on theme changes.
* Resource-id icons are tinted correctly, but arbitrarily-sized ones are not resized correctly. ¯\_(ツ)_/¯

Bug: 360399800
Test: atest StatusBarIconControllerImplTest PhoneStatusBarPolicyTest StatusBarIconViewTest StatusBarIconTest
Flag: android.app.modes_ui
Change-Id: I3fc3815895953c401c426d6eebcb9e4c83c95726
parent 14fcc462
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.internal.statusbar;

import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,7 +24,18 @@ import android.os.UserHandle;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Representation of an icon that should appear in the status bar.
 *
 * <p>This includes notifications, conversations, and icons displayed on the right side (e.g.
 * Wifi, Vibration/Silence, Priority Modes, etc).
 *
 * <p>This class is {@link Parcelable} but the {@link #preloadedIcon} is not (and will be lost if
 * the object is copied through parcelling). If {@link #preloadedIcon} is supplied, it must match
 * the {@link #icon} resource/bitmap.
 */
public class StatusBarIcon implements Parcelable {
    public enum Type {
        // Notification: the sender avatar for important conversations
@@ -34,7 +46,9 @@ public class StatusBarIcon implements Parcelable {
        // Notification: the small icon from the notification
        NotifSmallIcon,
        // The wi-fi, cellular or battery icon.
        SystemIcon
        SystemIcon,
        // Some other icon, corresponding to a resource (possibly in a different package).
        ResourceIcon
    }

    public UserHandle user;
@@ -46,6 +60,13 @@ public class StatusBarIcon implements Parcelable {
    public CharSequence contentDescription;
    public Type type;

    /**
     * Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so
     * will be lost if the object is sent to a different process. If you set it, make sure to
     * <em>also</em> set {@link #icon} pointing to the corresponding resource.
     */
    @Nullable public Drawable preloadedIcon;

    public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
            CharSequence contentDescription, Type type) {
        if (icon.getType() == Icon.TYPE_RESOURCE
@@ -88,6 +109,7 @@ public class StatusBarIcon implements Parcelable {
        StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
                this.iconLevel, this.number, this.contentDescription, this.type);
        that.visible = this.visible;
        that.preloadedIcon = this.preloadedIcon;
        return that;
    }

+47 −10
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.internal.statusbar;

import static com.google.common.truth.Truth.assertThat;

import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.UserHandle;

@@ -37,18 +39,55 @@ public class StatusBarIconTest {
     */
    @Test
    public void testParcelable() {
        final StatusBarIcon original = newStatusBarIcon();

        final StatusBarIcon copy = parcelAndUnparcel(original);

        assertSerializableFieldsEqual(copy, original);
    }

    @Test
    public void testClone_withPreloaded() {
        final StatusBarIcon original = newStatusBarIcon();
        original.preloadedIcon = new ColorDrawable(Color.RED);

        final StatusBarIcon copy = original.clone();

        assertSerializableFieldsEqual(copy, original);
        assertThat(copy.preloadedIcon).isNotNull();
        assertThat(copy.preloadedIcon).isInstanceOf(ColorDrawable.class);
        assertThat(((ColorDrawable) copy.preloadedIcon).getColor()).isEqualTo(Color.RED);
    }

    @Test
    public void testClone_noPreloaded() {
        final StatusBarIcon original = newStatusBarIcon();

        final StatusBarIcon copy = original.clone();

        assertSerializableFieldsEqual(copy, original);
        assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon);
    }


    private static StatusBarIcon newStatusBarIcon() {
        final UserHandle dummyUserHandle = UserHandle.of(100);
        final String dummyIconPackageName = "com.android.internal.statusbar.test";
        final int dummyIconId = 123;
        final int dummyIconLevel = 1;
        final int dummyIconNumber = 2;
        final CharSequence dummyIconContentDescription = "dummyIcon";
        final StatusBarIcon original = new StatusBarIcon(dummyIconPackageName, dummyUserHandle,
                dummyIconId, dummyIconLevel, dummyIconNumber, dummyIconContentDescription,
        return new StatusBarIcon(
                dummyIconPackageName,
                dummyUserHandle,
                dummyIconId,
                dummyIconLevel,
                dummyIconNumber,
                dummyIconContentDescription,
                StatusBarIcon.Type.SystemIcon);
    }

        final StatusBarIcon copy = clone(original);

    private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) {
        assertThat(copy.user).isEqualTo(original.user);
        assertThat(copy.pkg).isEqualTo(original.pkg);
        assertThat(copy.icon.sameAs(original.icon)).isTrue();
@@ -56,19 +95,17 @@ public class StatusBarIconTest {
        assertThat(copy.visible).isEqualTo(original.visible);
        assertThat(copy.number).isEqualTo(original.number);
        assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
        assertThat(copy.type).isEqualTo(original.type);
    }

    private StatusBarIcon clone(StatusBarIcon original) {
        Parcel parcel = null;
    private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) {
        Parcel parcel = Parcel.obtain();
        try {
            parcel = Parcel.obtain();
            original.writeToParcel(parcel, 0);
            parcel.setDataPosition(0);
            return StatusBarIcon.CREATOR.createFromParcel(parcel);
        } finally {
            if (parcel != null) {
            parcel.recycle();
        }
    }
}
}
+26 −11
Original line number Diff line number Diff line
@@ -471,17 +471,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
     */
    private Drawable getIcon(Context sysuiContext,
            Context context, StatusBarIcon statusBarIcon) {
        int userId = statusBarIcon.user.getIdentifier();
        if (userId == UserHandle.USER_ALL) {
            userId = UserHandle.USER_SYSTEM;
        }

        // Try to load the monochrome app icon if applicable
        Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
        // Otherwise, just use the icon normally
        if (icon == null) {
            icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
        }
        Drawable icon = loadDrawable(context, statusBarIcon);

        TypedValue typedValue = new TypedValue();
        sysuiContext.getResources().getValue(R.dimen.status_bar_icon_scale_factor,
@@ -508,6 +498,26 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
        return new ScalingDrawableWrapper(icon, scaleFactor);
    }

    @Nullable
    private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
        if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
            return statusBarIcon.preloadedIcon.mutate();
        } else {
            int userId = statusBarIcon.user.getIdentifier();
            if (userId == UserHandle.USER_ALL) {
                userId = UserHandle.USER_SYSTEM;
            }

            // Try to load the monochrome app icon if applicable
            Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
            // Otherwise, just use the icon normally
            if (icon == null) {
                icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
            }
            return icon;
        }
    }

    @Nullable
    private Drawable maybeGetMonochromeAppIcon(Context context,
            StatusBarIcon statusBarIcon) {
@@ -1020,4 +1030,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
    public boolean showsConversation() {
        return mShowsConversation;
    }

    private static boolean usesModeIcons() {
        return android.app.Flags.modesApi() && android.app.Flags.modesUi()
                && android.app.Flags.modesUiIcons();
    }
}
+14 −5
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.view.View;

import androidx.lifecycle.Observer;

import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -395,16 +396,24 @@ public class PhoneStatusBarPolicy
                () -> mResources.getString(R.string.accessibility_managed_profile));
    }

    private void onActiveModeChanged(@Nullable ZenMode zenMode) {
    private void onActiveModeChanged(@Nullable ZenMode mode) {
        if (!usesModeIcons()) {
            Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled");
            return;
        }
        boolean visible = zenMode != null;
        boolean visible = mode != null;
        if (visible) {
            // TODO: b/360399800 - Get the drawable from the mode; this is a placeholder.
            mIconController.setIcon(mSlotZen,
                    com.android.internal.R.drawable.ic_zen_mode_type_immersive, zenMode.getName());
            // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode;
            //  this is a shortcut for testing (there should be no direct dependency on
            //  ZenIconLoader here).
            String resPackage = mode.isSystemOwned() ? null : mode.getRule().getPackageName();
            int iconResId = mode.getRule().getIconResId();
            if (iconResId == 0) {
                iconResId = ZenIconLoader.getIconResourceIdFromType(mode.getType());
            }

            mIconController.setResourceIcon(mSlotZen, resPackage, iconResId,
                    /* preloadedIcon= */ null, mode.getName());
        }
        if (visible != mZenVisible) {
            mIconController.setIconVisibility(mSlotZen, visible);
+15 −0
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@ package com.android.systemui.statusbar.phone.ui;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.ArraySet;

import androidx.annotation.DrawableRes;

import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
@@ -57,6 +60,18 @@ public interface StatusBarIconController {
    /** Adds or updates an icon for the given slot for **internal system icons**. */
    void setIcon(String slot, int resourceId, CharSequence contentDescription);

    /**
     * Adds or updates an icon for the given slot.
     *
     * @param resPackage the package name containing the resource in question. Can be null if the
     *      icon is a system icon (e.g. a resource from {@code android.R.drawable} or
     *      {@code com.android.internal.R.drawable}).
     * @param iconResId id of the drawable resource
     * @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known
     */
    void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId,
            @Nullable Drawable preloadedIcon, CharSequence contentDescription);

    /**
     * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
     * set up (inflated and added to the view hierarchy).
Loading