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.animation.Interpolator; 31 import android.view.animation.LinearInterpolator; 32 import android.widget.FrameLayout; 33 import android.widget.ImageView; 34 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 private static final Paint INVERT_PAINT = createInvertPaint(); 46 private static final ColorFilter NO_COLOR_FILTER = new ColorFilter(); 47 48 private final Rect mClipBounds = new Rect(); 49 50 private View mContractedChild; 51 private View mExpandedChild; 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 NotificationContentView(Context context, AttributeSet attrs)64 public NotificationContentView(Context context, AttributeSet attrs) { 65 super(context, attrs); 66 mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 67 reset(); 68 } 69 70 @Override onLayout(boolean changed, int left, int top, int right, int bottom)71 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 72 super.onLayout(changed, left, top, right, bottom); 73 updateClipping(); 74 } 75 reset()76 public void reset() { 77 if (mContractedChild != null) { 78 mContractedChild.animate().cancel(); 79 } 80 if (mExpandedChild != null) { 81 mExpandedChild.animate().cancel(); 82 } 83 removeAllViews(); 84 mContractedChild = null; 85 mExpandedChild = null; 86 mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 87 mActualHeight = mSmallHeight; 88 mContractedVisible = true; 89 } 90 getContractedChild()91 public View getContractedChild() { 92 return mContractedChild; 93 } 94 getExpandedChild()95 public View getExpandedChild() { 96 return mExpandedChild; 97 } 98 setContractedChild(View child)99 public void setContractedChild(View child) { 100 if (mContractedChild != null) { 101 mContractedChild.animate().cancel(); 102 removeView(mContractedChild); 103 } 104 sanitizeContractedLayoutParams(child); 105 addView(child); 106 mContractedChild = child; 107 selectLayout(false /* animate */, true /* force */); 108 } 109 setExpandedChild(View child)110 public void setExpandedChild(View child) { 111 if (mExpandedChild != null) { 112 mExpandedChild.animate().cancel(); 113 removeView(mExpandedChild); 114 } 115 addView(child); 116 mExpandedChild = child; 117 selectLayout(false /* animate */, true /* force */); 118 } 119 setActualHeight(int actualHeight)120 public void setActualHeight(int actualHeight) { 121 mActualHeight = actualHeight; 122 selectLayout(true /* animate */, false /* force */); 123 updateClipping(); 124 } 125 getMaxHeight()126 public int getMaxHeight() { 127 128 // The maximum height is just the laid out height. 129 return getHeight(); 130 } 131 getMinHeight()132 public int getMinHeight() { 133 return mSmallHeight; 134 } 135 setClipTopAmount(int clipTopAmount)136 public void setClipTopAmount(int clipTopAmount) { 137 mClipTopAmount = clipTopAmount; 138 updateClipping(); 139 } 140 updateClipping()141 private void updateClipping() { 142 mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); 143 setClipBounds(mClipBounds); 144 } 145 sanitizeContractedLayoutParams(View contractedChild)146 private void sanitizeContractedLayoutParams(View contractedChild) { 147 LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); 148 lp.height = mSmallHeight; 149 contractedChild.setLayoutParams(lp); 150 } 151 selectLayout(boolean animate, boolean force)152 private void selectLayout(boolean animate, boolean force) { 153 if (mContractedChild == null) { 154 return; 155 } 156 boolean showContractedChild = showContractedChild(); 157 if (showContractedChild != mContractedVisible || force) { 158 if (animate && mExpandedChild != null) { 159 runSwitchAnimation(showContractedChild); 160 } else if (mExpandedChild != null) { 161 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE); 162 mContractedChild.setAlpha(showContractedChild ? 1f : 0f); 163 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE); 164 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f); 165 } 166 } 167 mContractedVisible = showContractedChild; 168 } 169 runSwitchAnimation(final boolean showContractedChild)170 private void runSwitchAnimation(final boolean showContractedChild) { 171 mContractedChild.setVisibility(View.VISIBLE); 172 mExpandedChild.setVisibility(View.VISIBLE); 173 mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 174 mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); 175 setLayerType(LAYER_TYPE_HARDWARE, null); 176 mContractedChild.animate() 177 .alpha(showContractedChild ? 1f : 0f) 178 .setDuration(ANIMATION_DURATION_LENGTH) 179 .setInterpolator(mLinearInterpolator); 180 mExpandedChild.animate() 181 .alpha(showContractedChild ? 0f : 1f) 182 .setDuration(ANIMATION_DURATION_LENGTH) 183 .setInterpolator(mLinearInterpolator) 184 .withEndAction(new Runnable() { 185 @Override 186 public void run() { 187 mContractedChild.setLayerType(LAYER_TYPE_NONE, null); 188 mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); 189 setLayerType(LAYER_TYPE_NONE, null); 190 mContractedChild.setVisibility(showContractedChild 191 ? View.VISIBLE 192 : View.INVISIBLE); 193 mExpandedChild.setVisibility(showContractedChild 194 ? View.INVISIBLE 195 : View.VISIBLE); 196 } 197 }); 198 } 199 showContractedChild()200 private boolean showContractedChild() { 201 return mActualHeight <= mSmallHeight || mExpandedChild == null; 202 } 203 notifyContentUpdated()204 public void notifyContentUpdated() { 205 selectLayout(false /* animate */, true /* force */); 206 } 207 isContentExpandable()208 public boolean isContentExpandable() { 209 return mExpandedChild != null; 210 } 211 setDark(boolean dark, boolean fade)212 public void setDark(boolean dark, boolean fade) { 213 if (mDark == dark || mContractedChild == null) return; 214 mDark = dark; 215 setImageViewDark(dark, fade, com.android.internal.R.id.right_icon); 216 setImageViewDark(dark, fade, com.android.internal.R.id.icon); 217 } 218 setImageViewDark(boolean dark, boolean fade, int imageViewId)219 private void setImageViewDark(boolean dark, boolean fade, int imageViewId) { 220 // TODO: implement fade 221 final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId); 222 if (v == null) return; 223 final Drawable d = v.getBackground(); 224 if (dark) { 225 v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT); 226 if (d != null) { 227 v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter() 228 : NO_COLOR_FILTER); 229 d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color), 230 PorterDuff.Mode.SRC_ATOP); 231 v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha)); 232 } 233 } else { 234 v.setLayerType(LAYER_TYPE_NONE, null); 235 if (d != null) { 236 final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag); 237 if (filter != null) { 238 d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter); 239 v.setTag(R.id.doze_saved_filter_tag, null); 240 } 241 v.setImageAlpha(0xff); 242 } 243 } 244 } 245 246 @Override hasOverlappingRendering()247 public boolean hasOverlappingRendering() { 248 249 // This is not really true, but good enough when fading from the contracted to the expanded 250 // layout, and saves us some layers. 251 return false; 252 } 253 createInvertPaint()254 private static Paint createInvertPaint() { 255 final Paint p = new Paint(); 256 final float[] invert = { 257 -1f, 0f, 0f, 1f, 1f, 258 0f, -1f, 0f, 1f, 1f, 259 0f, 0f, -1f, 1f, 1f, 260 0f, 0f, 0f, 1f, 0f 261 }; 262 p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert))); 263 return p; 264 } 265 } 266