1/* 2 * Copyright (C) 2014 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 17package com.android.systemui.statusbar.notification; 18 19import android.content.Context; 20import android.graphics.Color; 21import android.graphics.Rect; 22import android.service.notification.StatusBarNotification; 23import android.view.View; 24import android.widget.ImageView; 25import android.widget.ProgressBar; 26import android.widget.TextView; 27 28import com.android.systemui.statusbar.CrossFadeHelper; 29import com.android.systemui.statusbar.ExpandableNotificationRow; 30import com.android.systemui.statusbar.TransformableView; 31import com.android.systemui.statusbar.ViewTransformationHelper; 32 33/** 34 * Wraps a notification view inflated from a template. 35 */ 36public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper { 37 38 private static final int mDarkProgressTint = 0xffffffff; 39 40 protected ImageView mPicture; 41 private ProgressBar mProgressBar; 42 private TextView mTitle; 43 private TextView mText; 44 private View mActionsContainer; 45 private View mReplyAction; 46 private Rect mTmpRect = new Rect(); 47 48 private int mContentHeight; 49 private int mMinHeightHint; 50 51 protected NotificationTemplateViewWrapper(Context ctx, View view, 52 ExpandableNotificationRow row) { 53 super(ctx, view, row); 54 mTransformationHelper.setCustomTransformation( 55 new ViewTransformationHelper.CustomTransformation() { 56 @Override 57 public boolean transformTo(TransformState ownState, 58 TransformableView notification, final float transformationAmount) { 59 if (!(notification instanceof HybridNotificationView)) { 60 return false; 61 } 62 TransformState otherState = notification.getCurrentState( 63 TRANSFORMING_VIEW_TITLE); 64 final View text = ownState.getTransformedView(); 65 CrossFadeHelper.fadeOut(text, transformationAmount); 66 if (otherState != null) { 67 ownState.transformViewVerticalTo(otherState, this, 68 transformationAmount); 69 otherState.recycle(); 70 } 71 return true; 72 } 73 74 @Override 75 public boolean customTransformTarget(TransformState ownState, 76 TransformState otherState) { 77 float endY = getTransformationY(ownState, otherState); 78 ownState.setTransformationEndY(endY); 79 return true; 80 } 81 82 @Override 83 public boolean transformFrom(TransformState ownState, 84 TransformableView notification, float transformationAmount) { 85 if (!(notification instanceof HybridNotificationView)) { 86 return false; 87 } 88 TransformState otherState = notification.getCurrentState( 89 TRANSFORMING_VIEW_TITLE); 90 final View text = ownState.getTransformedView(); 91 CrossFadeHelper.fadeIn(text, transformationAmount); 92 if (otherState != null) { 93 ownState.transformViewVerticalFrom(otherState, this, 94 transformationAmount); 95 otherState.recycle(); 96 } 97 return true; 98 } 99 100 @Override 101 public boolean initTransformation(TransformState ownState, 102 TransformState otherState) { 103 float startY = getTransformationY(ownState, otherState); 104 ownState.setTransformationStartY(startY); 105 return true; 106 } 107 108 private float getTransformationY(TransformState ownState, 109 TransformState otherState) { 110 int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); 111 int[] ownStablePosition = ownState.getLaidOutLocationOnScreen(); 112 return (otherStablePosition[1] 113 + otherState.getTransformedView().getHeight() 114 - ownStablePosition[1]) * 0.33f; 115 } 116 117 }, TRANSFORMING_VIEW_TEXT); 118 } 119 120 private void resolveTemplateViews(StatusBarNotification notification) { 121 mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon); 122 if (mPicture != null) { 123 mPicture.setTag(ImageTransformState.ICON_TAG, 124 notification.getNotification().getLargeIcon()); 125 } 126 mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title); 127 mText = (TextView) mView.findViewById(com.android.internal.R.id.text); 128 final View progress = mView.findViewById(com.android.internal.R.id.progress); 129 if (progress instanceof ProgressBar) { 130 mProgressBar = (ProgressBar) progress; 131 } else { 132 // It's still a viewstub 133 mProgressBar = null; 134 } 135 mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container); 136 mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action); 137 } 138 139 @Override 140 public boolean disallowSingleClick(float x, float y) { 141 if (mReplyAction != null && mReplyAction.getVisibility() == View.VISIBLE) { 142 if (isOnView(mReplyAction, x, y) || isOnView(mPicture, x, y)) { 143 return true; 144 } 145 } 146 return super.disallowSingleClick(x, y); 147 } 148 149 private boolean isOnView(View view, float x, float y) { 150 View searchView = (View) view.getParent(); 151 while (searchView != null && !(searchView instanceof ExpandableNotificationRow)) { 152 searchView.getHitRect(mTmpRect); 153 x -= mTmpRect.left; 154 y -= mTmpRect.top; 155 searchView = (View) searchView.getParent(); 156 } 157 view.getHitRect(mTmpRect); 158 return mTmpRect.contains((int) x,(int) y); 159 } 160 161 @Override 162 public void onContentUpdated(ExpandableNotificationRow row) { 163 // Reinspect the notification. Before the super call, because the super call also updates 164 // the transformation types and we need to have our values set by then. 165 resolveTemplateViews(row.getStatusBarNotification()); 166 super.onContentUpdated(row); 167 } 168 169 @Override 170 protected void updateInvertHelper() { 171 super.updateInvertHelper(); 172 View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column); 173 if (mainColumn != null) { 174 mInvertHelper.addTarget(mainColumn); 175 } 176 } 177 178 @Override 179 protected void updateTransformedTypes() { 180 // This also clears the existing types 181 super.updateTransformedTypes(); 182 if (mTitle != null) { 183 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, 184 mTitle); 185 } 186 if (mText != null) { 187 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, 188 mText); 189 } 190 if (mPicture != null) { 191 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, 192 mPicture); 193 } 194 if (mProgressBar != null) { 195 mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, 196 mProgressBar); 197 } 198 } 199 200 @Override 201 public void setDark(boolean dark, boolean fade, long delay) { 202 if (dark == mDark && mDarkInitialized) { 203 return; 204 } 205 super.setDark(dark, fade, delay); 206 setPictureDark(dark, fade, delay); 207 setProgressBarDark(dark, fade, delay); 208 } 209 210 private void setProgressBarDark(boolean dark, boolean fade, long delay) { 211 if (mProgressBar != null) { 212 if (fade) { 213 fadeProgressDark(mProgressBar, dark, delay); 214 } else { 215 updateProgressDark(mProgressBar, dark); 216 } 217 } 218 } 219 220 private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) { 221 getDozer().startIntensityAnimation(animation -> { 222 float t = (float) animation.getAnimatedValue(); 223 updateProgressDark(target, t); 224 }, dark, delay, null /* listener */); 225 } 226 227 private void updateProgressDark(ProgressBar target, float intensity) { 228 int color = interpolateColor(mColor, mDarkProgressTint, intensity); 229 target.getIndeterminateDrawable().mutate().setTint(color); 230 target.getProgressDrawable().mutate().setTint(color); 231 } 232 233 private void updateProgressDark(ProgressBar target, boolean dark) { 234 updateProgressDark(target, dark ? 1f : 0f); 235 } 236 237 private void setPictureDark(boolean dark, boolean fade, long delay) { 238 if (mPicture != null) { 239 getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */); 240 } 241 } 242 243 private static int interpolateColor(int source, int target, float t) { 244 int aSource = Color.alpha(source); 245 int rSource = Color.red(source); 246 int gSource = Color.green(source); 247 int bSource = Color.blue(source); 248 int aTarget = Color.alpha(target); 249 int rTarget = Color.red(target); 250 int gTarget = Color.green(target); 251 int bTarget = Color.blue(target); 252 return Color.argb( 253 (int) (aSource * (1f - t) + aTarget * t), 254 (int) (rSource * (1f - t) + rTarget * t), 255 (int) (gSource * (1f - t) + gTarget * t), 256 (int) (bSource * (1f - t) + bTarget * t)); 257 } 258 259 @Override 260 public void setContentHeight(int contentHeight, int minHeightHint) { 261 super.setContentHeight(contentHeight, minHeightHint); 262 263 mContentHeight = contentHeight; 264 mMinHeightHint = minHeightHint; 265 updateActionOffset(); 266 } 267 268 private void updateActionOffset() { 269 if (mActionsContainer != null) { 270 // We should never push the actions higher than they are in the headsup view. 271 int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint); 272 mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight()); 273 } 274 } 275} 276