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