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