1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.internal.widget; 18 19 import static android.app.Flags.notificationsRedesignTemplates; 20 21 import android.annotation.ColorInt; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.graphics.drawable.LayerDrawable; 28 import android.util.AttributeSet; 29 import android.view.RemotableViewMethod; 30 import android.view.ViewGroup; 31 import android.view.accessibility.AccessibilityNodeInfo; 32 import android.widget.Button; 33 import android.widget.FrameLayout; 34 import android.widget.ImageView; 35 import android.widget.LinearLayout; 36 import android.widget.RemoteViews; 37 import android.widget.TextView; 38 39 import com.android.internal.R; 40 41 import java.util.Locale; 42 43 /** 44 * An expand button in a notification 45 */ 46 @RemoteViews.RemoteView 47 public class NotificationExpandButton extends FrameLayout { 48 49 private Drawable mPillDrawable; 50 private TextView mNumberView; 51 private ImageView mIconView; 52 private LinearLayout mPillView; 53 private boolean mExpanded; 54 private int mNumber; 55 private int mDefaultPillColor; 56 private int mDefaultTextColor; 57 private int mHighlightPillColor; 58 private int mHighlightTextColor; 59 NotificationExpandButton(Context context)60 public NotificationExpandButton(Context context) { 61 this(context, null, 0, 0); 62 } 63 NotificationExpandButton(Context context, @Nullable AttributeSet attrs)64 public NotificationExpandButton(Context context, @Nullable AttributeSet attrs) { 65 this(context, attrs, 0, 0); 66 } 67 NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr)68 public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, 69 int defStyleAttr) { 70 this(context, attrs, defStyleAttr, 0); 71 } 72 NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)73 public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 74 int defStyleRes) { 75 super(context, attrs, defStyleAttr, defStyleRes); 76 } 77 78 @Override onFinishInflate()79 protected void onFinishInflate() { 80 super.onFinishInflate(); 81 82 mPillView = findViewById(R.id.expand_button_pill); 83 final LayerDrawable layeredPill = (LayerDrawable) mPillView.getBackground(); 84 mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer); 85 mNumberView = findViewById(R.id.expand_button_number); 86 mIconView = findViewById(R.id.expand_button_icon); 87 } 88 89 /** 90 * Show the touchable area of the view for a11y. 91 * If the parent is the touch container, then that view's bounds are the touchable area. 92 */ 93 @Override getBoundsOnScreen(Rect outRect, boolean clipToParent)94 public void getBoundsOnScreen(Rect outRect, boolean clipToParent) { 95 ViewGroup parent = (ViewGroup) getParent(); 96 if (parent != null && parent.getId() == R.id.expand_button_touch_container) { 97 parent.getBoundsOnScreen(outRect, clipToParent); 98 } else { 99 super.getBoundsOnScreen(outRect, clipToParent); 100 } 101 } 102 103 /** 104 * Determined if the given point should be touchable. 105 * If the parent is the touch container, then any point in that view should be touchable. 106 */ 107 @Override pointInView(float localX, float localY, float slop)108 public boolean pointInView(float localX, float localY, float slop) { 109 ViewGroup parent = (ViewGroup) getParent(); 110 if (parent != null && parent.getId() == R.id.expand_button_touch_container) { 111 // If our parent is checking with us, then the point must be within its bounds. 112 return true; 113 } 114 return super.pointInView(localX, localY, slop); 115 } 116 117 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)118 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 119 super.onInitializeAccessibilityNodeInfo(info); 120 info.setClassName(Button.class.getName()); 121 } 122 123 /** 124 * Update the button's drawable, content description, and color for the given expanded state. 125 */ 126 @RemotableViewMethod setExpanded(boolean expanded)127 public void setExpanded(boolean expanded) { 128 mExpanded = expanded; 129 updateExpandedState(); 130 } 131 132 /** 133 * Adjust the padding at the start of the view based on the layout direction (RTL/LTR). 134 * This is needed because RemoteViews don't have an equivalent for 135 * {@link this#setPaddingRelative}. 136 */ 137 @RemotableViewMethod setStartPadding(int startPadding)138 public void setStartPadding(int startPadding) { 139 setPaddingRelative(startPadding, getPaddingTop(), getPaddingEnd(), getPaddingBottom()); 140 } 141 updateExpandedState()142 private void updateExpandedState() { 143 int drawableId; 144 int contentDescriptionId; 145 if (mExpanded) { 146 if (notificationsRedesignTemplates()) { 147 drawableId = R.drawable.ic_notification_2025_collapse; 148 } else { 149 drawableId = R.drawable.ic_collapse_notification; 150 } 151 contentDescriptionId = R.string.expand_button_content_description_expanded; 152 } else { 153 if (notificationsRedesignTemplates()) { 154 drawableId = R.drawable.ic_notification_2025_expand; 155 } else { 156 drawableId = R.drawable.ic_expand_notification; 157 } 158 contentDescriptionId = R.string.expand_button_content_description_collapsed; 159 } 160 setContentDescription(mContext.getText(contentDescriptionId)); 161 mIconView.setImageDrawable(getContext().getDrawable(drawableId)); 162 163 if (!notificationsRedesignTemplates()) { 164 // changing the expanded state can affect the number display 165 updateNumber(); 166 } 167 } 168 updateNumber()169 private void updateNumber() { 170 if (shouldShowNumber()) { 171 CharSequence text = mNumber >= 100 172 ? getResources().getString(R.string.unread_convo_overflow, 99) 173 : String.format(Locale.getDefault(), "%d", mNumber); 174 mNumberView.setText(text); 175 mNumberView.setVisibility(VISIBLE); 176 } else { 177 mNumberView.setVisibility(GONE); 178 } 179 180 // changing number can affect the color and padding 181 updateColors(); 182 updatePadding(); 183 } 184 updatePadding()185 private void updatePadding() { 186 if (!notificationsRedesignTemplates()) { 187 return; 188 } 189 190 // Reduce the padding at the end when showing the number, since the arrow icon has more 191 // inherent spacing than the number does. This makes the content look more centered. 192 // Vertical padding remains unchanged. 193 int reducedPadding = getResources().getDimensionPixelSize( 194 R.dimen.notification_2025_expand_button_reduced_end_padding); 195 int normalPadding = getResources().getDimensionPixelSize( 196 R.dimen.notification_2025_expand_button_horizontal_icon_padding); 197 mPillView.setPaddingRelative( 198 /* start = */ normalPadding, 199 /* top = */ mPillView.getPaddingTop(), 200 /* end = */ shouldShowNumber() ? reducedPadding : normalPadding, 201 /* bottom = */ mPillView.getPaddingBottom() 202 ); 203 } 204 updateColors()205 private void updateColors() { 206 if (shouldShowNumber()) { 207 if (mHighlightPillColor != 0) { 208 mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor)); 209 } 210 mIconView.setColorFilter(mHighlightTextColor); 211 if (mHighlightTextColor != 0) { 212 mNumberView.setTextColor(mHighlightTextColor); 213 } 214 } else { 215 if (mDefaultPillColor != 0) { 216 mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor)); 217 } 218 mIconView.setColorFilter(mDefaultTextColor); 219 if (mDefaultTextColor != 0) { 220 mNumberView.setTextColor(mDefaultTextColor); 221 } 222 } 223 } 224 shouldShowNumber()225 private boolean shouldShowNumber() { 226 if (notificationsRedesignTemplates()) { 227 return mNumber > 1; 228 } 229 return !mExpanded && mNumber > 1; 230 } 231 232 /** 233 * Set the color used for the expand chevron and the text 234 */ 235 @RemotableViewMethod setDefaultTextColor(int color)236 public void setDefaultTextColor(int color) { 237 mDefaultTextColor = color; 238 updateColors(); 239 } 240 241 /** 242 * Sets the color used to for the expander when there is no number shown 243 */ 244 @RemotableViewMethod setDefaultPillColor(@olorInt int color)245 public void setDefaultPillColor(@ColorInt int color) { 246 mDefaultPillColor = color; 247 updateColors(); 248 } 249 250 /** 251 * Set the color used for the expand chevron and the text 252 */ 253 @RemotableViewMethod setHighlightTextColor(int color)254 public void setHighlightTextColor(int color) { 255 mHighlightTextColor = color; 256 updateColors(); 257 } 258 259 /** 260 * Sets the color used to highlight the expander when there is a number shown 261 */ 262 @RemotableViewMethod setHighlightPillColor(@olorInt int color)263 public void setHighlightPillColor(@ColorInt int color) { 264 mHighlightPillColor = color; 265 updateColors(); 266 } 267 268 /** 269 * Sets the number shown inside the expand button. 270 * This only appears when {@link this#shouldShowNumber()} is true. 271 */ 272 @RemotableViewMethod setNumber(int number)273 public void setNumber(int number) { 274 if (mNumber != number) { 275 mNumber = number; 276 updateNumber(); 277 } 278 } 279 } 280