Home | History | Annotate | Download | only in widget
      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 
     17 package com.android.dialer.widget;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Canvas;
     22 import android.graphics.PixelFormat;
     23 import android.graphics.Rect;
     24 import android.graphics.drawable.Drawable;
     25 import android.os.Build;
     26 import android.os.Parcel;
     27 import android.os.Parcelable;
     28 import android.support.v4.view.AccessibilityDelegateCompat;
     29 import android.support.v4.view.MotionEventCompat;
     30 import android.support.v4.view.ViewCompat;
     31 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
     32 import android.util.AttributeSet;
     33 import android.util.Log;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.view.ViewConfiguration;
     37 import android.view.ViewGroup;
     38 import android.view.ViewParent;
     39 import android.view.accessibility.AccessibilityEvent;
     40 
     41 /**
     42  * A custom layout that aligns its child views vertically as two panes, and allows for the bottom
     43  * pane to be dragged upwards to overlap and hide the top pane. This layout is adapted from
     44  * {@link android.support.v4.widget.SlidingPaneLayout}.
     45  */
     46 public class OverlappingPaneLayout extends ViewGroup {
     47     private static final String TAG = "SlidingPaneLayout";
     48     private static final boolean DEBUG = false;
     49 
     50     /**
     51      * Default size of the overhang for a pane in the open state.
     52      * At least this much of a sliding pane will remain visible.
     53      * This indicates that there is more content available and provides
     54      * a "physical" edge to grab to pull it closed.
     55      */
     56     private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
     57 
     58     /**
     59      * If no fade color is given by default it will fade to 80% gray.
     60      */
     61     private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
     62 
     63     /**
     64      * Minimum velocity that will be detected as a fling
     65      */
     66     private static final int MIN_FLING_VELOCITY = 400; // dips per second
     67 
     68     /**
     69      * The size of the overhang in pixels.
     70      * This is the minimum section of the sliding panel that will
     71      * be visible in the open state to allow for a closing drag.
     72      */
     73     private final int mOverhangSize;
     74 
     75     /**
     76      * True if a panel can slide with the current measurements
     77      */
     78     private boolean mCanSlide;
     79 
     80     /**
     81      * The child view that can slide, if any.
     82      */
     83     private View mSlideableView;
     84 
     85     /**
     86      * The view that can be used to start the drag with.
     87      */
     88     private View mCapturableView;
     89 
     90     /**
     91      * How far the panel is offset from its closed position.
     92      * range [0, 1] where 0 = closed, 1 = open.
     93      */
     94     private float mSlideOffset;
     95 
     96     /**
     97      * How far the panel is offset from its closed position, in pixels.
     98      * range [0, {@link #mSlideRange}] where 0 is completely closed.
     99      */
    100     private int mSlideOffsetPx;
    101 
    102     /**
    103      * How far in pixels the slideable panel may move.
    104      */
    105     private int mSlideRange;
    106 
    107     /**
    108      * A panel view is locked into internal scrolling or another condition that
    109      * is preventing a drag.
    110      */
    111     private boolean mIsUnableToDrag;
    112 
    113     /**
    114      * Tracks whether or not a child view is in the process of a nested scroll.
    115      */
    116     private boolean mIsInNestedScroll;
    117 
    118     /**
    119      * Indicates that the layout is currently in the process of a nested pre-scroll operation where
    120      * the child scrolling view is being dragged downwards.
    121      */
    122     private boolean mInNestedPreScrollDownwards;
    123 
    124     /**
    125      * Indicates that the layout is currently in the process of a nested pre-scroll operation where
    126      * the child scrolling view is being dragged upwards.
    127      */
    128     private boolean mInNestedPreScrollUpwards;
    129 
    130     /**
    131      * Indicates that the layout is currently in the process of a fling initiated by a pre-fling
    132      * from the child scrolling view.
    133      */
    134     private boolean mIsInNestedFling;
    135 
    136     /**
    137      * Indicates the direction of the pre fling. We need to store this information since
    138      * OverScoller doesn't expose the direction of its velocity.
    139      */
    140     private boolean mInUpwardsPreFling;
    141 
    142     /**
    143      * Stores an offset used to represent a point somewhere in between the panel's fully closed
    144      * state and fully opened state where the panel can be temporarily pinned or opened up to
    145      * during scrolling.
    146      */
    147     private int mIntermediateOffset = 0;
    148 
    149     private float mInitialMotionX;
    150     private float mInitialMotionY;
    151 
    152     private PanelSlideCallbacks mPanelSlideCallbacks;
    153 
    154     private final ViewDragHelper mDragHelper;
    155 
    156     /**
    157      * Stores whether or not the pane was open the last time it was slideable.
    158      * If open/close operations are invoked this state is modified. Used by
    159      * instance state save/restore.
    160      */
    161     private boolean mPreservedOpenState;
    162     private boolean mFirstLayout = true;
    163 
    164     private final Rect mTmpRect = new Rect();
    165 
    166     /**
    167      * How many dips we need to scroll past a position before we can snap to the next position
    168      * on release. Using this prevents accidentally snapping to positions.
    169      *
    170      * This is needed since vertical nested scrolling can be passed to this class even if the
    171      * vertical scroll is less than the the nested list's touch slop.
    172      */
    173     private final int mReleaseScrollSlop;
    174 
    175     /**
    176      * Callbacks for interacting with sliding panes.
    177      */
    178     public interface PanelSlideCallbacks {
    179         /**
    180          * Called when a sliding pane's position changes.
    181          * @param panel The child view that was moved
    182          * @param slideOffset The new offset of this sliding pane within its range, from 0-1
    183          */
    184         public void onPanelSlide(View panel, float slideOffset);
    185         /**
    186          * Called when a sliding pane becomes slid completely open. The pane may or may not
    187          * be interactive at this point depending on how much of the pane is visible.
    188          * @param panel The child view that was slid to an open position, revealing other panes
    189          */
    190         public void onPanelOpened(View panel);
    191 
    192         /**
    193          * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
    194          * to be interactive. It may now obscure other views in the layout.
    195          * @param panel The child view that was slid to a closed position
    196          */
    197         public void onPanelClosed(View panel);
    198 
    199         /**
    200          * Called when a sliding pane is flung as far open/closed as it can be.
    201          * @param velocityY Velocity of the panel once its fling goes as far as it can.
    202          */
    203         public void onPanelFlingReachesEdge(int velocityY);
    204 
    205         /**
    206          * Returns true if the second panel's contents haven't been scrolled at all. This value is
    207          * used to determine whether or not we can fully expand the header on downwards scrolls.
    208          *
    209          * Instead of using this callback, it would be preferable to instead fully expand the header
    210          * on a View#onNestedFlingOver() callback. The behavior would be nicer. Unfortunately,
    211          * no such callback exists yet (b/17547693).
    212          */
    213         public boolean isScrollableChildUnscrolled();
    214     }
    215 
    216     public OverlappingPaneLayout(Context context) {
    217         this(context, null);
    218     }
    219 
    220     public OverlappingPaneLayout(Context context, AttributeSet attrs) {
    221         this(context, attrs, 0);
    222     }
    223 
    224     public OverlappingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
    225         super(context, attrs, defStyle);
    226 
    227         final float density = context.getResources().getDisplayMetrics().density;
    228         mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
    229 
    230         setWillNotDraw(false);
    231 
    232         ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
    233 
    234         mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
    235         mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
    236 
    237         mReleaseScrollSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    238     }
    239 
    240     /**
    241      * Set an offset, somewhere in between the panel's fully closed state and fully opened state,
    242      * where the panel can be temporarily pinned or opened up to.
    243      *
    244      * @param offset Offset in pixels
    245      */
    246     public void setIntermediatePinnedOffset(int offset) {
    247         mIntermediateOffset = offset;
    248     }
    249 
    250     /**
    251      * Set the view that can be used to start dragging the sliding pane.
    252      */
    253     public void setCapturableView(View capturableView) {
    254         mCapturableView = capturableView;
    255     }
    256 
    257     public void setPanelSlideCallbacks(PanelSlideCallbacks listener) {
    258         mPanelSlideCallbacks = listener;
    259     }
    260 
    261     void dispatchOnPanelSlide(View panel) {
    262         mPanelSlideCallbacks.onPanelSlide(panel, mSlideOffset);
    263     }
    264 
    265     void dispatchOnPanelOpened(View panel) {
    266         mPanelSlideCallbacks.onPanelOpened(panel);
    267         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    268     }
    269 
    270     void dispatchOnPanelClosed(View panel) {
    271         mPanelSlideCallbacks.onPanelClosed(panel);
    272         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    273     }
    274 
    275     void updateObscuredViewsVisibility(View panel) {
    276         final int startBound = getPaddingTop();
    277         final int endBound = getHeight() - getPaddingBottom();
    278 
    279         final int leftBound = getPaddingLeft();
    280         final int rightBound = getWidth() - getPaddingRight();
    281         final int left;
    282         final int right;
    283         final int top;
    284         final int bottom;
    285         if (panel != null && viewIsOpaque(panel)) {
    286             left = panel.getLeft();
    287             right = panel.getRight();
    288             top = panel.getTop();
    289             bottom = panel.getBottom();
    290         } else {
    291             left = right = top = bottom = 0;
    292         }
    293 
    294         for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
    295             final View child = getChildAt(i);
    296 
    297             if (child == panel) {
    298                 // There are still more children above the panel but they won't be affected.
    299                 break;
    300             }
    301 
    302             final int clampedChildLeft = Math.max(leftBound, child.getLeft());
    303             final int clampedChildRight = Math.min(rightBound, child.getRight());
    304             final int clampedChildTop = Math.max(startBound, child.getTop());
    305             final int clampedChildBottom = Math.min(endBound, child.getBottom());
    306 
    307             final int vis;
    308             if (clampedChildLeft >= left && clampedChildTop >= top &&
    309                     clampedChildRight <= right && clampedChildBottom <= bottom) {
    310                 vis = INVISIBLE;
    311             } else {
    312                 vis = VISIBLE;
    313             }
    314             child.setVisibility(vis);
    315         }
    316     }
    317 
    318     void setAllChildrenVisible() {
    319         for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
    320             final View child = getChildAt(i);
    321             if (child.getVisibility() == INVISIBLE) {
    322                 child.setVisibility(VISIBLE);
    323             }
    324         }
    325     }
    326 
    327     private static boolean viewIsOpaque(View v) {
    328         if (ViewCompat.isOpaque(v)) return true;
    329 
    330         final Drawable bg = v.getBackground();
    331         if (bg != null) {
    332             return bg.getOpacity() == PixelFormat.OPAQUE;
    333         }
    334         return false;
    335     }
    336 
    337     @Override
    338     protected void onAttachedToWindow() {
    339         super.onAttachedToWindow();
    340         mFirstLayout = true;
    341     }
    342 
    343     @Override
    344     protected void onDetachedFromWindow() {
    345         super.onDetachedFromWindow();
    346         mFirstLayout = true;
    347     }
    348 
    349     @Override
    350     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    351 
    352         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    353         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    354         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    355         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    356 
    357         if (widthMode != MeasureSpec.EXACTLY) {
    358             if (isInEditMode()) {
    359                 // Don't crash the layout editor. Consume all of the space if specified
    360                 // or pick a magic number from thin air otherwise.
    361                 // TODO Better communication with tools of this bogus state.
    362                 // It will crash on a real device.
    363                 if (widthMode == MeasureSpec.AT_MOST) {
    364                     widthMode = MeasureSpec.EXACTLY;
    365                 } else if (widthMode == MeasureSpec.UNSPECIFIED) {
    366                     widthMode = MeasureSpec.EXACTLY;
    367                     widthSize = 300;
    368                 }
    369             } else {
    370                 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
    371             }
    372         } else if (heightMode == MeasureSpec.UNSPECIFIED) {
    373             if (isInEditMode()) {
    374                 // Don't crash the layout editor. Pick a magic number from thin air instead.
    375                 // TODO Better communication with tools of this bogus state.
    376                 // It will crash on a real device.
    377                 if (heightMode == MeasureSpec.UNSPECIFIED) {
    378                     heightMode = MeasureSpec.AT_MOST;
    379                     heightSize = 300;
    380                 }
    381             } else {
    382                 throw new IllegalStateException("Height must not be UNSPECIFIED");
    383             }
    384         }
    385 
    386         int layoutWidth = 0;
    387         int maxLayoutWidth = -1;
    388         switch (widthMode) {
    389             case MeasureSpec.EXACTLY:
    390                 layoutWidth = maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
    391                 break;
    392             case MeasureSpec.AT_MOST:
    393                 maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
    394                 break;
    395         }
    396 
    397         float weightSum = 0;
    398         boolean canSlide = false;
    399         final int heightAvailable = heightSize - getPaddingTop() - getPaddingBottom();
    400         int heightRemaining = heightAvailable;
    401         final int childCount = getChildCount();
    402 
    403         if (childCount > 2) {
    404             Log.e(TAG, "onMeasure: More than two child views are not supported.");
    405         }
    406 
    407         // We'll find the current one below.
    408         mSlideableView = null;
    409 
    410         // First pass. Measure based on child LayoutParams width/height.
    411         // Weight will incur a second pass.
    412         for (int i = 0; i < childCount; i++) {
    413             final View child = getChildAt(i);
    414             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    415 
    416             if (child.getVisibility() == GONE) {
    417                 continue;
    418             }
    419 
    420             if (lp.weight > 0) {
    421                 weightSum += lp.weight;
    422 
    423                 // If we have no height, weight is the only contributor to the final size.
    424                 // Measure this view on the weight pass only.
    425                 if (lp.height == 0) continue;
    426             }
    427 
    428             int childHeightSpec;
    429             final int verticalMargin = lp.topMargin + lp.bottomMargin;
    430             if (lp.height == LayoutParams.WRAP_CONTENT) {
    431                 childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
    432                         MeasureSpec.AT_MOST);
    433             } else if (lp.height == LayoutParams.MATCH_PARENT) {
    434                 childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
    435                         MeasureSpec.EXACTLY);
    436             } else {
    437                 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
    438             }
    439 
    440             int childWidthSpec;
    441             if (lp.width == LayoutParams.WRAP_CONTENT) {
    442                 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
    443             } else if (lp.width == LayoutParams.MATCH_PARENT) {
    444                 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
    445             } else {
    446                 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
    447             }
    448 
    449             child.measure(childWidthSpec, childHeightSpec);
    450             final int childWidth = child.getMeasuredWidth();
    451             final int childHeight = child.getMeasuredHeight();
    452 
    453             if (widthMode == MeasureSpec.AT_MOST && childWidth > layoutWidth) {
    454                 layoutWidth = Math.min(childWidth, maxLayoutWidth);
    455             }
    456 
    457             heightRemaining -= childHeight;
    458             canSlide |= lp.slideable = heightRemaining < 0;
    459             if (lp.slideable) {
    460                 mSlideableView = child;
    461             }
    462         }
    463 
    464         // Resolve weight and make sure non-sliding panels are smaller than the full screen.
    465         if (canSlide || weightSum > 0) {
    466             final int fixedPanelHeightLimit = heightAvailable - mOverhangSize;
    467 
    468             for (int i = 0; i < childCount; i++) {
    469                 final View child = getChildAt(i);
    470 
    471                 if (child.getVisibility() == GONE) {
    472                     continue;
    473                 }
    474 
    475                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    476 
    477                 if (child.getVisibility() == GONE) {
    478                     continue;
    479                 }
    480 
    481                 final boolean skippedFirstPass = lp.height == 0 && lp.weight > 0;
    482                 final int measuredHeight = skippedFirstPass ? 0 : child.getMeasuredHeight();
    483                 if (canSlide && child != mSlideableView) {
    484                     if (lp.height < 0 && (measuredHeight > fixedPanelHeightLimit || lp.weight > 0)) {
    485                         // Fixed panels in a sliding configuration should
    486                         // be clamped to the fixed panel limit.
    487                         final int childWidthSpec;
    488                         if (skippedFirstPass) {
    489                             // Do initial width measurement if we skipped measuring this view
    490                             // the first time around.
    491                             if (lp.width == LayoutParams.WRAP_CONTENT) {
    492                                 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
    493                                         MeasureSpec.AT_MOST);
    494                             } else if (lp.height == LayoutParams.MATCH_PARENT) {
    495                                 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
    496                                         MeasureSpec.EXACTLY);
    497                             } else {
    498                                 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width,
    499                                         MeasureSpec.EXACTLY);
    500                             }
    501                         } else {
    502                             childWidthSpec = MeasureSpec.makeMeasureSpec(
    503                                     child.getMeasuredWidth(), MeasureSpec.EXACTLY);
    504                         }
    505                         final int childHeightSpec = MeasureSpec.makeMeasureSpec(
    506                                 fixedPanelHeightLimit, MeasureSpec.EXACTLY);
    507                         child.measure(childWidthSpec, childHeightSpec);
    508                     }
    509                 } else if (lp.weight > 0) {
    510                     int childWidthSpec;
    511                     if (lp.height == 0) {
    512                         // This was skipped the first time; figure out a real width spec.
    513                         if (lp.width == LayoutParams.WRAP_CONTENT) {
    514                             childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
    515                                     MeasureSpec.AT_MOST);
    516                         } else if (lp.width == LayoutParams.MATCH_PARENT) {
    517                             childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth,
    518                                     MeasureSpec.EXACTLY);
    519                         } else {
    520                             childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width,
    521                                     MeasureSpec.EXACTLY);
    522                         }
    523                     } else {
    524                         childWidthSpec = MeasureSpec.makeMeasureSpec(
    525                                 child.getMeasuredWidth(), MeasureSpec.EXACTLY);
    526                     }
    527 
    528                     if (canSlide) {
    529                         // Consume available space
    530                         final int verticalMargin = lp.topMargin + lp.bottomMargin;
    531                         final int newHeight = heightAvailable - verticalMargin;
    532                         final int childHeightSpec = MeasureSpec.makeMeasureSpec(
    533                                 newHeight, MeasureSpec.EXACTLY);
    534                         if (measuredHeight != newHeight) {
    535                             child.measure(childWidthSpec, childHeightSpec);
    536                         }
    537                     } else {
    538                         // Distribute the extra width proportionally similar to LinearLayout
    539                         final int heightToDistribute = Math.max(0, heightRemaining);
    540                         final int addedHeight = (int) (lp.weight * heightToDistribute / weightSum);
    541                         final int childHeightSpec = MeasureSpec.makeMeasureSpec(
    542                                 measuredHeight + addedHeight, MeasureSpec.EXACTLY);
    543                         child.measure(childWidthSpec, childHeightSpec);
    544                     }
    545                 }
    546             }
    547         }
    548 
    549         final int measuredHeight = heightSize;
    550         final int measuredWidth = layoutWidth + getPaddingLeft() + getPaddingRight();
    551 
    552         setMeasuredDimension(measuredWidth, measuredHeight);
    553         mCanSlide = canSlide;
    554 
    555         if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
    556             // Cancel scrolling in progress, it's no longer relevant.
    557             mDragHelper.abort();
    558         }
    559     }
    560 
    561     @Override
    562     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    563         mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
    564 
    565         final int height = b - t;
    566         final int paddingTop = getPaddingTop();
    567         final int paddingBottom = getPaddingBottom();
    568         final int paddingLeft = getPaddingLeft();
    569 
    570         final int childCount = getChildCount();
    571         int yStart = paddingTop;
    572         int nextYStart = yStart;
    573 
    574         if (mFirstLayout) {
    575             mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f;
    576         }
    577 
    578         for (int i = 0; i < childCount; i++) {
    579             final View child = getChildAt(i);
    580 
    581             if (child.getVisibility() == GONE) {
    582                 continue;
    583             }
    584 
    585             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    586 
    587             final int childHeight = child.getMeasuredHeight();
    588 
    589             if (lp.slideable) {
    590                 final int margin = lp.topMargin + lp.bottomMargin;
    591                 final int range = Math.min(nextYStart,
    592                         height - paddingBottom - mOverhangSize) - yStart - margin;
    593                 mSlideRange = range;
    594                 final int lpMargin = lp.topMargin;
    595                 final int pos = (int) (range * mSlideOffset);
    596                 yStart += pos + lpMargin;
    597                 updateSlideOffset(pos);
    598             } else {
    599                 yStart = nextYStart;
    600             }
    601 
    602             final int childTop = yStart;
    603             final int childBottom = childTop + childHeight;
    604             final int childLeft = paddingLeft;
    605             final int childRight = childLeft + child.getMeasuredWidth();
    606 
    607             child.layout(childLeft, childTop, childRight, childBottom);
    608 
    609             nextYStart += child.getHeight();
    610         }
    611 
    612         if (mFirstLayout) {
    613             updateObscuredViewsVisibility(mSlideableView);
    614         }
    615 
    616         mFirstLayout = false;
    617     }
    618 
    619     @Override
    620     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    621         super.onSizeChanged(w, h, oldw, oldh);
    622         // Recalculate sliding panes and their details
    623         if (h != oldh) {
    624             mFirstLayout = true;
    625         }
    626     }
    627 
    628     @Override
    629     public void requestChildFocus(View child, View focused) {
    630         super.requestChildFocus(child, focused);
    631         if (!isInTouchMode() && !mCanSlide) {
    632             mPreservedOpenState = child == mSlideableView;
    633         }
    634     }
    635 
    636     @Override
    637     public boolean onInterceptTouchEvent(MotionEvent ev) {
    638         final int action = MotionEventCompat.getActionMasked(ev);
    639 
    640         // Preserve the open state based on the last view that was touched.
    641         if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) {
    642             // After the first things will be slideable.
    643             final View secondChild = getChildAt(1);
    644             if (secondChild != null) {
    645                 mPreservedOpenState = !mDragHelper.isViewUnder(secondChild,
    646                         (int) ev.getX(), (int) ev.getY());
    647             }
    648         }
    649 
    650         if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
    651             if (!mIsInNestedScroll) {
    652                 mDragHelper.cancel();
    653             }
    654             return super.onInterceptTouchEvent(ev);
    655         }
    656 
    657         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
    658             if (!mIsInNestedScroll) {
    659                 mDragHelper.cancel();
    660             }
    661             return false;
    662         }
    663 
    664         switch (action) {
    665             case MotionEvent.ACTION_DOWN: {
    666                 mIsUnableToDrag = false;
    667                 final float x = ev.getX();
    668                 final float y = ev.getY();
    669                 mInitialMotionX = x;
    670                 mInitialMotionY = y;
    671 
    672                 break;
    673             }
    674 
    675             case MotionEvent.ACTION_MOVE: {
    676                 final float x = ev.getX();
    677                 final float y = ev.getY();
    678                 final float adx = Math.abs(x - mInitialMotionX);
    679                 final float ady = Math.abs(y - mInitialMotionY);
    680                 final int slop = mDragHelper.getTouchSlop();
    681                 if (ady > slop && adx > ady || !isCapturableViewUnder((int) x, (int) y)) {
    682                     if (!mIsInNestedScroll) {
    683                         mDragHelper.cancel();
    684                     }
    685                     mIsUnableToDrag = true;
    686                     return false;
    687                 }
    688             }
    689         }
    690 
    691         final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev);
    692 
    693         return interceptForDrag;
    694     }
    695 
    696     @Override
    697     public boolean onTouchEvent(MotionEvent ev) {
    698         if (!mCanSlide) {
    699             return super.onTouchEvent(ev);
    700         }
    701 
    702         mDragHelper.processTouchEvent(ev);
    703 
    704         final int action = ev.getAction();
    705         boolean wantTouchEvents = true;
    706 
    707         switch (action & MotionEventCompat.ACTION_MASK) {
    708             case MotionEvent.ACTION_DOWN: {
    709                 final float x = ev.getX();
    710                 final float y = ev.getY();
    711                 mInitialMotionX = x;
    712                 mInitialMotionY = y;
    713                 break;
    714             }
    715         }
    716 
    717         return wantTouchEvents;
    718     }
    719 
    720     /**
    721      * Refreshes the {@link OverlappingPaneLayout} be attempting to re-open or re-close the pane.
    722      * This ensures that the overlapping pane is repositioned based on any changes to the view
    723      * which is being overlapped.
    724      * <p>
    725      * The {@link #openPane()} and {@link #closePane()} methods do not perform any animation if the
    726      * pane has already been positioned appropriately.
    727      */
    728     public void refresh() {
    729         if (isOpen()) {
    730             openPane();
    731         } else {
    732             closePane();
    733         }
    734     }
    735 
    736     private boolean closePane(View pane, int initialVelocity) {
    737         if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
    738             mPreservedOpenState = false;
    739             return true;
    740         }
    741         return false;
    742     }
    743 
    744     private boolean openPane(View pane, int initialVelocity) {
    745         if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
    746             mPreservedOpenState = true;
    747             return true;
    748         }
    749         return false;
    750     }
    751 
    752     private void updateSlideOffset(int offsetPx) {
    753         mSlideOffsetPx = offsetPx;
    754         mSlideOffset = (float) mSlideOffsetPx / mSlideRange;
    755     }
    756 
    757     /**
    758      * Open the sliding pane if it is currently slideable. If first layout
    759      * has already completed this will animate.
    760      *
    761      * @return true if the pane was slideable and is now open/in the process of opening
    762      */
    763     public boolean openPane() {
    764         return openPane(mSlideableView, 0);
    765     }
    766 
    767     /**
    768      * Close the sliding pane if it is currently slideable. If first layout
    769      * has already completed this will animate.
    770      *
    771      * @return true if the pane was slideable and is now closed/in the process of closing
    772      */
    773     public boolean closePane() {
    774         return closePane(mSlideableView, 0);
    775     }
    776 
    777     /**
    778      * Check if the layout is open. It can be open either because the slider
    779      * itself is open revealing the left pane, or if all content fits without sliding.
    780      *
    781      * @return true if sliding panels are open
    782      */
    783     public boolean isOpen() {
    784         return !mCanSlide || mSlideOffset > 0;
    785     }
    786 
    787     /**
    788      * Check if the content in this layout cannot fully fit side by side and therefore
    789      * the content pane can be slid back and forth.
    790      *
    791      * @return true if content in this layout can be slid open and closed
    792      */
    793     public boolean isSlideable() {
    794         return mCanSlide;
    795     }
    796 
    797     private void onPanelDragged(int newTop) {
    798         if (mSlideableView == null) {
    799             // This can happen if we're aborting motion during layout because everything now fits.
    800             mSlideOffset = 0;
    801             return;
    802         }
    803         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
    804 
    805         final int lpMargin = lp.topMargin;
    806         final int topBound = getPaddingTop() + lpMargin;
    807 
    808         updateSlideOffset(newTop - topBound);
    809 
    810         dispatchOnPanelSlide(mSlideableView);
    811     }
    812 
    813     @Override
    814     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    815         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    816         boolean result;
    817         final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
    818 
    819         if (mCanSlide && !lp.slideable && mSlideableView != null) {
    820             // Clip against the slider; no sense drawing what will immediately be covered.
    821             canvas.getClipBounds(mTmpRect);
    822 
    823             mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
    824             canvas.clipRect(mTmpRect);
    825         }
    826 
    827         if (Build.VERSION.SDK_INT >= 11) { // HC
    828             result = super.drawChild(canvas, child, drawingTime);
    829         } else {
    830             if (child.isDrawingCacheEnabled()) {
    831                 child.setDrawingCacheEnabled(false);
    832             }
    833             result = super.drawChild(canvas, child, drawingTime);
    834         }
    835 
    836         canvas.restoreToCount(save);
    837 
    838         return result;
    839     }
    840 
    841     /**
    842      * Smoothly animate mDraggingPane to the target X position within its range.
    843      *
    844      * @param slideOffset position to animate to
    845      * @param velocity initial velocity in case of fling, or 0.
    846      */
    847     boolean smoothSlideTo(float slideOffset, int velocity) {
    848         if (!mCanSlide) {
    849             // Nothing to do.
    850             return false;
    851         }
    852 
    853         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
    854 
    855         int y;
    856         int topBound = getPaddingTop() + lp.topMargin;
    857         y = (int) (topBound + slideOffset * mSlideRange);
    858 
    859         if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
    860             setAllChildrenVisible();
    861             ViewCompat.postInvalidateOnAnimation(this);
    862             return true;
    863         }
    864         return false;
    865     }
    866 
    867     @Override
    868     public void computeScroll() {
    869         if (mDragHelper.continueSettling(/* deferCallbacks = */ false)) {
    870             if (!mCanSlide) {
    871                 mDragHelper.abort();
    872                 return;
    873             }
    874 
    875             ViewCompat.postInvalidateOnAnimation(this);
    876         }
    877     }
    878 
    879     private boolean isCapturableViewUnder(int x, int y) {
    880         View capturableView = mCapturableView != null ? mCapturableView : mSlideableView;
    881         if (capturableView == null) {
    882             return false;
    883         }
    884         int[] viewLocation = new int[2];
    885         capturableView.getLocationOnScreen(viewLocation);
    886         int[] parentLocation = new int[2];
    887         this.getLocationOnScreen(parentLocation);
    888         int screenX = parentLocation[0] + x;
    889         int screenY = parentLocation[1] + y;
    890         return screenX >= viewLocation[0]
    891                 && screenX < viewLocation[0] + capturableView.getWidth()
    892                 && screenY >= viewLocation[1]
    893                 && screenY < viewLocation[1] + capturableView.getHeight();
    894     }
    895 
    896     @Override
    897     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    898         return new LayoutParams();
    899     }
    900 
    901     @Override
    902     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    903         return p instanceof MarginLayoutParams
    904                 ? new LayoutParams((MarginLayoutParams) p)
    905                 : new LayoutParams(p);
    906     }
    907 
    908     @Override
    909     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    910         return p instanceof LayoutParams && super.checkLayoutParams(p);
    911     }
    912 
    913     @Override
    914     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    915         return new LayoutParams(getContext(), attrs);
    916     }
    917 
    918     @Override
    919     protected Parcelable onSaveInstanceState() {
    920         Parcelable superState = super.onSaveInstanceState();
    921 
    922         SavedState ss = new SavedState(superState);
    923         ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
    924 
    925         return ss;
    926     }
    927 
    928     @Override
    929     protected void onRestoreInstanceState(Parcelable state) {
    930         SavedState ss = (SavedState) state;
    931         super.onRestoreInstanceState(ss.getSuperState());
    932 
    933         if (ss.isOpen) {
    934             openPane();
    935         } else {
    936             closePane();
    937         }
    938         mPreservedOpenState = ss.isOpen;
    939     }
    940 
    941     @Override
    942     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    943         final boolean startNestedScroll = (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
    944         if (startNestedScroll) {
    945             mIsInNestedScroll = true;
    946             mDragHelper.startNestedScroll(mSlideableView);
    947         }
    948         if (DEBUG) {
    949             Log.d(TAG, "onStartNestedScroll: " + startNestedScroll);
    950         }
    951         return startNestedScroll;
    952     }
    953 
    954     @Override
    955     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    956         if (dy == 0) {
    957             // Nothing to do
    958             return;
    959         }
    960         if (DEBUG) {
    961             Log.d(TAG, "onNestedPreScroll: " + dy);
    962         }
    963 
    964         mInNestedPreScrollDownwards = dy < 0;
    965         mInNestedPreScrollUpwards = dy > 0;
    966         mIsInNestedFling = false;
    967         mDragHelper.processNestedScroll(mSlideableView, 0, -dy, consumed);
    968     }
    969 
    970     @Override
    971     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
    972         if (!(velocityY > 0 && mSlideOffsetPx != 0
    973                 || velocityY < 0 && mSlideOffsetPx < mIntermediateOffset
    974                 || velocityY < 0 && mSlideOffsetPx < mSlideRange
    975                 && mPanelSlideCallbacks.isScrollableChildUnscrolled())) {
    976             // No need to consume the fling if the fling won't collapse or expand the header.
    977             // How far we are willing to expand the header depends on isScrollableChildUnscrolled().
    978             return false;
    979         }
    980 
    981         if (DEBUG) {
    982             Log.d(TAG, "onNestedPreFling: " + velocityY);
    983         }
    984         mInUpwardsPreFling = velocityY > 0;
    985         mIsInNestedFling = true;
    986         mIsInNestedScroll = false;
    987         mDragHelper.processNestedFling(mSlideableView, (int) -velocityY);
    988         return true;
    989     }
    990 
    991     @Override
    992     public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
    993             int dyUnconsumed) {
    994         if (DEBUG) {
    995             Log.d(TAG, "onNestedScroll: " + dyUnconsumed);
    996         }
    997         mIsInNestedFling = false;
    998         mDragHelper.processNestedScroll(mSlideableView, 0, -dyUnconsumed, null);
    999     }
   1000 
   1001     @Override
   1002     public void onStopNestedScroll(View child) {
   1003         if (DEBUG) {
   1004             Log.d(TAG, "onStopNestedScroll");
   1005         }
   1006         if (mIsInNestedScroll && !mIsInNestedFling) {
   1007             mDragHelper.stopNestedScroll(mSlideableView);
   1008             mInNestedPreScrollDownwards = false;
   1009             mInNestedPreScrollUpwards = false;
   1010             mIsInNestedScroll = false;
   1011         }
   1012     }
   1013 
   1014     private class DragHelperCallback extends ViewDragHelper.Callback {
   1015 
   1016         @Override
   1017         public boolean tryCaptureView(View child, int pointerId) {
   1018             if (mIsUnableToDrag) {
   1019                 return false;
   1020             }
   1021 
   1022             return ((LayoutParams) child.getLayoutParams()).slideable;
   1023         }
   1024 
   1025         @Override
   1026         public void onViewDragStateChanged(int state) {
   1027             if (DEBUG) {
   1028                 Log.d(TAG, "onViewDragStateChanged: " + state);
   1029             }
   1030 
   1031             if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
   1032                 if (mSlideOffset == 0) {
   1033                     updateObscuredViewsVisibility(mSlideableView);
   1034                     dispatchOnPanelClosed(mSlideableView);
   1035                     mPreservedOpenState = false;
   1036                 } else {
   1037                     dispatchOnPanelOpened(mSlideableView);
   1038                     mPreservedOpenState = true;
   1039                 }
   1040             }
   1041 
   1042             if (state == ViewDragHelper.STATE_IDLE
   1043                     && mDragHelper.getVelocityMagnitude() > 0
   1044                     && mIsInNestedFling) {
   1045                 mIsInNestedFling = false;
   1046                 final int flingVelocity = !mInUpwardsPreFling ?
   1047                         -mDragHelper.getVelocityMagnitude() : mDragHelper.getVelocityMagnitude();
   1048                 mPanelSlideCallbacks.onPanelFlingReachesEdge(flingVelocity);
   1049             }
   1050         }
   1051 
   1052         @Override
   1053         public void onViewCaptured(View capturedChild, int activePointerId) {
   1054             // Make all child views visible in preparation for sliding things around
   1055             setAllChildrenVisible();
   1056         }
   1057 
   1058         @Override
   1059         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
   1060             onPanelDragged(top);
   1061             invalidate();
   1062         }
   1063 
   1064         @Override
   1065         public void onViewFling(View releasedChild, float xVelocity, float yVelocity) {
   1066             if (releasedChild == null) {
   1067                 return;
   1068             }
   1069             if (DEBUG) {
   1070                 Log.d(TAG, "onViewFling: " + yVelocity);
   1071             }
   1072 
   1073             // Flings won't always fully expand or collapse the header. Instead of performing the
   1074             // fling and then waiting for the fling to end before snapping into place, we
   1075             // immediately snap into place if we predict the fling won't fully expand or collapse
   1076             // the header.
   1077             int yOffsetPx = mDragHelper.predictFlingYOffset((int) yVelocity);
   1078             if (yVelocity < 0) {
   1079                 // Only perform a fling if we know the fling will fully compress the header.
   1080                 if (-yOffsetPx > mSlideOffsetPx) {
   1081                     mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
   1082                             mSlideRange, Integer.MAX_VALUE, (int) yVelocity);
   1083                 } else {
   1084                     mIsInNestedFling = false;
   1085                     onViewReleased(releasedChild, xVelocity, yVelocity);
   1086                 }
   1087             } else {
   1088                 // Only perform a fling if we know the fling will expand the header as far
   1089                 // as it can possible be expanded, given the isScrollableChildUnscrolled() value.
   1090                 if (yOffsetPx + mSlideOffsetPx >= mSlideRange
   1091                         && mPanelSlideCallbacks.isScrollableChildUnscrolled()) {
   1092                     mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
   1093                             Integer.MAX_VALUE, mSlideRange, (int) yVelocity);
   1094                 } else if (yOffsetPx + mSlideOffsetPx >= mIntermediateOffset
   1095                         && mSlideOffsetPx <= mIntermediateOffset
   1096                         && !mPanelSlideCallbacks.isScrollableChildUnscrolled()) {
   1097                     mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0,
   1098                             Integer.MAX_VALUE, mIntermediateOffset, (int) yVelocity);
   1099                 } else {
   1100                     mIsInNestedFling = false;
   1101                     onViewReleased(releasedChild, xVelocity, yVelocity);
   1102                 }
   1103             }
   1104 
   1105             mInNestedPreScrollDownwards = false;
   1106             mInNestedPreScrollUpwards = false;
   1107 
   1108             // Without this invalidate, some calls to flingCapturedView can have no affect.
   1109             invalidate();
   1110         }
   1111 
   1112         @Override
   1113         public void onViewReleased(View releasedChild, float xvel, float yvel) {
   1114             if (DEBUG) {
   1115                 Log.d(TAG, "onViewReleased: "
   1116                         + " mIsInNestedFling=" + mIsInNestedFling
   1117                         + " unscrolled=" + mPanelSlideCallbacks.isScrollableChildUnscrolled()
   1118                         + ", mInNestedPreScrollDownwards = " + mInNestedPreScrollDownwards
   1119                         + ", mInNestedPreScrollUpwards = " + mInNestedPreScrollUpwards
   1120                         + ", yvel=" + yvel);
   1121             }
   1122             if (releasedChild == null) {
   1123                 return;
   1124             }
   1125 
   1126             final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
   1127             int top = getPaddingTop() + lp.topMargin;
   1128 
   1129             // Decide where to snap to according to the current direction of motion and the current
   1130             // position. The velocity's magnitude has no bearing on this.
   1131             if (mInNestedPreScrollDownwards || yvel > 0) {
   1132                 // Scrolling downwards
   1133                 if (mSlideOffsetPx > mIntermediateOffset + mReleaseScrollSlop) {
   1134                     top += mSlideRange;
   1135                 } else if (mSlideOffsetPx > mReleaseScrollSlop) {
   1136                     top += mIntermediateOffset;
   1137                 } else {
   1138                     // Offset is very close to 0
   1139                 }
   1140             } else if (mInNestedPreScrollUpwards || yvel < 0) {
   1141                 // Scrolling upwards
   1142                 if (mSlideOffsetPx > mSlideRange - mReleaseScrollSlop) {
   1143                     // Offset is very close to mSlideRange
   1144                     top += mSlideRange;
   1145                 } else if (mSlideOffsetPx > mIntermediateOffset - mReleaseScrollSlop) {
   1146                     // Offset is between mIntermediateOffset and mSlideRange.
   1147                     top += mIntermediateOffset;
   1148                 } else {
   1149                     // Offset is between 0 and mIntermediateOffset.
   1150                 }
   1151             } else {
   1152                 // Not moving upwards or downwards. This case can only be triggered when directly
   1153                 // dragging the tabs. We don't bother to remember previous scroll direction
   1154                 // when directly dragging the tabs.
   1155                 if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) {
   1156                     // Offset is between 0 and mIntermediateOffset, but closer to 0
   1157                     // Leave top unchanged
   1158                 } else if (mIntermediateOffset / 2 <= mSlideOffsetPx
   1159                         && mSlideOffsetPx <= (mIntermediateOffset + mSlideRange) / 2) {
   1160                     // Offset is closest to mIntermediateOffset
   1161                     top += mIntermediateOffset;
   1162                 } else {
   1163                     // Offset is between mIntermediateOffset and mSlideRange, but closer to
   1164                     // mSlideRange
   1165                     top += mSlideRange;
   1166                 }
   1167             }
   1168 
   1169             mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
   1170             invalidate();
   1171         }
   1172 
   1173         @Override
   1174         public int getViewVerticalDragRange(View child) {
   1175             return mSlideRange;
   1176         }
   1177 
   1178         @Override
   1179         public int clampViewPositionHorizontal(View child, int left, int dx) {
   1180             // Make sure we never move views horizontally.
   1181             return child.getLeft();
   1182         }
   1183 
   1184         @Override
   1185         public int clampViewPositionVertical(View child, int top, int dy) {
   1186             final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
   1187 
   1188             final int newTop;
   1189             int previousTop = top - dy;
   1190             int topBound = getPaddingTop() + lp.topMargin;
   1191             int bottomBound = topBound + (mPanelSlideCallbacks.isScrollableChildUnscrolled()
   1192                     || !mIsInNestedScroll ? mSlideRange : mIntermediateOffset);
   1193             if (previousTop > bottomBound) {
   1194                 // We were previously below the bottomBound, so loosen the bottomBound so that this
   1195                 // makes sense. This can occur after the view was directly dragged by the tabs.
   1196                 bottomBound = Math.max(bottomBound, mSlideRange);
   1197             }
   1198             newTop = Math.min(Math.max(top, topBound), bottomBound);
   1199 
   1200             return newTop;
   1201         }
   1202 
   1203         @Override
   1204         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
   1205             mDragHelper.captureChildView(mSlideableView, pointerId);
   1206         }
   1207     }
   1208 
   1209     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   1210         private static final int[] ATTRS = new int[] {
   1211             android.R.attr.layout_weight
   1212         };
   1213 
   1214         /**
   1215          * The weighted proportion of how much of the leftover space
   1216          * this child should consume after measurement.
   1217          */
   1218         public float weight = 0;
   1219 
   1220         /**
   1221          * True if this pane is the slideable pane in the layout.
   1222          */
   1223         boolean slideable;
   1224 
   1225         public LayoutParams() {
   1226             super(FILL_PARENT, FILL_PARENT);
   1227         }
   1228 
   1229         public LayoutParams(int width, int height) {
   1230             super(width, height);
   1231         }
   1232 
   1233         public LayoutParams(android.view.ViewGroup.LayoutParams source) {
   1234             super(source);
   1235         }
   1236 
   1237         public LayoutParams(MarginLayoutParams source) {
   1238             super(source);
   1239         }
   1240 
   1241         public LayoutParams(LayoutParams source) {
   1242             super(source);
   1243             this.weight = source.weight;
   1244         }
   1245 
   1246         public LayoutParams(Context c, AttributeSet attrs) {
   1247             super(c, attrs);
   1248 
   1249             final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
   1250             this.weight = a.getFloat(0, 0);
   1251             a.recycle();
   1252         }
   1253 
   1254     }
   1255 
   1256     static class SavedState extends BaseSavedState {
   1257         boolean isOpen;
   1258 
   1259         SavedState(Parcelable superState) {
   1260             super(superState);
   1261         }
   1262 
   1263         private SavedState(Parcel in) {
   1264             super(in);
   1265             isOpen = in.readInt() != 0;
   1266         }
   1267 
   1268         @Override
   1269         public void writeToParcel(Parcel out, int flags) {
   1270             super.writeToParcel(out, flags);
   1271             out.writeInt(isOpen ? 1 : 0);
   1272         }
   1273 
   1274         public static final Parcelable.Creator<SavedState> CREATOR =
   1275                 new Parcelable.Creator<SavedState>() {
   1276             public SavedState createFromParcel(Parcel in) {
   1277                 return new SavedState(in);
   1278             }
   1279 
   1280             public SavedState[] newArray(int size) {
   1281                 return new SavedState[size];
   1282             }
   1283         };
   1284     }
   1285 
   1286     class AccessibilityDelegate extends AccessibilityDelegateCompat {
   1287         private final Rect mTmpRect = new Rect();
   1288 
   1289         @Override
   1290         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
   1291             final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
   1292             super.onInitializeAccessibilityNodeInfo(host, superNode);
   1293             copyNodeInfoNoChildren(info, superNode);
   1294             superNode.recycle();
   1295 
   1296             info.setClassName(OverlappingPaneLayout.class.getName());
   1297             info.setSource(host);
   1298 
   1299             final ViewParent parent = ViewCompat.getParentForAccessibility(host);
   1300             if (parent instanceof View) {
   1301                 info.setParent((View) parent);
   1302             }
   1303 
   1304             // This is a best-approximation of addChildrenForAccessibility()
   1305             // that accounts for filtering.
   1306             final int childCount = getChildCount();
   1307             for (int i = 0; i < childCount; i++) {
   1308                 final View child = getChildAt(i);
   1309                 if (child.getVisibility() == View.VISIBLE) {
   1310                     // Force importance to "yes" since we can't read the value.
   1311                     ViewCompat.setImportantForAccessibility(
   1312                             child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
   1313                     info.addChild(child);
   1314                 }
   1315             }
   1316         }
   1317 
   1318         @Override
   1319         public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
   1320             super.onInitializeAccessibilityEvent(host, event);
   1321 
   1322             event.setClassName(OverlappingPaneLayout.class.getName());
   1323         }
   1324 
   1325         /**
   1326          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
   1327          * seem to be a few elements that are not easily cloneable using the underlying API.
   1328          * Leave it private here as it's not general-purpose useful.
   1329          */
   1330         private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
   1331                 AccessibilityNodeInfoCompat src) {
   1332             final Rect rect = mTmpRect;
   1333 
   1334             src.getBoundsInParent(rect);
   1335             dest.setBoundsInParent(rect);
   1336 
   1337             src.getBoundsInScreen(rect);
   1338             dest.setBoundsInScreen(rect);
   1339 
   1340             dest.setVisibleToUser(src.isVisibleToUser());
   1341             dest.setPackageName(src.getPackageName());
   1342             dest.setClassName(src.getClassName());
   1343             dest.setContentDescription(src.getContentDescription());
   1344 
   1345             dest.setEnabled(src.isEnabled());
   1346             dest.setClickable(src.isClickable());
   1347             dest.setFocusable(src.isFocusable());
   1348             dest.setFocused(src.isFocused());
   1349             dest.setAccessibilityFocused(src.isAccessibilityFocused());
   1350             dest.setSelected(src.isSelected());
   1351             dest.setLongClickable(src.isLongClickable());
   1352 
   1353             dest.addAction(src.getActions());
   1354 
   1355             dest.setMovementGranularities(src.getMovementGranularities());
   1356         }
   1357     }
   1358 }
   1359