Also, don't apply the layout params, etc on each call to updateState - once per displayPreference is enough. Fixes: 356399449 Bug: 357861830 Test: manual Flag: android.app.modes_ui Change-Id: I6967ea1745377d0f514ca0f68101043f017a8fd7
230 lines
11 KiB
Java
230 lines
11 KiB
Java
/*
|
|
* 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.settings.notification.modes;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.ColorStateList;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.graphics.Typeface;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.graphics.drawable.LayerDrawable;
|
|
import android.graphics.drawable.ShapeDrawable;
|
|
import android.graphics.drawable.StateListDrawable;
|
|
import android.graphics.drawable.shapes.OvalShape;
|
|
import android.util.StateSet;
|
|
import android.view.Gravity;
|
|
|
|
import androidx.annotation.AttrRes;
|
|
import androidx.annotation.DrawableRes;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.Px;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settingslib.Utils;
|
|
|
|
import com.google.common.base.Strings;
|
|
|
|
import java.util.Locale;
|
|
|
|
class IconUtil {
|
|
|
|
static Drawable applyNormalTint(@NonNull Context context, @NonNull Drawable icon) {
|
|
return applyTint(context, icon, android.R.attr.colorControlNormal);
|
|
}
|
|
|
|
static Drawable applyAccentTint(@NonNull Context context, @NonNull Drawable icon) {
|
|
return applyTint(context, icon, android.R.attr.colorAccent);
|
|
}
|
|
|
|
private static Drawable applyTint(@NonNull Context context, @NonNull Drawable icon,
|
|
@AttrRes int colorAttr) {
|
|
icon = icon.mutate();
|
|
icon.setTintList(Utils.getColorAttr(context, colorAttr));
|
|
return icon;
|
|
}
|
|
|
|
/**
|
|
* Returns a variant of the supplied mode icon to be used as the header in the mode page. The
|
|
* mode icon is contained in a 12-sided-cookie. The color combination is "material secondary"
|
|
* when unselected and "material primary" when selected; the switch between these two color sets
|
|
* is animated with a cross-fade. The selected colors should be used when the mode is currently
|
|
* active.
|
|
*/
|
|
static Drawable makeModeHeader(@NonNull Context context, Drawable modeIcon) {
|
|
Resources res = context.getResources();
|
|
Drawable background = checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_icon_cookie));
|
|
@Px int outerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_size);
|
|
@Px int innerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_inner_icon_size);
|
|
|
|
Drawable base = composeIcons(
|
|
background,
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorSecondaryContainer),
|
|
outerSizePx,
|
|
modeIcon,
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorOnSecondaryContainer),
|
|
innerSizePx);
|
|
|
|
Drawable selected = composeIcons(
|
|
background,
|
|
Utils.getColorAttr(context, com.android.internal.R.attr.materialColorPrimary),
|
|
outerSizePx,
|
|
modeIcon,
|
|
Utils.getColorAttr(context, com.android.internal.R.attr.materialColorOnPrimary),
|
|
innerSizePx);
|
|
|
|
StateListDrawable result = new StateListDrawable();
|
|
result.setEnterFadeDuration(res.getInteger(android.R.integer.config_mediumAnimTime));
|
|
result.setExitFadeDuration(res.getInteger(android.R.integer.config_mediumAnimTime));
|
|
result.addState(new int[] { android.R.attr.state_selected }, selected);
|
|
result.addState(StateSet.WILD_CARD, base);
|
|
result.setBounds(0, 0, outerSizePx, outerSizePx);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns a variant of the supplied {@code icon} to be used as the header in the icon picker
|
|
* (large icon within large circle, with the "material secondary" color combination).
|
|
*/
|
|
static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
|
|
return composeIconCircle(
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorSecondaryContainer),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_icon_list_header_circle_diameter),
|
|
icon,
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorOnSecondaryContainer),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_icon_list_header_icon_size));
|
|
}
|
|
|
|
/**
|
|
* Returns a variant of the supplied {@code icon} to be used as an option in the icon picker
|
|
* (small icon in small circle, with "material secondary" colors for the normal state and
|
|
* "material primary" colors for the selected state).
|
|
*/
|
|
static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
|
|
return composeIconCircle(
|
|
context.getColorStateList(R.color.modes_icon_selectable_background),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_icon_list_item_circle_diameter),
|
|
checkNotNull(context.getDrawable(iconResId)),
|
|
context.getColorStateList(R.color.modes_icon_selectable_icon),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_icon_list_item_icon_size));
|
|
}
|
|
|
|
/**
|
|
* Returns a variant of the supplied icon to be used in a {@link CircularIconsPreference}. The
|
|
* inner icon is 20x20 dp and it's contained in a circle of diameter 32dp, and is tinted
|
|
* with the "material secondary" color combination.
|
|
*/
|
|
static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
|
|
@DrawableRes int iconResId) {
|
|
return composeIconCircle(
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorSecondaryContainer),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_circular_icon_diameter),
|
|
checkNotNull(context.getDrawable(iconResId)),
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorOnSecondaryContainer),
|
|
context.getResources().getDimensionPixelSize(
|
|
R.dimen.zen_mode_circular_icon_inner_icon_size));
|
|
}
|
|
|
|
/**
|
|
* Returns an icon representing a contact that doesn't have an associated photo, to be used in
|
|
* a {@link CircularIconsPreference}, tinted with the "material tertiary". If the contact's
|
|
* display name is not empty, it's the contact's monogram, otherwise it's a generic icon.
|
|
*/
|
|
static Drawable makeContactMonogram(@NonNull Context context, @Nullable String displayName) {
|
|
Resources res = context.getResources();
|
|
if (Strings.isNullOrEmpty(displayName)) {
|
|
return composeIconCircle(
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorTertiaryContainer),
|
|
res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter),
|
|
checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_generic_contact)),
|
|
Utils.getColorAttr(context,
|
|
com.android.internal.R.attr.materialColorOnTertiaryContainer),
|
|
res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_inner_icon_size));
|
|
}
|
|
|
|
float diameter = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter);
|
|
Bitmap bitmap = Bitmap.createBitmap((int) diameter, (int) diameter,
|
|
Bitmap.Config.ARGB_8888);
|
|
Canvas canvas = new Canvas(bitmap);
|
|
|
|
Paint circlePaint = new Paint();
|
|
circlePaint.setColor(Utils.getColorAttrDefaultColor(context,
|
|
com.android.internal.R.attr.materialColorTertiaryContainer));
|
|
circlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
|
canvas.drawCircle(diameter / 2f, diameter / 2f, diameter / 2f, circlePaint);
|
|
|
|
Paint textPaint = new Paint();
|
|
textPaint.setColor(Utils.getColorAttrDefaultColor(context,
|
|
com.android.internal.R.attr.materialColorOnTertiaryContainer));
|
|
textPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
|
|
textPaint.setTextAlign(Paint.Align.LEFT);
|
|
textPaint.setTextSize(res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_text_size));
|
|
|
|
String text = displayName.substring(0, 1).toUpperCase(Locale.getDefault());
|
|
Rect textRect = new Rect();
|
|
textPaint.getTextBounds(text, 0, text.length(), textRect);
|
|
|
|
float textX = diameter / 2f - textRect.width() / 2f - textRect.left;
|
|
float textY = diameter / 2f + textRect.height() / 2f - textRect.bottom;
|
|
canvas.drawText(text, textX, textY, textPaint);
|
|
|
|
return new BitmapDrawable(context.getResources(), bitmap);
|
|
}
|
|
|
|
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
|
|
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
|
return composeIcons(new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx, icon,
|
|
iconColor, iconSizePx);
|
|
}
|
|
|
|
private static Drawable composeIcons(Drawable outer, ColorStateList outerColor,
|
|
@Px int outerSizePx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
|
Drawable background = checkNotNull(outer.getConstantState()).newDrawable().mutate();
|
|
background.setTintList(outerColor);
|
|
Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate();
|
|
foreground.setTintList(iconColor);
|
|
|
|
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
|
|
|
|
layerDrawable.setLayerSize(0, outerSizePx, outerSizePx);
|
|
layerDrawable.setLayerGravity(1, Gravity.CENTER);
|
|
layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
|
|
|
|
layerDrawable.setBounds(0, 0, outerSizePx, outerSizePx);
|
|
return layerDrawable;
|
|
}
|
|
}
|