Loading libs/WindowManager/Shell/res/drawable/expand_menu_error.xml 0 → 100644 +31 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2025 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:viewportWidth="960" android:viewportHeight="960"> <path android:fillColor="#FFB4AB" android:pathData="M480,0 A480,480 0 1,1 480,960 A480,480 0 1,1 480,0Z" /> <path android:fillColor="#000000" android:fillType="evenOdd" android:pathData="M480,680Q497,680 508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680ZM440,520L520,520L520,280L440,280L440,520Z" /> </vector> libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +35 −14 Original line number Diff line number Diff line Loading @@ -43,23 +43,44 @@ android:focusable="false" android:scaleType="centerCrop"/> <FrameLayout android:id="@+id/app_name_layout" android:layout_marginStart="8dp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"> <TextView android:id="@+id/application_name" android:layout_width="0dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxWidth="130dp" android:maxWidth="@dimen/desktop_mode_header_app_name_max_width" android:textSize="14sp" android:textFontWeight="500" android:lineHeight="20sp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginStart="8dp" android:layout_gravity="start|center_vertical" android:singleLine="true" android:ellipsize="end" android:ellipsize="none" android:clickable="false" android:focusable="false" tools:text="Gmail"/> <ImageView android:id="@+id/expand_menu_error" android:src="@drawable/expand_menu_error" android:layout_width="@dimen/desktop_mode_header_expand_menu_error_image_width" android:layout_height="@dimen/desktop_mode_header_expand_menu_error_image_width" android:layout_gravity="end|center_vertical" android:layout_marginStart="@dimen/desktop_mode_header_expand_menu_error_image_margin" android:clickable="false" android:focusable="false" android:screenReaderFocusable="false" android:importantForAccessibility="no" android:contentDescription="@null" android:scaleType="centerCrop"/> </FrameLayout> <ImageButton android:id="@+id/expand_menu_button" android:layout_width="16dp" Loading libs/WindowManager/Shell/res/values/dimen.xml +9 −0 Original line number Diff line number Diff line Loading @@ -661,6 +661,15 @@ <!-- The horizontal inset to apply to the close button's ripple drawable --> <dimen name="desktop_mode_header_close_ripple_inset_horizontal">6dp</dimen> <!-- The max width of the app name shown on the app header --> <dimen name="desktop_mode_header_app_name_max_width">130dp</dimen> <!-- The width of the fadeout effect applied to a long app name shown on the app header --> <dimen name="desktop_mode_header_app_name_fadeout_width">48dp</dimen> <!-- The width of the expand menu error image on the app header --> <dimen name="desktop_mode_header_expand_menu_error_image_width">16dp</dimen> <!-- The margin added between app name and expand menu error image on the app header --> <dimen name="desktop_mode_header_expand_menu_error_image_margin">8dp</dimen> <!-- The padding added to all sides of windowing education tooltip --> <dimen name="desktop_windowing_education_tooltip_padding">8dp</dimen> Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +91 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,13 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color import android.graphics.LinearGradient import android.graphics.Rect import android.graphics.Shader import android.os.Bundle import android.view.View import android.view.View.OnLongClickListener import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo Loading @@ -48,6 +51,7 @@ import com.android.internal.R.color.materialColorSecondaryContainer import com.android.internal.R.color.materialColorSurfaceContainerHigh import com.android.internal.R.color.materialColorSurfaceContainerLow import com.android.internal.R.color.materialColorSurfaceDim import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_ACTION_MAXIMIZE_RESTORE Loading @@ -66,6 +70,7 @@ import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.createBackgroundDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance import kotlin.math.roundToInt /** * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts Loading Loading @@ -104,6 +109,30 @@ class AppHeaderViewHolder( private val headerButtonsRippleRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_buttons_ripple_radius) /** * The max width of the app name shown on the app header. **/ private val appNameMaxWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_app_name_max_width) /** * The width of the fadeout effect applied to a long app name shown on the app header. **/ private val appNameFadeoutWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_app_name_fadeout_width) /** * The width of the expand menu error image on the app header. **/ private val expandMenuErrorImageWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_expand_menu_error_image_width) /** * The margin added between app name and expand menu error image on the app header. **/ private val expandMenuErrorImageMargin = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_expand_menu_error_image_margin) /** * The app chip, minimize, maximize and close button's height extends to the top & bottom edges * of the header, and their width may be larger than their height. This is by design to increase Loading Loading @@ -147,6 +176,9 @@ class AppHeaderViewHolder( private val minimizeWindowButton: ImageButton = rootView.requireViewById(R.id.minimize_window) private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name) private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon) private val expandMenuErrorImageView: ImageView = rootView.requireViewById(R.id.expand_menu_error) val appNameTextWidth: Int get() = appNameTextView.width Loading Loading @@ -349,6 +381,60 @@ class AppHeaderViewHolder( a11yTextRestore = context.getString(R.string.restore_button_text, name) updateMaximizeButtonContentDescription() updateAppNameLayoutAndEffect() } private fun updateAppNameLayoutAndEffect() { if (!Flags.enableRestartMenuForConnectedDisplays()) return appNameTextView.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { appNameTextView.viewTreeObserver.removeOnPreDrawListener(this) val errorIconWidth = expandMenuErrorImageWidth + expandMenuErrorImageMargin val textWidth = appNameTextView.paint.measureText(appNameTextView.text.toString()) .roundToInt() val isRestartMenuShown = currentTaskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove // Adjust the right padding of the app text so the error icon will be placed // properly. In case the text is short enough, the padding will be // |errorIconWidth| so the error icon will look like being placed to the right // of the text. Otherwise, the error icon will overlap with the text. val errorIconPadding = if (isRestartMenuShown && textWidth <= appNameMaxWidth) { minOf(appNameMaxWidth - textWidth, errorIconWidth) } else { 0 } appNameTextView.setPaddingRelative(0, 0, errorIconPadding, 0) // In case the app text (and the error icon) is too long to fit in the app // header, fade out the text by applying the custom shader. val availableWidth = if (isRestartMenuShown) { appNameMaxWidth - errorIconWidth } else { appNameMaxWidth } if (textWidth > availableWidth) { val textColor = appNameTextView.currentTextColor val transparentColor = Color.argb( 0, Color.red(textColor), Color.green(textColor), Color.blue(textColor) ) appNameTextView.paint.shader = LinearGradient( (availableWidth - appNameFadeoutWidth).toFloat(), 0f, availableWidth.toFloat(), 0f, textColor, transparentColor, Shader.TileMode.CLAMP ) } return true } }) } private fun updateMaximizeButtonContentDescription() { Loading Loading @@ -412,6 +498,7 @@ class AppHeaderViewHolder( minimizeWindowButton.imageAlpha = alpha closeWindowButton.imageAlpha = alpha expandMenuButton.imageAlpha = alpha expandMenuErrorImageView.imageAlpha = alpha context.withStyledAttributes( set = null, attrs = intArrayOf( Loading Loading @@ -467,6 +554,9 @@ class AppHeaderViewHolder( drawableInsets = appChipDrawableInsets, ) expandMenuButton.imageTintList = colorStateList expandMenuErrorImageView.visibility = if (currentTaskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove) View.VISIBLE else View.GONE appNameTextView.apply { isVisible = header.type == Header.Type.DEFAULT setTextColor(colorStateList) Loading Loading @@ -524,6 +614,7 @@ class AppHeaderViewHolder( } } updateMaximizeButtonContentDescription() updateAppNameLayoutAndEffect() } // Close button. closeWindowButton.apply { Loading Loading
libs/WindowManager/Shell/res/drawable/expand_menu_error.xml 0 → 100644 +31 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2025 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:viewportWidth="960" android:viewportHeight="960"> <path android:fillColor="#FFB4AB" android:pathData="M480,0 A480,480 0 1,1 480,960 A480,480 0 1,1 480,0Z" /> <path android:fillColor="#000000" android:fillType="evenOdd" android:pathData="M480,680Q497,680 508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680ZM440,520L520,520L520,280L440,280L440,520Z" /> </vector>
libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +35 −14 Original line number Diff line number Diff line Loading @@ -43,23 +43,44 @@ android:focusable="false" android:scaleType="centerCrop"/> <FrameLayout android:id="@+id/app_name_layout" android:layout_marginStart="8dp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"> <TextView android:id="@+id/application_name" android:layout_width="0dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxWidth="130dp" android:maxWidth="@dimen/desktop_mode_header_app_name_max_width" android:textSize="14sp" android:textFontWeight="500" android:lineHeight="20sp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginStart="8dp" android:layout_gravity="start|center_vertical" android:singleLine="true" android:ellipsize="end" android:ellipsize="none" android:clickable="false" android:focusable="false" tools:text="Gmail"/> <ImageView android:id="@+id/expand_menu_error" android:src="@drawable/expand_menu_error" android:layout_width="@dimen/desktop_mode_header_expand_menu_error_image_width" android:layout_height="@dimen/desktop_mode_header_expand_menu_error_image_width" android:layout_gravity="end|center_vertical" android:layout_marginStart="@dimen/desktop_mode_header_expand_menu_error_image_margin" android:clickable="false" android:focusable="false" android:screenReaderFocusable="false" android:importantForAccessibility="no" android:contentDescription="@null" android:scaleType="centerCrop"/> </FrameLayout> <ImageButton android:id="@+id/expand_menu_button" android:layout_width="16dp" Loading
libs/WindowManager/Shell/res/values/dimen.xml +9 −0 Original line number Diff line number Diff line Loading @@ -661,6 +661,15 @@ <!-- The horizontal inset to apply to the close button's ripple drawable --> <dimen name="desktop_mode_header_close_ripple_inset_horizontal">6dp</dimen> <!-- The max width of the app name shown on the app header --> <dimen name="desktop_mode_header_app_name_max_width">130dp</dimen> <!-- The width of the fadeout effect applied to a long app name shown on the app header --> <dimen name="desktop_mode_header_app_name_fadeout_width">48dp</dimen> <!-- The width of the expand menu error image on the app header --> <dimen name="desktop_mode_header_expand_menu_error_image_width">16dp</dimen> <!-- The margin added between app name and expand menu error image on the app header --> <dimen name="desktop_mode_header_expand_menu_error_image_margin">8dp</dimen> <!-- The padding added to all sides of windowing education tooltip --> <dimen name="desktop_windowing_education_tooltip_padding">8dp</dimen> Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +91 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,13 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color import android.graphics.LinearGradient import android.graphics.Rect import android.graphics.Shader import android.os.Bundle import android.view.View import android.view.View.OnLongClickListener import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo Loading @@ -48,6 +51,7 @@ import com.android.internal.R.color.materialColorSecondaryContainer import com.android.internal.R.color.materialColorSurfaceContainerHigh import com.android.internal.R.color.materialColorSurfaceContainerLow import com.android.internal.R.color.materialColorSurfaceDim import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_ACTION_MAXIMIZE_RESTORE Loading @@ -66,6 +70,7 @@ import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.createBackgroundDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance import kotlin.math.roundToInt /** * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts Loading Loading @@ -104,6 +109,30 @@ class AppHeaderViewHolder( private val headerButtonsRippleRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_buttons_ripple_radius) /** * The max width of the app name shown on the app header. **/ private val appNameMaxWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_app_name_max_width) /** * The width of the fadeout effect applied to a long app name shown on the app header. **/ private val appNameFadeoutWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_app_name_fadeout_width) /** * The width of the expand menu error image on the app header. **/ private val expandMenuErrorImageWidth = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_expand_menu_error_image_width) /** * The margin added between app name and expand menu error image on the app header. **/ private val expandMenuErrorImageMargin = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_expand_menu_error_image_margin) /** * The app chip, minimize, maximize and close button's height extends to the top & bottom edges * of the header, and their width may be larger than their height. This is by design to increase Loading Loading @@ -147,6 +176,9 @@ class AppHeaderViewHolder( private val minimizeWindowButton: ImageButton = rootView.requireViewById(R.id.minimize_window) private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name) private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon) private val expandMenuErrorImageView: ImageView = rootView.requireViewById(R.id.expand_menu_error) val appNameTextWidth: Int get() = appNameTextView.width Loading Loading @@ -349,6 +381,60 @@ class AppHeaderViewHolder( a11yTextRestore = context.getString(R.string.restore_button_text, name) updateMaximizeButtonContentDescription() updateAppNameLayoutAndEffect() } private fun updateAppNameLayoutAndEffect() { if (!Flags.enableRestartMenuForConnectedDisplays()) return appNameTextView.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { appNameTextView.viewTreeObserver.removeOnPreDrawListener(this) val errorIconWidth = expandMenuErrorImageWidth + expandMenuErrorImageMargin val textWidth = appNameTextView.paint.measureText(appNameTextView.text.toString()) .roundToInt() val isRestartMenuShown = currentTaskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove // Adjust the right padding of the app text so the error icon will be placed // properly. In case the text is short enough, the padding will be // |errorIconWidth| so the error icon will look like being placed to the right // of the text. Otherwise, the error icon will overlap with the text. val errorIconPadding = if (isRestartMenuShown && textWidth <= appNameMaxWidth) { minOf(appNameMaxWidth - textWidth, errorIconWidth) } else { 0 } appNameTextView.setPaddingRelative(0, 0, errorIconPadding, 0) // In case the app text (and the error icon) is too long to fit in the app // header, fade out the text by applying the custom shader. val availableWidth = if (isRestartMenuShown) { appNameMaxWidth - errorIconWidth } else { appNameMaxWidth } if (textWidth > availableWidth) { val textColor = appNameTextView.currentTextColor val transparentColor = Color.argb( 0, Color.red(textColor), Color.green(textColor), Color.blue(textColor) ) appNameTextView.paint.shader = LinearGradient( (availableWidth - appNameFadeoutWidth).toFloat(), 0f, availableWidth.toFloat(), 0f, textColor, transparentColor, Shader.TileMode.CLAMP ) } return true } }) } private fun updateMaximizeButtonContentDescription() { Loading Loading @@ -412,6 +498,7 @@ class AppHeaderViewHolder( minimizeWindowButton.imageAlpha = alpha closeWindowButton.imageAlpha = alpha expandMenuButton.imageAlpha = alpha expandMenuErrorImageView.imageAlpha = alpha context.withStyledAttributes( set = null, attrs = intArrayOf( Loading Loading @@ -467,6 +554,9 @@ class AppHeaderViewHolder( drawableInsets = appChipDrawableInsets, ) expandMenuButton.imageTintList = colorStateList expandMenuErrorImageView.visibility = if (currentTaskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove) View.VISIBLE else View.GONE appNameTextView.apply { isVisible = header.type == Header.Type.DEFAULT setTextColor(colorStateList) Loading Loading @@ -524,6 +614,7 @@ class AppHeaderViewHolder( } } updateMaximizeButtonContentDescription() updateAppNameLayoutAndEffect() } // Close button. closeWindowButton.apply { Loading