1/*
2 * Copyright (C) 2012 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 android.support.v4.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffColorFilter;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.os.Build;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.support.v4.view.AccessibilityDelegateCompat;
33import android.support.v4.view.MotionEventCompat;
34import android.support.v4.view.ViewCompat;
35import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
36import android.util.AttributeSet;
37import android.util.Log;
38import android.view.MotionEvent;
39import android.view.View;
40import android.view.ViewConfiguration;
41import android.view.ViewGroup;
42import android.view.ViewParent;
43import android.view.accessibility.AccessibilityEvent;
44
45import java.lang.reflect.Field;
46import java.lang.reflect.Method;
47import java.util.ArrayList;
48
49/**
50 * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level
51 * of a UI. A left (or first) pane is treated as a content list or browser, subordinate to a
52 * primary detail view for displaying content.
53 *
54 * <p>Child views may overlap if their combined width exceeds the available width
55 * in the SlidingPaneLayout. When this occurs the user may slide the topmost view out of the way
56 * by dragging it, or by navigating in the direction of the overlapped view using a keyboard.
57 * If the content of the dragged child view is itself horizontally scrollable, the user may
58 * grab it by the very edge.</p>
59 *
60 * <p>Thanks to this sliding behavior, SlidingPaneLayout may be suitable for creating layouts
61 * that can smoothly adapt across many different screen sizes, expanding out fully on larger
62 * screens and collapsing on smaller screens.</p>
63 *
64 * <p>SlidingPaneLayout is distinct from a navigation drawer as described in the design
65 * guide and should not be used in the same scenarios. SlidingPaneLayout should be thought
66 * of only as a way to allow a two-pane layout normally used on larger screens to adapt to smaller
67 * screens in a natural way. The interaction patterns expressed by SlidingPaneLayout imply
68 * a physicality and direct information hierarchy between panes that does not necessarily exist
69 * in a scenario where a navigation drawer should be used instead.</p>
70 *
71 * <p>Appropriate uses of SlidingPaneLayout include pairings of panes such as a contact list and
72 * subordinate interactions with those contacts, or an email thread list with the content pane
73 * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include
74 * switching between disparate functions of your app, such as jumping from a social stream view
75 * to a view of your personal profile - cases such as this should use the navigation drawer
76 * pattern instead. ({@link DrawerLayout DrawerLayout} implements this pattern.)</p>
77 *
78 * <p>Like {@link android.widget.LinearLayout LinearLayout}, SlidingPaneLayout supports
79 * the use of the layout parameter <code>layout_weight</code> on child views to determine
80 * how to divide leftover space after measurement is complete. It is only relevant for width.
81 * When views do not overlap weight behaves as it does in a LinearLayout.</p>
82 *
83 * <p>When views do overlap, weight on a slideable pane indicates that the pane should be
84 * sized to fill all available space in the closed state. Weight on a pane that becomes covered
85 * indicates that the pane should be sized to fill all available space except a small minimum strip
86 * that the user may use to grab the slideable view and pull it back over into a closed state.</p>
87 */
88public class SlidingPaneLayout extends ViewGroup {
89    private static final String TAG = "SlidingPaneLayout";
90
91    /**
92     * Default size of the overhang for a pane in the open state.
93     * At least this much of a sliding pane will remain visible.
94     * This indicates that there is more content available and provides
95     * a "physical" edge to grab to pull it closed.
96     */
97    private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
98
99    /**
100     * If no fade color is given by default it will fade to 80% gray.
101     */
102    private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
103
104    /**
105     * The fade color used for the sliding panel. 0 = no fading.
106     */
107    private int mSliderFadeColor = DEFAULT_FADE_COLOR;
108
109    /**
110     * Minimum velocity that will be detected as a fling
111     */
112    private static final int MIN_FLING_VELOCITY = 400; // dips per second
113
114    /**
115     * The fade color used for the panel covered by the slider. 0 = no fading.
116     */
117    private int mCoveredFadeColor;
118
119    /**
120     * Drawable used to draw the shadow between panes.
121     */
122    private Drawable mShadowDrawable;
123
124    /**
125     * The size of the overhang in pixels.
126     * This is the minimum section of the sliding panel that will
127     * be visible in the open state to allow for a closing drag.
128     */
129    private final int mOverhangSize;
130
131    /**
132     * True if a panel can slide with the current measurements
133     */
134    private boolean mCanSlide;
135
136    /**
137     * The child view that can slide, if any.
138     */
139    private View mSlideableView;
140
141    /**
142     * How far the panel is offset from its closed position.
143     * range [0, 1] where 0 = closed, 1 = open.
144     */
145    private float mSlideOffset;
146
147    /**
148     * How far the non-sliding panel is parallaxed from its usual position when open.
149     * range [0, 1]
150     */
151    private float mParallaxOffset;
152
153    /**
154     * How far in pixels the slideable panel may move.
155     */
156    private int mSlideRange;
157
158    /**
159     * A panel view is locked into internal scrolling or another condition that
160     * is preventing a drag.
161     */
162    private boolean mIsUnableToDrag;
163
164    /**
165     * Distance in pixels to parallax the fixed pane by when fully closed
166     */
167    private int mParallaxBy;
168
169    private float mInitialMotionX;
170    private float mInitialMotionY;
171
172    private PanelSlideListener mPanelSlideListener;
173
174    private final ViewDragHelper mDragHelper;
175
176    /**
177     * Stores whether or not the pane was open the last time it was slideable.
178     * If open/close operations are invoked this state is modified. Used by
179     * instance state save/restore.
180     */
181    private boolean mPreservedOpenState;
182    private boolean mFirstLayout = true;
183
184    private final Rect mTmpRect = new Rect();
185
186    private final ArrayList<DisableLayerRunnable> mPostedRunnables =
187            new ArrayList<DisableLayerRunnable>();
188
189    static final SlidingPanelLayoutImpl IMPL;
190
191    static {
192        final int deviceVersion = Build.VERSION.SDK_INT;
193        if (deviceVersion >= 17) {
194            IMPL = new SlidingPanelLayoutImplJBMR1();
195        } else if (deviceVersion >= 16) {
196            IMPL = new SlidingPanelLayoutImplJB();
197        } else {
198            IMPL = new SlidingPanelLayoutImplBase();
199        }
200    }
201
202    /**
203     * Listener for monitoring events about sliding panes.
204     */
205    public interface PanelSlideListener {
206        /**
207         * Called when a sliding pane's position changes.
208         * @param panel The child view that was moved
209         * @param slideOffset The new offset of this sliding pane within its range, from 0-1
210         */
211        public void onPanelSlide(View panel, float slideOffset);
212        /**
213         * Called when a sliding pane becomes slid completely open. The pane may or may not
214         * be interactive at this point depending on how much of the pane is visible.
215         * @param panel The child view that was slid to an open position, revealing other panes
216         */
217        public void onPanelOpened(View panel);
218
219        /**
220         * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
221         * to be interactive. It may now obscure other views in the layout.
222         * @param panel The child view that was slid to a closed position
223         */
224        public void onPanelClosed(View panel);
225    }
226
227    /**
228     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
229     * of the listener methods you can extend this instead of implement the full interface.
230     */
231    public static class SimplePanelSlideListener implements PanelSlideListener {
232        @Override
233        public void onPanelSlide(View panel, float slideOffset) {
234        }
235        @Override
236        public void onPanelOpened(View panel) {
237        }
238        @Override
239        public void onPanelClosed(View panel) {
240        }
241    }
242
243    public SlidingPaneLayout(Context context) {
244        this(context, null);
245    }
246
247    public SlidingPaneLayout(Context context, AttributeSet attrs) {
248        this(context, attrs, 0);
249    }
250
251    public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
252        super(context, attrs, defStyle);
253
254        final float density = context.getResources().getDisplayMetrics().density;
255        mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
256
257        final ViewConfiguration viewConfig = ViewConfiguration.get(context);
258
259        setWillNotDraw(false);
260
261        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
262        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
263
264        mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
265        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
266        mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
267    }
268
269    /**
270     * Set a distance to parallax the lower pane by when the upper pane is in its
271     * fully closed state. The lower pane will scroll between this position and
272     * its fully open state.
273     *
274     * @param parallaxBy Distance to parallax by in pixels
275     */
276    public void setParallaxDistance(int parallaxBy) {
277        mParallaxBy = parallaxBy;
278        requestLayout();
279    }
280
281    /**
282     * @return The distance the lower pane will parallax by when the upper pane is fully closed.
283     *
284     * @see #setParallaxDistance(int)
285     */
286    public int getParallaxDistance() {
287        return mParallaxBy;
288    }
289
290    /**
291     * Set the color used to fade the sliding pane out when it is slid most of the way offscreen.
292     *
293     * @param color An ARGB-packed color value
294     */
295    public void setSliderFadeColor(int color) {
296        mSliderFadeColor = color;
297    }
298
299    /**
300     * @return The ARGB-packed color value used to fade the sliding pane
301     */
302    public int getSliderFadeColor() {
303        return mSliderFadeColor;
304    }
305
306    /**
307     * Set the color used to fade the pane covered by the sliding pane out when the pane
308     * will become fully covered in the closed state.
309     *
310     * @param color An ARGB-packed color value
311     */
312    public void setCoveredFadeColor(int color) {
313        mCoveredFadeColor = color;
314    }
315
316    /**
317     * @return The ARGB-packed color value used to fade the fixed pane
318     */
319    public int getCoveredFadeColor() {
320        return mCoveredFadeColor;
321    }
322
323    public void setPanelSlideListener(PanelSlideListener listener) {
324        mPanelSlideListener = listener;
325    }
326
327    void dispatchOnPanelSlide(View panel) {
328        if (mPanelSlideListener != null) {
329            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
330        }
331    }
332
333    void dispatchOnPanelOpened(View panel) {
334        if (mPanelSlideListener != null) {
335            mPanelSlideListener.onPanelOpened(panel);
336        }
337        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
338    }
339
340    void dispatchOnPanelClosed(View panel) {
341        if (mPanelSlideListener != null) {
342            mPanelSlideListener.onPanelClosed(panel);
343        }
344        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
345    }
346
347    void updateObscuredViewsVisibility(View panel) {
348        final int leftBound = getPaddingLeft();
349        final int rightBound = getWidth() - getPaddingRight();
350        final int topBound = getPaddingTop();
351        final int bottomBound = getHeight() - getPaddingBottom();
352        final int left;
353        final int right;
354        final int top;
355        final int bottom;
356        if (panel != null && viewIsOpaque(panel)) {
357            left = panel.getLeft();
358            right = panel.getRight();
359            top = panel.getTop();
360            bottom = panel.getBottom();
361        } else {
362            left = right = top = bottom = 0;
363        }
364
365        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
366            final View child = getChildAt(i);
367
368            if (child == panel) {
369                // There are still more children above the panel but they won't be affected.
370                break;
371            }
372
373            final int clampedChildLeft = Math.max(leftBound, child.getLeft());
374            final int clampedChildTop = Math.max(topBound, child.getTop());
375            final int clampedChildRight = Math.min(rightBound, child.getRight());
376            final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
377            final int vis;
378            if (clampedChildLeft >= left && clampedChildTop >= top &&
379                    clampedChildRight <= right && clampedChildBottom <= bottom) {
380                vis = INVISIBLE;
381            } else {
382                vis = VISIBLE;
383            }
384            child.setVisibility(vis);
385        }
386    }
387
388    void setAllChildrenVisible() {
389        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
390            final View child = getChildAt(i);
391            if (child.getVisibility() == INVISIBLE) {
392                child.setVisibility(VISIBLE);
393            }
394        }
395    }
396
397    private static boolean viewIsOpaque(View v) {
398        if (ViewCompat.isOpaque(v)) return true;
399
400        // View#isOpaque didn't take all valid opaque scrollbar modes into account
401        // before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false
402        // here. On older devices, check the view's background drawable directly as a fallback.
403        if (Build.VERSION.SDK_INT >= 18) return false;
404
405        final Drawable bg = v.getBackground();
406        if (bg != null) {
407            return bg.getOpacity() == PixelFormat.OPAQUE;
408        }
409        return false;
410    }
411
412    @Override
413    protected void onAttachedToWindow() {
414        super.onAttachedToWindow();
415        mFirstLayout = true;
416    }
417
418    @Override
419    protected void onDetachedFromWindow() {
420        super.onDetachedFromWindow();
421        mFirstLayout = true;
422
423        for (int i = 0, count = mPostedRunnables.size(); i < count; i++) {
424            final DisableLayerRunnable dlr = mPostedRunnables.get(i);
425            dlr.run();
426        }
427        mPostedRunnables.clear();
428    }
429
430    @Override
431    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
432        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
433        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
434        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
435        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
436
437        if (widthMode != MeasureSpec.EXACTLY) {
438            if (isInEditMode()) {
439                // Don't crash the layout editor. Consume all of the space if specified
440                // or pick a magic number from thin air otherwise.
441                // TODO Better communication with tools of this bogus state.
442                // It will crash on a real device.
443                if (widthMode == MeasureSpec.AT_MOST) {
444                    widthMode = MeasureSpec.EXACTLY;
445                } else if (widthMode == MeasureSpec.UNSPECIFIED) {
446                    widthMode = MeasureSpec.EXACTLY;
447                    widthSize = 300;
448                }
449            } else {
450                throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
451            }
452        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
453            if (isInEditMode()) {
454                // Don't crash the layout editor. Pick a magic number from thin air instead.
455                // TODO Better communication with tools of this bogus state.
456                // It will crash on a real device.
457                if (heightMode == MeasureSpec.UNSPECIFIED) {
458                    heightMode = MeasureSpec.AT_MOST;
459                    heightSize = 300;
460                }
461            } else {
462                throw new IllegalStateException("Height must not be UNSPECIFIED");
463            }
464        }
465
466        int layoutHeight = 0;
467        int maxLayoutHeight = -1;
468        switch (heightMode) {
469            case MeasureSpec.EXACTLY:
470                layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
471                break;
472            case MeasureSpec.AT_MOST:
473                maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
474                break;
475        }
476
477        float weightSum = 0;
478        boolean canSlide = false;
479        int widthRemaining = widthSize - getPaddingLeft() - getPaddingRight();
480        final int childCount = getChildCount();
481
482        if (childCount > 2) {
483            Log.e(TAG, "onMeasure: More than two child views are not supported.");
484        }
485
486        // We'll find the current one below.
487        mSlideableView = null;
488
489        // First pass. Measure based on child LayoutParams width/height.
490        // Weight will incur a second pass.
491        for (int i = 0; i < childCount; i++) {
492            final View child = getChildAt(i);
493            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
494
495            if (child.getVisibility() == GONE) {
496                lp.dimWhenOffset = false;
497                continue;
498            }
499
500            if (lp.weight > 0) {
501                weightSum += lp.weight;
502
503                // If we have no width, weight is the only contributor to the final size.
504                // Measure this view on the weight pass only.
505                if (lp.width == 0) continue;
506            }
507
508            int childWidthSpec;
509            final int horizontalMargin = lp.leftMargin + lp.rightMargin;
510            if (lp.width == LayoutParams.WRAP_CONTENT) {
511                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin,
512                        MeasureSpec.AT_MOST);
513            } else if (lp.width == LayoutParams.FILL_PARENT) {
514                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin,
515                        MeasureSpec.EXACTLY);
516            } else {
517                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
518            }
519
520            int childHeightSpec;
521            if (lp.height == LayoutParams.WRAP_CONTENT) {
522                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
523            } else if (lp.height == LayoutParams.FILL_PARENT) {
524                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);
525            } else {
526                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
527            }
528
529            child.measure(childWidthSpec, childHeightSpec);
530            final int childWidth = child.getMeasuredWidth();
531            final int childHeight = child.getMeasuredHeight();
532
533            if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) {
534                layoutHeight = Math.min(childHeight, maxLayoutHeight);
535            }
536
537            widthRemaining -= childWidth;
538            canSlide |= lp.slideable = widthRemaining < 0;
539            if (lp.slideable) {
540                mSlideableView = child;
541            }
542        }
543
544        // Resolve weight and make sure non-sliding panels are smaller than the full screen.
545        if (canSlide || weightSum > 0) {
546            final int fixedPanelWidthLimit = widthSize - mOverhangSize;
547
548            for (int i = 0; i < childCount; i++) {
549                final View child = getChildAt(i);
550
551                if (child.getVisibility() == GONE) {
552                    continue;
553                }
554
555                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
556
557                if (child.getVisibility() == GONE) {
558                    continue;
559                }
560
561                final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
562                final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
563                if (canSlide && child != mSlideableView) {
564                    if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) {
565                        // Fixed panels in a sliding configuration should
566                        // be clamped to the fixed panel limit.
567                        final int childHeightSpec;
568                        if (skippedFirstPass) {
569                            // Do initial height measurement if we skipped measuring this view
570                            // the first time around.
571                            if (lp.height == LayoutParams.WRAP_CONTENT) {
572                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
573                                        MeasureSpec.AT_MOST);
574                            } else if (lp.height == LayoutParams.FILL_PARENT) {
575                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
576                                        MeasureSpec.EXACTLY);
577                            } else {
578                                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
579                                        MeasureSpec.EXACTLY);
580                            }
581                        } else {
582                            childHeightSpec = MeasureSpec.makeMeasureSpec(
583                                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
584                        }
585                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
586                                fixedPanelWidthLimit, MeasureSpec.EXACTLY);
587                        child.measure(childWidthSpec, childHeightSpec);
588                    }
589                } else if (lp.weight > 0) {
590                    int childHeightSpec;
591                    if (lp.width == 0) {
592                        // This was skipped the first time; figure out a real height spec.
593                        if (lp.height == LayoutParams.WRAP_CONTENT) {
594                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
595                                    MeasureSpec.AT_MOST);
596                        } else if (lp.height == LayoutParams.FILL_PARENT) {
597                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
598                                    MeasureSpec.EXACTLY);
599                        } else {
600                            childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
601                                    MeasureSpec.EXACTLY);
602                        }
603                    } else {
604                        childHeightSpec = MeasureSpec.makeMeasureSpec(
605                                child.getMeasuredHeight(), MeasureSpec.EXACTLY);
606                    }
607
608                    if (canSlide) {
609                        // Consume available space
610                        final int horizontalMargin = lp.leftMargin + lp.rightMargin;
611                        final int newWidth = widthSize - horizontalMargin;
612                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
613                                newWidth, MeasureSpec.EXACTLY);
614                        if (measuredWidth != newWidth) {
615                            child.measure(childWidthSpec, childHeightSpec);
616                        }
617                    } else {
618                        // Distribute the extra width proportionally similar to LinearLayout
619                        final int widthToDistribute = Math.max(0, widthRemaining);
620                        final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
621                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
622                                measuredWidth + addedWidth, MeasureSpec.EXACTLY);
623                        child.measure(childWidthSpec, childHeightSpec);
624                    }
625                }
626            }
627        }
628
629        setMeasuredDimension(widthSize, layoutHeight);
630        mCanSlide = canSlide;
631        if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
632            // Cancel scrolling in progress, it's no longer relevant.
633            mDragHelper.abort();
634        }
635    }
636
637    @Override
638    protected void onLayout(boolean changed, int l, int t, int r, int b) {
639
640        final int width = r - l;
641        final int paddingLeft = getPaddingLeft();
642        final int paddingRight = getPaddingRight();
643        final int paddingTop = getPaddingTop();
644
645        final int childCount = getChildCount();
646        int xStart = paddingLeft;
647        int nextXStart = xStart;
648
649        if (mFirstLayout) {
650            mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
651        }
652
653        for (int i = 0; i < childCount; i++) {
654            final View child = getChildAt(i);
655
656            if (child.getVisibility() == GONE) {
657                continue;
658            }
659
660            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
661
662            final int childWidth = child.getMeasuredWidth();
663            int offset = 0;
664
665            if (lp.slideable) {
666                final int margin = lp.leftMargin + lp.rightMargin;
667                final int range = Math.min(nextXStart,
668                        width - paddingRight - mOverhangSize) - xStart - margin;
669                mSlideRange = range;
670                lp.dimWhenOffset = xStart + lp.leftMargin + range + childWidth / 2 >
671                        width - paddingRight;
672                xStart += (int) (range * mSlideOffset) + lp.leftMargin;
673            } else if (mCanSlide && mParallaxBy != 0) {
674                offset = (int) ((1 - mSlideOffset) * mParallaxBy);
675                xStart = nextXStart;
676            } else {
677                xStart = nextXStart;
678            }
679
680            final int childLeft = xStart - offset;
681            final int childRight = childLeft + childWidth;
682            final int childTop = paddingTop;
683            final int childBottom = childTop + child.getMeasuredHeight();
684            child.layout(childLeft, paddingTop, childRight, childBottom);
685
686            nextXStart += child.getWidth();
687        }
688
689        if (mFirstLayout) {
690            if (mCanSlide) {
691                if (mParallaxBy != 0) {
692                    parallaxOtherViews(mSlideOffset);
693                }
694                if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) {
695                    dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
696                }
697            } else {
698                // Reset the dim level of all children; it's irrelevant when nothing moves.
699                for (int i = 0; i < childCount; i++) {
700                    dimChildView(getChildAt(i), 0, mSliderFadeColor);
701                }
702            }
703            updateObscuredViewsVisibility(mSlideableView);
704        }
705
706        mFirstLayout = false;
707    }
708
709    @Override
710    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
711        super.onSizeChanged(w, h, oldw, oldh);
712        // Recalculate sliding panes and their details
713        if (w != oldw) {
714            mFirstLayout = true;
715        }
716    }
717
718    @Override
719    public void requestChildFocus(View child, View focused) {
720        super.requestChildFocus(child, focused);
721        if (!isInTouchMode() && !mCanSlide) {
722            mPreservedOpenState = child == mSlideableView;
723        }
724    }
725
726    @Override
727    public boolean onInterceptTouchEvent(MotionEvent ev) {
728        final int action = MotionEventCompat.getActionMasked(ev);
729
730        // Preserve the open state based on the last view that was touched.
731        if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
732            // After the first things will be slideable.
733            final View secondChild = getChildAt(1);
734            if (secondChild != null) {
735                mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
736                        (int) ev.getX(), (int) ev.getY());
737            }
738        }
739
740        if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
741            mDragHelper.cancel();
742            return super.onInterceptTouchEvent(ev);
743        }
744
745        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
746            mDragHelper.cancel();
747            return false;
748        }
749
750        boolean interceptTap = false;
751
752        switch (action) {
753            case MotionEvent.ACTION_DOWN: {
754                mIsUnableToDrag = false;
755                final float x = ev.getX();
756                final float y = ev.getY();
757                mInitialMotionX = x;
758                mInitialMotionY = y;
759
760                if (mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y) &&
761                        isDimmed(mSlideableView)) {
762                    interceptTap = true;
763                }
764                break;
765            }
766
767            case MotionEvent.ACTION_MOVE: {
768                final float x = ev.getX();
769                final float y = ev.getY();
770                final float adx = Math.abs(x - mInitialMotionX);
771                final float ady = Math.abs(y - mInitialMotionY);
772                final int slop = mDragHelper.getTouchSlop();
773                if (adx > slop && ady > adx) {
774                    mDragHelper.cancel();
775                    mIsUnableToDrag = true;
776                    return false;
777                }
778            }
779        }
780
781        final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
782
783        return interceptForDrag || interceptTap;
784    }
785
786    @Override
787    public boolean onTouchEvent(MotionEvent ev) {
788        if (!mCanSlide) {
789            return super.onTouchEvent(ev);
790        }
791
792        mDragHelper.processTouchEvent(ev);
793
794        final int action = ev.getAction();
795        boolean wantTouchEvents = true;
796
797        switch (action & MotionEventCompat.ACTION_MASK) {
798            case MotionEvent.ACTION_DOWN: {
799                final float x = ev.getX();
800                final float y = ev.getY();
801                mInitialMotionX = x;
802                mInitialMotionY = y;
803                break;
804            }
805
806            case MotionEvent.ACTION_UP: {
807                if (isDimmed(mSlideableView)) {
808                    final float x = ev.getX();
809                    final float y = ev.getY();
810                    final float dx = x - mInitialMotionX;
811                    final float dy = y - mInitialMotionY;
812                    final int slop = mDragHelper.getTouchSlop();
813                    if (dx * dx + dy * dy < slop * slop &&
814                            mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)) {
815                        // Taps close a dimmed open pane.
816                        closePane(mSlideableView, 0);
817                        break;
818                    }
819                }
820                break;
821            }
822        }
823
824        return wantTouchEvents;
825    }
826
827    private boolean closePane(View pane, int initialVelocity) {
828        if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
829            mPreservedOpenState = false;
830            return true;
831        }
832        return false;
833    }
834
835    private boolean openPane(View pane, int initialVelocity) {
836        if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
837            mPreservedOpenState = true;
838            return true;
839        }
840        return false;
841    }
842
843    /**
844     * @deprecated Renamed to {@link #openPane()} - this method is going away soon!
845     */
846    @Deprecated
847    public void smoothSlideOpen() {
848        openPane();
849    }
850
851    /**
852     * Open the sliding pane if it is currently slideable. If first layout
853     * has already completed this will animate.
854     *
855     * @return true if the pane was slideable and is now open/in the process of opening
856     */
857    public boolean openPane() {
858        return openPane(mSlideableView, 0);
859    }
860
861    /**
862     * @deprecated Renamed to {@link #closePane()} - this method is going away soon!
863     */
864    @Deprecated
865    public void smoothSlideClosed() {
866        closePane();
867    }
868
869    /**
870     * Close the sliding pane if it is currently slideable. If first layout
871     * has already completed this will animate.
872     *
873     * @return true if the pane was slideable and is now closed/in the process of closing
874     */
875    public boolean closePane() {
876        return closePane(mSlideableView, 0);
877    }
878
879    /**
880     * Check if the layout is completely open. It can be open either because the slider
881     * itself is open revealing the left pane, or if all content fits without sliding.
882     *
883     * @return true if sliding panels are completely open
884     */
885    public boolean isOpen() {
886        return !mCanSlide || mSlideOffset == 1;
887    }
888
889    /**
890     * @return true if content in this layout can be slid open and closed
891     * @deprecated Renamed to {@link #isSlideable()} - this method is going away soon!
892     */
893    @Deprecated
894    public boolean canSlide() {
895        return mCanSlide;
896    }
897
898    /**
899     * Check if the content in this layout cannot fully fit side by side and therefore
900     * the content pane can be slid back and forth.
901     *
902     * @return true if content in this layout can be slid open and closed
903     */
904    public boolean isSlideable() {
905        return mCanSlide;
906    }
907
908    private void onPanelDragged(int newLeft) {
909        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
910        final int leftBound = getPaddingLeft() + lp.leftMargin;
911
912        mSlideOffset = (float) (newLeft - leftBound) / mSlideRange;
913
914        if (mParallaxBy != 0) {
915            parallaxOtherViews(mSlideOffset);
916        }
917
918        if (lp.dimWhenOffset) {
919            dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
920        }
921        dispatchOnPanelSlide(mSlideableView);
922    }
923
924    private void dimChildView(View v, float mag, int fadeColor) {
925        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
926
927        if (mag > 0 && fadeColor != 0) {
928            final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
929            int imag = (int) (baseAlpha * mag);
930            int color = imag << 24 | (fadeColor & 0xffffff);
931            if (lp.dimPaint == null) {
932                lp.dimPaint = new Paint();
933            }
934            lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER));
935            if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) {
936                ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint);
937            }
938            invalidateChildRegion(v);
939        } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) {
940            if (lp.dimPaint != null) {
941                lp.dimPaint.setColorFilter(null);
942            }
943            final DisableLayerRunnable dlr = new DisableLayerRunnable(v);
944            mPostedRunnables.add(dlr);
945            ViewCompat.postOnAnimation(this, dlr);
946        }
947    }
948
949    @Override
950    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
951        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
952        boolean result;
953        final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
954
955        if (mCanSlide && !lp.slideable && mSlideableView != null) {
956            // Clip against the slider; no sense drawing what will immediately be covered.
957            canvas.getClipBounds(mTmpRect);
958            mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
959            canvas.clipRect(mTmpRect);
960        }
961
962        if (Build.VERSION.SDK_INT >= 11) { // HC
963            result = super.drawChild(canvas, child, drawingTime);
964        } else {
965            if (lp.dimWhenOffset && mSlideOffset > 0) {
966                if (!child.isDrawingCacheEnabled()) {
967                    child.setDrawingCacheEnabled(true);
968                }
969                final Bitmap cache = child.getDrawingCache();
970                if (cache != null) {
971                    canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint);
972                    result = false;
973                } else {
974                    Log.e(TAG, "drawChild: child view " + child + " returned null drawing cache");
975                    result = super.drawChild(canvas, child, drawingTime);
976                }
977            } else {
978                if (child.isDrawingCacheEnabled()) {
979                    child.setDrawingCacheEnabled(false);
980                }
981                result = super.drawChild(canvas, child, drawingTime);
982            }
983        }
984
985        canvas.restoreToCount(save);
986
987        return result;
988    }
989
990    private void invalidateChildRegion(View v) {
991        IMPL.invalidateChildRegion(this, v);
992    }
993
994    /**
995     * Smoothly animate mDraggingPane to the target X position within its range.
996     *
997     * @param slideOffset position to animate to
998     * @param velocity initial velocity in case of fling, or 0.
999     */
1000    boolean smoothSlideTo(float slideOffset, int velocity) {
1001        if (!mCanSlide) {
1002            // Nothing to do.
1003            return false;
1004        }
1005
1006        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1007
1008        final int leftBound = getPaddingLeft() + lp.leftMargin;
1009        int x = (int) (leftBound + slideOffset * mSlideRange);
1010
1011        if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) {
1012            setAllChildrenVisible();
1013            ViewCompat.postInvalidateOnAnimation(this);
1014            return true;
1015        }
1016        return false;
1017    }
1018
1019    @Override
1020    public void computeScroll() {
1021        if (mDragHelper.continueSettling(true)) {
1022            if (!mCanSlide) {
1023                mDragHelper.abort();
1024                return;
1025            }
1026
1027            ViewCompat.postInvalidateOnAnimation(this);
1028        }
1029    }
1030
1031    /**
1032     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1033     * during opening/closing.
1034     *
1035     * @param d drawable to use as a shadow
1036     */
1037    public void setShadowDrawable(Drawable d) {
1038        mShadowDrawable = d;
1039    }
1040
1041    /**
1042     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1043     * during opening/closing.
1044     *
1045     * @param resId Resource ID of a drawable to use
1046     */
1047    public void setShadowResource(int resId) {
1048        setShadowDrawable(getResources().getDrawable(resId));
1049    }
1050
1051    @Override
1052    public void draw(Canvas c) {
1053        super.draw(c);
1054
1055        final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
1056        if (shadowView == null || mShadowDrawable == null) {
1057            // No need to draw a shadow if we don't have one.
1058            return;
1059        }
1060
1061        final int shadowWidth = mShadowDrawable.getIntrinsicWidth();
1062        final int right = shadowView.getLeft();
1063        final int top = shadowView.getTop();
1064        final int bottom = shadowView.getBottom();
1065        final int left = right - shadowWidth;
1066        mShadowDrawable.setBounds(left, top, right, bottom);
1067        mShadowDrawable.draw(c);
1068    }
1069
1070    private void parallaxOtherViews(float slideOffset) {
1071        final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
1072        final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0;
1073        final int childCount = getChildCount();
1074        for (int i = 0; i < childCount; i++) {
1075            final View v = getChildAt(i);
1076            if (v == mSlideableView) continue;
1077
1078            final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy);
1079            mParallaxOffset = slideOffset;
1080            final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
1081            final int dx = oldOffset - newOffset;
1082
1083            v.offsetLeftAndRight(dx);
1084
1085            if (dimViews) {
1086                dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor);
1087            }
1088        }
1089    }
1090
1091    /**
1092     * Tests scrollability within child views of v given a delta of dx.
1093     *
1094     * @param v View to test for horizontal scrollability
1095     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1096     *               or just its children (false).
1097     * @param dx Delta scrolled in pixels
1098     * @param x X coordinate of the active touch point
1099     * @param y Y coordinate of the active touch point
1100     * @return true if child views of v can be scrolled by delta of dx.
1101     */
1102    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1103        if (v instanceof ViewGroup) {
1104            final ViewGroup group = (ViewGroup) v;
1105            final int scrollX = v.getScrollX();
1106            final int scrollY = v.getScrollY();
1107            final int count = group.getChildCount();
1108            // Count backwards - let topmost views consume scroll distance first.
1109            for (int i = count - 1; i >= 0; i--) {
1110                // TODO: Add versioned support here for transformed views.
1111                // This will not work for transformed views in Honeycomb+
1112                final View child = group.getChildAt(i);
1113                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1114                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1115                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
1116                                y + scrollY - child.getTop())) {
1117                    return true;
1118                }
1119            }
1120        }
1121
1122        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1123    }
1124
1125    boolean isDimmed(View child) {
1126        if (child == null) {
1127            return false;
1128        }
1129        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1130        return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0;
1131    }
1132
1133    @Override
1134    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1135        return new LayoutParams();
1136    }
1137
1138    @Override
1139    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1140        return p instanceof MarginLayoutParams
1141                ? new LayoutParams((MarginLayoutParams) p)
1142                : new LayoutParams(p);
1143    }
1144
1145    @Override
1146    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1147        return p instanceof LayoutParams && super.checkLayoutParams(p);
1148    }
1149
1150    @Override
1151    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1152        return new LayoutParams(getContext(), attrs);
1153    }
1154
1155    @Override
1156    protected Parcelable onSaveInstanceState() {
1157        Parcelable superState = super.onSaveInstanceState();
1158
1159        SavedState ss = new SavedState(superState);
1160        ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
1161
1162        return ss;
1163    }
1164
1165    @Override
1166    protected void onRestoreInstanceState(Parcelable state) {
1167        SavedState ss = (SavedState) state;
1168        super.onRestoreInstanceState(ss.getSuperState());
1169
1170        if (ss.isOpen) {
1171            openPane();
1172        } else {
1173            closePane();
1174        }
1175        mPreservedOpenState = ss.isOpen;
1176    }
1177
1178    private class DragHelperCallback extends ViewDragHelper.Callback {
1179
1180        @Override
1181        public boolean tryCaptureView(View child, int pointerId) {
1182            if (mIsUnableToDrag) {
1183                return false;
1184            }
1185
1186            return ((LayoutParams) child.getLayoutParams()).slideable;
1187        }
1188
1189        @Override
1190        public void onViewDragStateChanged(int state) {
1191            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1192                if (mSlideOffset == 0) {
1193                    updateObscuredViewsVisibility(mSlideableView);
1194                    dispatchOnPanelClosed(mSlideableView);
1195                    mPreservedOpenState = false;
1196                } else {
1197                    dispatchOnPanelOpened(mSlideableView);
1198                    mPreservedOpenState = true;
1199                }
1200            }
1201        }
1202
1203        @Override
1204        public void onViewCaptured(View capturedChild, int activePointerId) {
1205            // Make all child views visible in preparation for sliding things around
1206            setAllChildrenVisible();
1207        }
1208
1209        @Override
1210        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1211            onPanelDragged(left);
1212            invalidate();
1213        }
1214
1215        @Override
1216        public void onViewReleased(View releasedChild, float xvel, float yvel) {
1217            final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
1218            int left = getPaddingLeft() + lp.leftMargin;
1219            if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
1220                left += mSlideRange;
1221            }
1222            mDragHelper.settleCapturedViewAt(left, releasedChild.getTop());
1223            invalidate();
1224        }
1225
1226        @Override
1227        public int getViewHorizontalDragRange(View child) {
1228            return mSlideRange;
1229        }
1230
1231        @Override
1232        public int clampViewPositionHorizontal(View child, int left, int dx) {
1233            final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1234            final int leftBound = getPaddingLeft() + lp.leftMargin;
1235            final int rightBound = leftBound + mSlideRange;
1236
1237            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
1238
1239            return newLeft;
1240        }
1241
1242        @Override
1243        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1244            mDragHelper.captureChildView(mSlideableView, pointerId);
1245        }
1246    }
1247
1248    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1249        private static final int[] ATTRS = new int[] {
1250            android.R.attr.layout_weight
1251        };
1252
1253        /**
1254         * The weighted proportion of how much of the leftover space
1255         * this child should consume after measurement.
1256         */
1257        public float weight = 0;
1258
1259        /**
1260         * True if this pane is the slideable pane in the layout.
1261         */
1262        boolean slideable;
1263
1264        /**
1265         * True if this view should be drawn dimmed
1266         * when it's been offset from its default position.
1267         */
1268        boolean dimWhenOffset;
1269
1270        Paint dimPaint;
1271
1272        public LayoutParams() {
1273            super(FILL_PARENT, FILL_PARENT);
1274        }
1275
1276        public LayoutParams(int width, int height) {
1277            super(width, height);
1278        }
1279
1280        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1281            super(source);
1282        }
1283
1284        public LayoutParams(MarginLayoutParams source) {
1285            super(source);
1286        }
1287
1288        public LayoutParams(LayoutParams source) {
1289            super(source);
1290            this.weight = source.weight;
1291        }
1292
1293        public LayoutParams(Context c, AttributeSet attrs) {
1294            super(c, attrs);
1295
1296            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1297            this.weight = a.getFloat(0, 0);
1298            a.recycle();
1299        }
1300
1301    }
1302
1303    static class SavedState extends BaseSavedState {
1304        boolean isOpen;
1305
1306        SavedState(Parcelable superState) {
1307            super(superState);
1308        }
1309
1310        private SavedState(Parcel in) {
1311            super(in);
1312            isOpen = in.readInt() != 0;
1313        }
1314
1315        @Override
1316        public void writeToParcel(Parcel out, int flags) {
1317            super.writeToParcel(out, flags);
1318            out.writeInt(isOpen ? 1 : 0);
1319        }
1320
1321        public static final Parcelable.Creator<SavedState> CREATOR =
1322                new Parcelable.Creator<SavedState>() {
1323            public SavedState createFromParcel(Parcel in) {
1324                return new SavedState(in);
1325            }
1326
1327            public SavedState[] newArray(int size) {
1328                return new SavedState[size];
1329            }
1330        };
1331    }
1332
1333    interface SlidingPanelLayoutImpl {
1334        void invalidateChildRegion(SlidingPaneLayout parent, View child);
1335    }
1336
1337    static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
1338        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1339            ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
1340                    child.getRight(), child.getBottom());
1341        }
1342    }
1343
1344    static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
1345        /*
1346         * Private API hacks! Nasty! Bad!
1347         *
1348         * In Jellybean, some optimizations in the hardware UI renderer
1349         * prevent a changed Paint on a View using a hardware layer from having
1350         * the intended effect. This twiddles some internal bits on the view to force
1351         * it to recreate the display list.
1352         */
1353        private Method mGetDisplayList;
1354        private Field mRecreateDisplayList;
1355
1356        SlidingPanelLayoutImplJB() {
1357            try {
1358                mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
1359            } catch (NoSuchMethodException e) {
1360                Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
1361            }
1362            try {
1363                mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
1364                mRecreateDisplayList.setAccessible(true);
1365            } catch (NoSuchFieldException e) {
1366                Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
1367            }
1368        }
1369
1370        @Override
1371        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1372            if (mGetDisplayList != null && mRecreateDisplayList != null) {
1373                try {
1374                    mRecreateDisplayList.setBoolean(child, true);
1375                    mGetDisplayList.invoke(child, (Object[]) null);
1376                } catch (Exception e) {
1377                    Log.e(TAG, "Error refreshing display list state", e);
1378                }
1379            } else {
1380                // Slow path. REALLY slow path. Let's hope we don't get here.
1381                child.invalidate();
1382                return;
1383            }
1384            super.invalidateChildRegion(parent, child);
1385        }
1386    }
1387
1388    static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
1389        @Override
1390        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1391            ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
1392        }
1393    }
1394
1395    class AccessibilityDelegate extends AccessibilityDelegateCompat {
1396        private final Rect mTmpRect = new Rect();
1397
1398        @Override
1399        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1400            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1401            super.onInitializeAccessibilityNodeInfo(host, superNode);
1402            copyNodeInfoNoChildren(info, superNode);
1403            superNode.recycle();
1404
1405            info.setClassName(SlidingPaneLayout.class.getName());
1406            info.setSource(host);
1407
1408            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1409            if (parent instanceof View) {
1410                info.setParent((View) parent);
1411            }
1412
1413            // This is a best-approximation of addChildrenForAccessibility()
1414            // that accounts for filtering.
1415            final int childCount = getChildCount();
1416            for (int i = 0; i < childCount; i++) {
1417                final View child = getChildAt(i);
1418                if (!filter(child) && (child.getVisibility() == View.VISIBLE)) {
1419                    // Force importance to "yes" since we can't read the value.
1420                    ViewCompat.setImportantForAccessibility(
1421                            child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1422                    info.addChild(child);
1423                }
1424            }
1425        }
1426
1427        @Override
1428        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1429            super.onInitializeAccessibilityEvent(host, event);
1430
1431            event.setClassName(SlidingPaneLayout.class.getName());
1432        }
1433
1434        @Override
1435        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1436                AccessibilityEvent event) {
1437            if (!filter(child)) {
1438                return super.onRequestSendAccessibilityEvent(host, child, event);
1439            }
1440            return false;
1441        }
1442
1443        public boolean filter(View child) {
1444            return isDimmed(child);
1445        }
1446
1447        /**
1448         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1449         * seem to be a few elements that are not easily cloneable using the underlying API.
1450         * Leave it private here as it's not general-purpose useful.
1451         */
1452        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1453                AccessibilityNodeInfoCompat src) {
1454            final Rect rect = mTmpRect;
1455
1456            src.getBoundsInParent(rect);
1457            dest.setBoundsInParent(rect);
1458
1459            src.getBoundsInScreen(rect);
1460            dest.setBoundsInScreen(rect);
1461
1462            dest.setVisibleToUser(src.isVisibleToUser());
1463            dest.setPackageName(src.getPackageName());
1464            dest.setClassName(src.getClassName());
1465            dest.setContentDescription(src.getContentDescription());
1466
1467            dest.setEnabled(src.isEnabled());
1468            dest.setClickable(src.isClickable());
1469            dest.setFocusable(src.isFocusable());
1470            dest.setFocused(src.isFocused());
1471            dest.setAccessibilityFocused(src.isAccessibilityFocused());
1472            dest.setSelected(src.isSelected());
1473            dest.setLongClickable(src.isLongClickable());
1474
1475            dest.addAction(src.getActions());
1476
1477            dest.setMovementGranularities(src.getMovementGranularities());
1478        }
1479    }
1480
1481    private class DisableLayerRunnable implements Runnable {
1482        final View mChildView;
1483
1484        DisableLayerRunnable(View childView) {
1485            mChildView = childView;
1486        }
1487
1488        @Override
1489        public void run() {
1490            if (mChildView.getParent() == SlidingPaneLayout.this) {
1491                ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null);
1492                invalidateChildRegion(mChildView);
1493            }
1494            mPostedRunnables.remove(this);
1495        }
1496    }
1497}
1498