diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 34cf56b4bf..ef5c88aa33 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -20,6 +20,7 @@ import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static android.text.Layout.Alignment.ALIGN_NORMAL; +import static com.android.launcher3.Flags.enableContrastTiles; import static com.android.launcher3.Flags.enableCursorHoverStates; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE; @@ -39,6 +40,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.icu.text.MessageFormat; @@ -722,6 +724,29 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + /** Draws a background behind the App Title label when required. **/ + public void drawAppContrastTile(Canvas canvas) { + RectF appTitleBounds; + Paint.FontMetrics fm = getPaint().getFontMetrics(); + Rect tmpRect = new Rect(); + getDrawingRect(tmpRect); + + if (mIcon == null) { + appTitleBounds = new RectF(0, 0, tmpRect.right, + (int) Math.ceil(fm.bottom - fm.top)); + } else { + Rect iconBounds = new Rect(); + getIconBounds(iconBounds); + int textStart = iconBounds.bottom + getCompoundDrawablePadding(); + appTitleBounds = new RectF(tmpRect.left, textStart, tmpRect.right, + textStart + (int) Math.ceil(fm.bottom - fm.top)); + } + + canvas.drawRoundRect(appTitleBounds, appTitleBounds.height() / 2, + appTitleBounds.height() / 2, + PillColorProvider.getInstance(getContext()).getAppTitlePillPaint()); + } + /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */ protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) { if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) { @@ -909,7 +934,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public void setTextColor(ColorStateList colors) { - mTextColor = colors.getDefaultColor(); + mTextColor = shouldDrawAppContrastTile() ? PillColorProvider.getInstance( + getContext()).getAppTitleTextPaint().getColor() + : colors.getDefaultColor(); mTextColorStateList = colors; if (Float.compare(mTextAlpha, 1) == 0) { super.setTextColor(colors); @@ -926,6 +953,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION); } + /** + * Whether or not an App title contrast tile should be drawn for this element. + **/ + public boolean shouldDrawAppContrastTile() { + return mDisplay == DISPLAY_WORKSPACE && shouldTextBeVisible() + && PillColorProvider.getInstance(getContext()).isMatchaEnabled() + && enableContrastTiles(); + } + public void setTextVisibility(boolean visible) { setTextAlpha(visible ? 1 : 0); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 6145077085..305941e07a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -537,6 +537,7 @@ public class Launcher extends StatefulActivity mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots); mWidgetPickerDataProvider = new WidgetPickerDataProvider(); + PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver(); boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this); if (internalStateHandled) { @@ -1813,6 +1814,7 @@ public class Launcher extends StatefulActivity // changes while launcher is still loading. getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener); mOverlayManager.onActivityDestroyed(); + PillColorProvider.getInstance(mWorkspace.getContext()).unregisterObserver(); } public LauncherAccessibilityDelegate getAccessibilityDelegate() { diff --git a/src/com/android/launcher3/PillColorPorovider.kt b/src/com/android/launcher3/PillColorPorovider.kt new file mode 100644 index 0000000000..347c5d6581 --- /dev/null +++ b/src/com/android/launcher3/PillColorPorovider.kt @@ -0,0 +1,85 @@ +/* + * 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.launcher3 + +import android.content.Context +import android.database.ContentObserver +import android.graphics.Paint +import android.net.Uri +import android.provider.Settings +import com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR + +class PillColorProvider private constructor(c: Context) { + private val context = c.applicationContext + + private val matchaUri by lazy { Settings.Secure.getUriFor(MATCHA_SETTING) } + var appTitlePillPaint = Paint() + private set + + var appTitleTextPaint = Paint() + private set + + private var isMatchaEnabledInternal = 0 + + var isMatchaEnabled = isMatchaEnabledInternal != 0 + + private val pillColorObserver = + object : ContentObserver(ORDERED_BG_EXECUTOR.handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == matchaUri) { + isMatchaEnabledInternal = + Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0) + isMatchaEnabled = isMatchaEnabledInternal != 0 + } + } + } + + fun registerObserver() { + context.contentResolver.registerContentObserver(matchaUri, false, pillColorObserver) + setup() + } + + fun unregisterObserver() { + context.contentResolver.unregisterContentObserver(pillColorObserver) + } + + fun setup() { + appTitlePillPaint.color = + context.resources.getColor( + R.color.material_color_surface_container_lowest, + context.theme, + ) + appTitleTextPaint.color = + context.resources.getColor(R.color.material_color_on_surface, context.theme) + isMatchaEnabledInternal = Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0) + isMatchaEnabled = isMatchaEnabledInternal != 0 + } + + companion object { + private var INSTANCE: PillColorProvider? = null + private const val MATCHA_SETTING = "matcha_enable" + + // TODO: Replace with a Dagger injection that is a singleton. + @JvmStatic + fun getInstance(context: Context): PillColorProvider { + if (INSTANCE == null) { + INSTANCE = PillColorProvider(context) + } + return INSTANCE!! + } + } +} diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java index ef66ffe091..392d9a7014 100644 --- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java +++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java @@ -102,6 +102,9 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { @Override public void onDraw(Canvas canvas) { + if (shouldDrawAppContrastTile()) { + drawAppContrastTile(canvas); + } // If text is transparent or shadow alpha is 0, don't draw any shadow if (skipDoubleShadow()) { super.onDraw(canvas);