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 17 package com.android.systemui.statusbar; 18 19 import android.content.Context; 20 import android.graphics.ColorFilter; 21 import android.graphics.ColorMatrix; 22 import android.graphics.ColorMatrixColorFilter; 23 import android.graphics.Paint; 24 import android.graphics.PorterDuff; 25 import android.graphics.PorterDuffXfermode; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.util.AttributeSet; 29 import android.view.View; 30 import android.view.ViewTreeObserver; 31 import android.view.animation.Interpolator; 32 import android.view.animation.LinearInterpolator; 33 import android.widget.FrameLayout; 34 import android.widget.ImageView; 35 import com.android.systemui.R; 36 37 /** 38 * A frame layout containing the actual payload of the notification, including the contracted and 39 * expanded layout. This class is responsible for clipping the content and and switching between the 40 * expanded and contracted view depending on its clipped size. 41 */ 42 public class NotificationContentView extends FrameLayout { 43 44 private static final long ANIMATION_DURATION_LENGTH = 170; 45 46 private final Rect mClipBounds = new Rect(); 47 48 private View mContractedChild; 49 private View mExpandedChild; 50 51 private NotificationViewWrapper mContractedWrapper; 52 53 private int mSmallHeight; 54 private int mClipTopAmount; 55 private int mActualHeight; 56 57 private final Interpolator mLinearInterpolator = new LinearInterpolator(); 58 59 private boolean mContractedVisible = true; 60 private boolean mDark; 61 62 private final Paint mFadePaint = new Paint(); 63 private boolean mAnimate; 64 private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener 65 = new ViewTreeObserver.OnPreDrawListener() { 66 @Override 67 public boolean onPreDraw() { 68 mAnimate = true; 69 getViewTreeObserver().removeOnPreDrawListener(this); 70 return true; 71 } 72 }; 73 74 public NotificationContentView(Context context, AttributeSet attrs) { 75 super(context, attrs); 76 mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 77 reset(true); 78 } 79 80 @Override 81 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 82 super.onLayout(changed, left, top, right, bottom); 83 updateClipping(); 84 } 85 86 @Override 87 protected void onAttachedToWindow() { 88 super.onAttachedToWindow(); 89 updateVisibility(); 90 } 91 92 public void reset(boolean resetActualHeight) { 93 if (mContractedChild != null) { 94 mContractedChild.animate().cancel(); 95 } 96 if (mExpandedChild != null) { 97 mExpandedChild.animate().cancel(); 98 } 99 removeAllViews(); 100 mContractedChild = null; 101 mExpandedChild = null; 102 mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 103 mContractedVisible = true; 104 if (resetActualHeight) { 105 mActualHeight = mSmallHeight; 106 } 107 } 108 109 public View getContractedChild() { 110 return mContractedChild; 111 } 112 113 public View getExpandedChild() { 114 return mExpandedChild; 115 } 116 117 public void setContractedChild(View child) { 118 if (mContractedChild != null) { 119 mContractedChild.animate().cancel(); 120 removeView(mContractedChild); 121 } 122 sanitizeContractedLayoutParams(child); 123 addView(child); 124 mContractedChild = child; 125 mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child); 126 selectLayout(false /* animate */, true /* force */); 127 mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); 128 } 129 130 public void setExpandedChild(View child) { 131 if (mExpandedChild != null) { 132 mExpandedChild.animate().cancel(); 133 removeView(mExpandedChild); 134 } 135 addView(child); 136 mExpandedChild = child; 137 selectLayout(false /* animate */, true /* force */); 138 } 139 140 @Override 141 protected void onVisibilityChanged(View changedView, int visibility) { 142 super.onVisibilityChanged(changedView, visibility); 143 updateVisibility(); 144 } 145 146 private void updateVisibility() { 147 setVisible(isShown()); 148 } 149 150 private void setVisible(final boolean isVisible) { 151 if (isVisible) { 152 153 // We only animate if we are drawn at least once, otherwise the view might animate when 154 // it's shown the first time 155 getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener); 156 } else { 157 getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener); 158 mAnimate = false; 159 } 160 } 161 162 public void setActualHeight(int actualHeight) { 163 mActualHeight = actualHeight; 164 selectLayout(mAnimate /* animate */, false /* force */); 165 updateClipping(); 166 } 167 168 public int getMaxHeight() { 169 170 // The maximum height is just the laid out height. 171 return getHeight(); 172 } 173 174 public int getMinHeight() { 175 return mSmallHeight; 176 } 177 178 public void setClipTopAmount(int clipTopAmount) { 179 mClipTopAmount = clipTopAmount; 180 updateClipping(); 181 } 182 183 private void updateClipping() { 184 mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); 185 setClipBounds(mClipBounds); 186 } 187 188 private void sanitizeContractedLayoutParams(View contractedChild) { 189 LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); 190 lp.height = mSmallHeight; 191 contractedChild.setLayoutParams(lp); 192 } 193 194 private void selectLayout(boolean animate, boolean force) { 195 if (mContractedChild == null) { 196 return; 197 } 198 boolean showContractedChild = showContractedChild(); 199 if (showContractedChild != mContractedVisible || force) { 200 if (animate && mExpandedChild != null) { 201 runSwitchAnimation(showContractedChild); 202 } else if (mExpandedChild != null) { 203 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE); 204 mContractedChild.setAlpha(showContractedChild ? 1f : 0f); 205 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE); 206 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f); 207 } 208 } 209 mContractedVisible = showContractedChild; 210 } 211 212 private void runSwitchAnimation(final boolean showContractedChild) { 213 mContractedChild.setVisibility(View.VISIBLE); 214 mExpandedChild.setVisibility(View.VISIBLE); 215 mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 216 mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 217 setLayerType(LAYER_TYPE_HARDWARE, null); 218 mContractedChild.animate() 219 .alpha(showContractedChild ? 1f : 0f) 220 .setDuration(ANIMATION_DURATION_LENGTH) 221 .setInterpolator(mLinearInterpolator); 222 mExpandedChild.animate() 223 .alpha(showContractedChild ? 0f : 1f) 224 .setDuration(ANIMATION_DURATION_LENGTH) 225 .setInterpolator(mLinearInterpolator) 226 .withEndAction(new Runnable() { 227 @Override 228 public void run() { 229 mContractedChild.setLayerType(LAYER_TYPE_NONE, null); 230 mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); 231 setLayerType(LAYER_TYPE_NONE, null); 232 mContractedChild.setVisibility(showContractedChild 233 ? View.VISIBLE 234 : View.INVISIBLE); 235 mExpandedChild.setVisibility(showContractedChild 236 ? View.INVISIBLE 237 : View.VISIBLE); 238 } 239 }); 240 } 241 242 private boolean showContractedChild() { 243 return mActualHeight <= mSmallHeight || mExpandedChild == null; 244 } 245 246 public void notifyContentUpdated() { 247 selectLayout(false /* animate */, true /* force */); 248 if (mContractedChild != null) { 249 mContractedWrapper.notifyContentUpdated(); 250 mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */); 251 } 252 } 253 254 public boolean isContentExpandable() { 255 return mExpandedChild != null; 256 } 257 258 public void setDark(boolean dark, boolean fade, long delay) { 259 if (mDark == dark || mContractedChild == null) return; 260 mDark = dark; 261 mContractedWrapper.setDark(dark, fade, delay); 262 } 263 264 @Override 265 public boolean hasOverlappingRendering() { 266 267 // This is not really true, but good enough when fading from the contracted to the expanded 268 // layout, and saves us some layers. 269 return false; 270 } 271 } 272