1/* 2 * Copyright (C) 2011 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.recent; 18 19import android.animation.LayoutTransition; 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.graphics.Canvas; 23import android.graphics.LinearGradient; 24import android.graphics.Matrix; 25import android.graphics.Paint; 26import android.graphics.Shader; 27import android.graphics.drawable.Drawable; 28import android.util.AttributeSet; 29import android.view.View; 30import android.view.ViewConfiguration; 31import android.view.ViewGroup; 32import android.widget.LinearLayout; 33 34import com.android.systemui.R; 35 36public class RecentsScrollViewPerformanceHelper { 37 public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true; 38 public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true; 39 private View mScrollView; 40 private LinearLayout mLinearLayout; 41 private RecentsCallback mCallback; 42 43 private boolean mShowBackground = false; 44 private int mFadingEdgeLength; 45 private Drawable.ConstantState mBackgroundDrawable; 46 private Context mContext; 47 private boolean mIsVertical; 48 private boolean mFirstTime = true; 49 private boolean mSoftwareRendered = false; 50 private boolean mAttachedToWindow = false; 51 52 public static RecentsScrollViewPerformanceHelper create(Context context, 53 AttributeSet attrs, View scrollView, boolean isVertical) { 54 boolean isTablet = context.getResources(). 55 getBoolean(R.bool.config_recents_interface_for_tablets); 56 if (!isTablet && (OPTIMIZE_SW_RENDERED_RECENTS || USE_DARK_FADE_IN_HW_ACCELERATED_MODE)) { 57 return new RecentsScrollViewPerformanceHelper(context, attrs, scrollView, isVertical); 58 } else { 59 return null; 60 } 61 } 62 63 public RecentsScrollViewPerformanceHelper(Context context, 64 AttributeSet attrs, View scrollView, boolean isVertical) { 65 mScrollView = scrollView; 66 mContext = context; 67 TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View); 68 mFadingEdgeLength = a.getDimensionPixelSize(android.R.styleable.View_fadingEdgeLength, 69 ViewConfiguration.get(context).getScaledFadingEdgeLength()); 70 mIsVertical = isVertical; 71 } 72 73 public void onAttachedToWindowCallback( 74 RecentsCallback callback, LinearLayout layout, boolean hardwareAccelerated) { 75 mSoftwareRendered = !hardwareAccelerated; 76 if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) 77 || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) { 78 mScrollView.setVerticalFadingEdgeEnabled(false); 79 mScrollView.setHorizontalFadingEdgeEnabled(false); 80 } 81 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 82 mCallback = callback; 83 mLinearLayout = layout; 84 mAttachedToWindow = true; 85 mBackgroundDrawable = mContext.getResources() 86 .getDrawable(R.drawable.status_bar_recents_background_solid).getConstantState(); 87 updateShowBackground(); 88 } 89 90 } 91 92 public void addViewCallback(View newLinearLayoutChild) { 93 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 94 final View view = newLinearLayoutChild; 95 if (mShowBackground) { 96 view.setBackgroundDrawable(mBackgroundDrawable.newDrawable()); 97 view.setDrawingCacheEnabled(true); 98 view.buildDrawingCache(); 99 } else { 100 view.setBackgroundDrawable(null); 101 view.setDrawingCacheEnabled(false); 102 view.destroyDrawingCache(); 103 } 104 } 105 } 106 107 public void onLayoutCallback() { 108 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 109 mScrollView.post(new Runnable() { 110 public void run() { 111 updateShowBackground(); 112 } 113 }); 114 } 115 } 116 117 public void drawCallback(Canvas canvas, 118 int left, int right, int top, int bottom, int scrollX, int scrollY, 119 float topFadingEdgeStrength, float bottomFadingEdgeStrength, 120 float leftFadingEdgeStrength, float rightFadingEdgeStrength) { 121 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 122 if (mIsVertical) { 123 if (scrollY < 0) { 124 Drawable d = mBackgroundDrawable.newDrawable().getCurrent(); 125 d.setBounds(0, scrollY, mScrollView.getWidth(), 0); 126 d.draw(canvas); 127 } else { 128 final int childHeight = mLinearLayout.getHeight(); 129 if (scrollY + mScrollView.getHeight() > childHeight) { 130 Drawable d = mBackgroundDrawable.newDrawable().getCurrent(); 131 d.setBounds(0, childHeight, mScrollView.getWidth(), 132 scrollY + mScrollView.getHeight()); 133 d.draw(canvas); 134 } 135 } 136 } else { 137 if (scrollX < 0) { 138 Drawable d = mBackgroundDrawable.newDrawable().getCurrent(); 139 d.setBounds(scrollX, 0, 0, mScrollView.getHeight()); 140 d.draw(canvas); 141 } else { 142 final int childWidth = mLinearLayout.getWidth(); 143 if (scrollX + mScrollView.getWidth() > childWidth) { 144 Drawable d = mBackgroundDrawable.newDrawable().getCurrent(); 145 d.setBounds(childWidth, 0, 146 scrollX + mScrollView.getWidth(), mScrollView.getHeight()); 147 d.draw(canvas); 148 } 149 } 150 } 151 } 152 153 if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) 154 || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) { 155 Paint p = new Paint(); 156 Matrix matrix = new Matrix(); 157 // use use a height of 1, and then wack the matrix each time we 158 // actually use it. 159 Shader fade = new LinearGradient(0, 0, 0, 1, 0xCC000000, 0, Shader.TileMode.CLAMP); 160 // PULL OUT THIS CONSTANT 161 162 p.setShader(fade); 163 164 // draw the fade effect 165 boolean drawTop = false; 166 boolean drawBottom = false; 167 boolean drawLeft = false; 168 boolean drawRight = false; 169 170 float topFadeStrength = 0.0f; 171 float bottomFadeStrength = 0.0f; 172 float leftFadeStrength = 0.0f; 173 float rightFadeStrength = 0.0f; 174 175 final float fadeHeight = mFadingEdgeLength; 176 int length = (int) fadeHeight; 177 178 // clip the fade length if top and bottom fades overlap 179 // overlapping fades produce odd-looking artifacts 180 if (mIsVertical && (top + length > bottom - length)) { 181 length = (bottom - top) / 2; 182 } 183 184 // also clip horizontal fades if necessary 185 if (!mIsVertical && (left + length > right - length)) { 186 length = (right - left) / 2; 187 } 188 189 if (mIsVertical) { 190 topFadeStrength = Math.max(0.0f, Math.min(1.0f, topFadingEdgeStrength)); 191 drawTop = topFadeStrength * fadeHeight > 1.0f; 192 bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, bottomFadingEdgeStrength)); 193 drawBottom = bottomFadeStrength * fadeHeight > 1.0f; 194 } 195 196 if (!mIsVertical) { 197 leftFadeStrength = Math.max(0.0f, Math.min(1.0f, leftFadingEdgeStrength)); 198 drawLeft = leftFadeStrength * fadeHeight > 1.0f; 199 rightFadeStrength = Math.max(0.0f, Math.min(1.0f, rightFadingEdgeStrength)); 200 drawRight = rightFadeStrength * fadeHeight > 1.0f; 201 } 202 203 if (drawTop) { 204 matrix.setScale(1, fadeHeight * topFadeStrength); 205 matrix.postTranslate(left, top); 206 fade.setLocalMatrix(matrix); 207 canvas.drawRect(left, top, right, top + length, p); 208 } 209 210 if (drawBottom) { 211 matrix.setScale(1, fadeHeight * bottomFadeStrength); 212 matrix.postRotate(180); 213 matrix.postTranslate(left, bottom); 214 fade.setLocalMatrix(matrix); 215 canvas.drawRect(left, bottom - length, right, bottom, p); 216 } 217 218 if (drawLeft) { 219 matrix.setScale(1, fadeHeight * leftFadeStrength); 220 matrix.postRotate(-90); 221 matrix.postTranslate(left, top); 222 fade.setLocalMatrix(matrix); 223 canvas.drawRect(left, top, left + length, bottom, p); 224 } 225 226 if (drawRight) { 227 matrix.setScale(1, fadeHeight * rightFadeStrength); 228 matrix.postRotate(90); 229 matrix.postTranslate(right, top); 230 fade.setLocalMatrix(matrix); 231 canvas.drawRect(right - length, top, right, bottom, p); 232 } 233 } 234 } 235 236 public int getVerticalFadingEdgeLengthCallback() { 237 return mFadingEdgeLength; 238 } 239 240 public int getHorizontalFadingEdgeLengthCallback() { 241 return mFadingEdgeLength; 242 } 243 244 public void setLayoutTransitionCallback(LayoutTransition transition) { 245 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 246 if (transition != null) { 247 transition.addTransitionListener(new LayoutTransition.TransitionListener() { 248 @Override 249 public void startTransition(LayoutTransition transition, 250 ViewGroup container, View view, int transitionType) { 251 updateShowBackground(); 252 } 253 254 @Override 255 public void endTransition(LayoutTransition transition, 256 ViewGroup container, View view, int transitionType) { 257 updateShowBackground(); 258 } 259 }); 260 } 261 } 262 } 263 264 // Turn on/off drawing the background in our ancestor, and turn on/off drawing 265 // in the items in LinearLayout contained by this scrollview. 266 // Moving the background drawing to our children, and turning on a drawing cache 267 // for each of them, gives us a ~20fps gain when Recents is rendered in software 268 public void updateShowBackground() { 269 if (!mAttachedToWindow) { 270 // We haven't been initialized yet-- we'll get called again when we are 271 return; 272 } 273 if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) { 274 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 275 int linearLayoutSize = 276 mIsVertical ? mLinearLayout.getHeight() : mLinearLayout.getWidth(); 277 int scrollViewSize = 278 mIsVertical ? mScrollView.getHeight() : mScrollView.getWidth(); 279 boolean show = !mScrollView.isHardwareAccelerated() && 280 (linearLayoutSize > scrollViewSize) && 281 !(transition != null && transition.isRunning()) && 282 mCallback.isRecentsVisible(); 283 284 if (!mFirstTime && show == mShowBackground) return; 285 mShowBackground = show; 286 mFirstTime = false; 287 288 mCallback.handleShowBackground(!show); 289 for (int i = 0; i < mLinearLayout.getChildCount(); i++) { 290 View v = mLinearLayout.getChildAt(i); 291 if (show) { 292 v.setBackgroundDrawable(mBackgroundDrawable.newDrawable()); 293 v.setDrawingCacheEnabled(true); 294 v.buildDrawingCache(); 295 } else { 296 v.setDrawingCacheEnabled(false); 297 v.destroyDrawingCache(); 298 v.setBackgroundDrawable(null); 299 } 300 } 301 } 302 } 303 304} 305