1 /* 2 * Copyright (C) 2015 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.systemui.statusbar.notification.row.wrapper; 18 19 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; 20 21 import android.app.AppOpsManager; 22 import android.app.Notification; 23 import android.content.Context; 24 import android.util.ArraySet; 25 import android.view.NotificationHeaderView; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.view.animation.Interpolator; 29 import android.view.animation.PathInterpolator; 30 import android.widget.FrameLayout; 31 import android.widget.ImageView; 32 import android.widget.TextView; 33 34 import com.android.internal.widget.CachingIconView; 35 import com.android.internal.widget.NotificationExpandButton; 36 import com.android.settingslib.Utils; 37 import com.android.systemui.Interpolators; 38 import com.android.systemui.R; 39 import com.android.systemui.statusbar.TransformableView; 40 import com.android.systemui.statusbar.ViewTransformationHelper; 41 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation; 42 import com.android.systemui.statusbar.notification.ImageTransformState; 43 import com.android.systemui.statusbar.notification.NotificationUtils; 44 import com.android.systemui.statusbar.notification.TransformState; 45 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 46 47 import java.util.Stack; 48 49 /** 50 * Wraps a notification header view. 51 */ 52 public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 53 54 private static final Interpolator LOW_PRIORITY_HEADER_CLOSE 55 = new PathInterpolator(0.4f, 0f, 0.7f, 1f); 56 57 protected final ViewTransformationHelper mTransformationHelper; 58 59 protected int mColor; 60 61 private CachingIconView mIcon; 62 private NotificationExpandButton mExpandButton; 63 protected NotificationHeaderView mNotificationHeader; 64 private TextView mHeaderText; 65 private TextView mAppNameText; 66 private ImageView mWorkProfileImage; 67 private View mCameraIcon; 68 private View mMicIcon; 69 private View mOverlayIcon; 70 private View mAppOps; 71 private View mAudiblyAlertedIcon; 72 private FrameLayout mIconContainer; 73 74 private boolean mIsLowPriority; 75 private boolean mTransformLowPriorityTitle; 76 private boolean mShowExpandButtonAtEnd; 77 NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row)78 protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { 79 super(ctx, view, row); 80 mShowExpandButtonAtEnd = ctx.getResources().getBoolean( 81 R.bool.config_showNotificationExpandButtonAtEnd) 82 || NotificationUtils.useNewInterruptionModel(ctx); 83 mTransformationHelper = new ViewTransformationHelper(); 84 85 // we want to avoid that the header clashes with the other text when transforming 86 // low-priority 87 mTransformationHelper.setCustomTransformation( 88 new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) { 89 90 @Override 91 public Interpolator getCustomInterpolator(int interpolationType, 92 boolean isFrom) { 93 boolean isLowPriority = mView instanceof NotificationHeaderView; 94 if (interpolationType == TRANSFORM_Y) { 95 if (isLowPriority && !isFrom 96 || !isLowPriority && isFrom) { 97 return Interpolators.LINEAR_OUT_SLOW_IN; 98 } else { 99 return LOW_PRIORITY_HEADER_CLOSE; 100 } 101 } 102 return null; 103 } 104 105 @Override 106 protected boolean hasCustomTransformation() { 107 return mIsLowPriority && mTransformLowPriorityTitle; 108 } 109 }, TRANSFORMING_VIEW_TITLE); 110 resolveHeaderViews(); 111 addAppOpsOnClickListener(row); 112 } 113 resolveHeaderViews()114 protected void resolveHeaderViews() { 115 mIconContainer = mView.findViewById(com.android.internal.R.id.header_icon_container); 116 mIcon = mView.findViewById(com.android.internal.R.id.icon); 117 mHeaderText = mView.findViewById(com.android.internal.R.id.header_text); 118 mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text); 119 mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); 120 mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); 121 mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); 122 mCameraIcon = mView.findViewById(com.android.internal.R.id.camera); 123 mMicIcon = mView.findViewById(com.android.internal.R.id.mic); 124 mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay); 125 mAppOps = mView.findViewById(com.android.internal.R.id.app_ops); 126 mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); 127 if (mNotificationHeader != null) { 128 mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd); 129 mColor = mNotificationHeader.getOriginalIconColor(); 130 } 131 } 132 addAppOpsOnClickListener(ExpandableNotificationRow row)133 private void addAppOpsOnClickListener(ExpandableNotificationRow row) { 134 View.OnClickListener listener = row.getAppOpsOnClickListener(); 135 if (mNotificationHeader != null) { 136 mNotificationHeader.setAppOpsOnClickListener(listener); 137 } 138 if (mAppOps != null) { 139 mAppOps.setOnClickListener(listener); 140 } 141 } 142 143 /** 144 * Shows or hides 'app op in use' icons based on app usage. 145 */ 146 @Override showAppOpsIcons(ArraySet<Integer> appOps)147 public void showAppOpsIcons(ArraySet<Integer> appOps) { 148 if (appOps == null) { 149 return; 150 } 151 if (mOverlayIcon != null) { 152 mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) 153 ? View.VISIBLE : View.GONE); 154 } 155 if (mCameraIcon != null) { 156 mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA) 157 ? View.VISIBLE : View.GONE); 158 } 159 if (mMicIcon != null) { 160 mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO) 161 ? View.VISIBLE : View.GONE); 162 } 163 } 164 165 @Override onContentUpdated(ExpandableNotificationRow row)166 public void onContentUpdated(ExpandableNotificationRow row) { 167 super.onContentUpdated(row); 168 mIsLowPriority = row.getEntry().isAmbient(); 169 mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren(); 170 ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews(); 171 172 // Reinspect the notification. 173 resolveHeaderViews(); 174 updateTransformedTypes(); 175 addRemainingTransformTypes(); 176 updateCropToPaddingForImageViews(); 177 Notification notification = row.getEntry().getSbn().getNotification(); 178 mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); 179 180 // We need to reset all views that are no longer transforming in case a view was previously 181 // transformed, but now we decided to transform its container instead. 182 ArraySet<View> currentViews = mTransformationHelper.getAllTransformingViews(); 183 for (int i = 0; i < previousViews.size(); i++) { 184 View view = previousViews.valueAt(i); 185 if (!currentViews.contains(view)) { 186 mTransformationHelper.resetTransformedView(view); 187 } 188 } 189 } 190 applyConversationSkin()191 public void applyConversationSkin() { 192 if (mAppNameText != null) { 193 mAppNameText.setTextAppearance( 194 com.android.internal.R.style 195 .TextAppearance_DeviceDefault_Notification_Conversation_AppName); 196 ViewGroup.MarginLayoutParams layoutParams = 197 (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); 198 layoutParams.setMarginStart(0); 199 } 200 if (mIconContainer != null) { 201 ViewGroup.MarginLayoutParams layoutParams = 202 (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); 203 layoutParams.width = 204 mIconContainer.getContext().getResources().getDimensionPixelSize( 205 com.android.internal.R.dimen.conversation_content_start); 206 final int marginStart = 207 mIconContainer.getContext().getResources().getDimensionPixelSize( 208 com.android.internal.R.dimen.notification_content_margin_start); 209 layoutParams.setMarginStart(marginStart * -1); 210 } 211 if (mIcon != null) { 212 ViewGroup.MarginLayoutParams layoutParams = 213 (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); 214 layoutParams.setMarginEnd(0); 215 } 216 } 217 clearConversationSkin()218 public void clearConversationSkin() { 219 if (mAppNameText != null) { 220 final int textAppearance = Utils.getThemeAttr( 221 mAppNameText.getContext(), 222 com.android.internal.R.attr.notificationHeaderTextAppearance, 223 com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info); 224 mAppNameText.setTextAppearance(textAppearance); 225 ViewGroup.MarginLayoutParams layoutParams = 226 (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams(); 227 final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize( 228 com.android.internal.R.dimen.notification_header_app_name_margin_start); 229 layoutParams.setMarginStart(marginStart); 230 } 231 if (mIconContainer != null) { 232 ViewGroup.MarginLayoutParams layoutParams = 233 (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams(); 234 layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 235 layoutParams.setMarginStart(0); 236 } 237 if (mIcon != null) { 238 ViewGroup.MarginLayoutParams layoutParams = 239 (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); 240 final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize( 241 com.android.internal.R.dimen.notification_header_icon_margin_end); 242 layoutParams.setMarginEnd(marginEnd); 243 } 244 } 245 246 /** 247 * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each 248 * child is faded automatically and doesn't have to be manually added. 249 * The keys used for the views are the ids. 250 */ addRemainingTransformTypes()251 private void addRemainingTransformTypes() { 252 mTransformationHelper.addRemainingTransformTypes(mView); 253 } 254 255 /** 256 * Since we are deactivating the clipping when transforming the ImageViews don't get clipped 257 * anymore during these transitions. We can avoid that by using 258 * {@link ImageView#setCropToPadding(boolean)} on all ImageViews. 259 */ updateCropToPaddingForImageViews()260 private void updateCropToPaddingForImageViews() { 261 Stack<View> stack = new Stack<>(); 262 stack.push(mView); 263 while (!stack.isEmpty()) { 264 View child = stack.pop(); 265 if (child instanceof ImageView 266 // Skip the importance ring for conversations, disabled cropping is needed for 267 // its animation 268 && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) { 269 ((ImageView) child).setCropToPadding(true); 270 } else if (child instanceof ViewGroup){ 271 ViewGroup group = (ViewGroup) child; 272 for (int i = 0; i < group.getChildCount(); i++) { 273 stack.push(group.getChildAt(i)); 274 } 275 } 276 } 277 } 278 updateTransformedTypes()279 protected void updateTransformedTypes() { 280 mTransformationHelper.reset(); 281 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, 282 mIcon); 283 mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage); 284 if (mIsLowPriority && mHeaderText != null) { 285 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, 286 mHeaderText); 287 } 288 if (mCameraIcon != null) { 289 mTransformationHelper.addViewTransformingToSimilar(mCameraIcon); 290 } 291 if (mMicIcon != null) { 292 mTransformationHelper.addViewTransformingToSimilar(mMicIcon); 293 } 294 if (mOverlayIcon != null) { 295 mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon); 296 } 297 if (mAudiblyAlertedIcon != null) { 298 mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon); 299 } 300 } 301 302 @Override updateExpandability(boolean expandable, View.OnClickListener onClickListener)303 public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { 304 mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); 305 if (mNotificationHeader != null) { 306 mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); 307 } 308 } 309 310 @Override setRecentlyAudiblyAlerted(boolean audiblyAlerted)311 public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { 312 if (mAudiblyAlertedIcon != null) { 313 mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE); 314 } 315 } 316 317 @Override getNotificationHeader()318 public NotificationHeaderView getNotificationHeader() { 319 return mNotificationHeader; 320 } 321 322 @Override getExpandButton()323 public View getExpandButton() { 324 return mExpandButton; 325 } 326 327 @Override getOriginalIconColor()328 public int getOriginalIconColor() { 329 return mIcon.getOriginalIconColor(); 330 } 331 332 @Override getShelfTransformationTarget()333 public View getShelfTransformationTarget() { 334 return mIcon; 335 } 336 337 @Override setShelfIconVisible(boolean visible)338 public void setShelfIconVisible(boolean visible) { 339 super.setShelfIconVisible(visible); 340 mIcon.setForceHidden(visible); 341 } 342 343 @Override getCurrentState(int fadingView)344 public TransformState getCurrentState(int fadingView) { 345 return mTransformationHelper.getCurrentState(fadingView); 346 } 347 348 @Override transformTo(TransformableView notification, Runnable endRunnable)349 public void transformTo(TransformableView notification, Runnable endRunnable) { 350 mTransformationHelper.transformTo(notification, endRunnable); 351 } 352 353 @Override transformTo(TransformableView notification, float transformationAmount)354 public void transformTo(TransformableView notification, float transformationAmount) { 355 mTransformationHelper.transformTo(notification, transformationAmount); 356 } 357 358 @Override transformFrom(TransformableView notification)359 public void transformFrom(TransformableView notification) { 360 mTransformationHelper.transformFrom(notification); 361 } 362 363 @Override transformFrom(TransformableView notification, float transformationAmount)364 public void transformFrom(TransformableView notification, float transformationAmount) { 365 mTransformationHelper.transformFrom(notification, transformationAmount); 366 } 367 368 @Override setIsChildInGroup(boolean isChildInGroup)369 public void setIsChildInGroup(boolean isChildInGroup) { 370 super.setIsChildInGroup(isChildInGroup); 371 mTransformLowPriorityTitle = !isChildInGroup; 372 } 373 374 @Override setVisible(boolean visible)375 public void setVisible(boolean visible) { 376 super.setVisible(visible); 377 mTransformationHelper.setVisible(visible); 378 } 379 } 380