1 /*
2  * Copyright (C) 2015 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 package android.car.ui.provider;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.os.Handler;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.support.annotation.NonNull;
31 import android.support.car.ui.CarUiResourceLoader;
32 import android.support.car.ui.QuantumInterpolator;
33 import android.support.car.ui.R;
34 import android.support.car.ui.ReversibleInterpolator;
35 import android.support.v4.view.GravityCompat;
36 import android.support.v4.view.MotionEventCompat;
37 import android.support.v4.view.ViewCompat;
38 import android.support.v4.view.ViewGroupCompat;
39 import android.support.v4.widget.ViewDragHelper;
40 import android.util.AttributeSet;
41 import android.view.Gravity;
42 import android.view.KeyEvent;
43 import android.view.MotionEvent;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.widget.FrameLayout;
47 
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.Set;
53 
54 /**
55  * Acts as a top-level container for window content that allows for
56  * interactive "drawer" views to be pulled out from the edge of the window.
57  *
58  * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
59  * attribute on child views corresponding to which side of the view you want the drawer
60  * to emerge from: left or right. (Or start/end on platform versions that support layout direction.)
61  * </p>
62  *
63  * <p> To use CarDrawerLayout, add your drawer view as the first view in the CarDrawerLayout
64  * element and set the <code>layout_gravity</code> appropriately. Drawers commonly use
65  * <code>match_parent</code> for height with a fixed width. Add the content views as sibling views
66  * after the drawer view.</p>
67  *
68  * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
69  * Avoid performing expensive operations such as layout during animation as it can cause
70  * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
71  * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
72  */
73 public class CarDrawerLayout extends ViewGroup {
74     /**
75      * Indicates that any drawers are in an idle, settled state. No animation is in progress.
76      */
77     public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
78 
79     /**
80      * The drawer is unlocked.
81      */
82     public static final int LOCK_MODE_UNLOCKED = 0;
83 
84     /**
85      * The drawer is locked closed. The user may not open it, though
86      * the app may open it programmatically.
87      */
88     public static final int LOCK_MODE_LOCKED_CLOSED = 1;
89 
90     /**
91      * The drawer is locked open. The user may not close it, though the app
92      * may close it programmatically.
93      */
94     public static final int LOCK_MODE_LOCKED_OPEN = 2;
95 
96     private static final float MAX_SCRIM_ALPHA = 0.8f;
97 
98     private static final boolean SCRIM_ENABLED = true;
99 
100     private static final boolean SHADOW_ENABLED = true;
101 
102     /**
103      * Minimum velocity that will be detected as a fling
104      */
105     private static final int MIN_FLING_VELOCITY = 400; // dips per second
106 
107     /**
108      * Experimental feature.
109      */
110     private static final boolean ALLOW_EDGE_LOCK = false;
111 
112     private static final boolean EDGE_DRAG_ENABLED = false;
113 
114     private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
115 
116     private static final float TOUCH_SLOP_SENSITIVITY = 1.f;
117 
118     private static final int[] LAYOUT_ATTRS = new int[] {
119             android.R.attr.layout_gravity
120     };
121 
122     public static final int DEFAULT_SCRIM_COLOR = 0xff262626;
123 
124     private int mScrimColor = DEFAULT_SCRIM_COLOR;
125     private final Paint mScrimPaint = new Paint();
126     private final Paint mEdgeHighlightPaint = new Paint();
127 
128     private final ViewDragHelper mDragger;
129 
130     private final Runnable mInvalidateRunnable = new Runnable() {
131         @Override
132         public void run() {
133             requestLayout();
134             invalidate();
135         }
136     };
137 
138     // view faders who will be given different colors as the drawer opens
139     private final Set<ViewFaderHolder> mViewFaders;
140     private final ReversibleInterpolator mViewFaderInterpolator;
141     private final ReversibleInterpolator mDrawerFadeInterpolator;
142     private final Handler mHandler = new Handler();
143 
144     private int mEndingViewColor;
145     private int mStartingViewColor;
146     private int mDrawerState;
147     private boolean mInLayout;
148     /** Whether we have done a layout yet. Used to initialize some view-related state. */
149     private boolean mFirstLayout = true;
150     private boolean mHasInflated;
151     private int mLockModeLeft;
152     private int mLockModeRight;
153     private boolean mChildrenCanceledTouch;
154     private DrawerListener mDrawerListener;
155     private DrawerControllerListener mDrawerControllerListener;
156     private Drawable mShadow;
157     private View mDrawerView;
158     private View mContentView;
159     private boolean mNeedsFocus;
160     /** Whether or not the drawer started open for the current gesture */
161     private boolean mStartedOpen;
162     private boolean mHasWheel;
163 
164     /**
165      * Listener for monitoring events about drawers.
166      */
167     public interface DrawerListener {
168         /**
169          * Called when a drawer's position changes.
170          * @param drawerView The child view that was moved
171          * @param slideOffset The new offset of this drawer within its range, from 0-1
172          */
onDrawerSlide(View drawerView, float slideOffset)173         void onDrawerSlide(View drawerView, float slideOffset);
174 
175         /**
176          * Called when a drawer has settled in a completely open state.
177          * The drawer is interactive at this point.
178          *
179          * @param drawerView Drawer view that is now open
180          */
onDrawerOpened(View drawerView)181         void onDrawerOpened(View drawerView);
182 
183         /**
184          * Called when a drawer has settled in a completely closed state.
185          *
186          * @param drawerView Drawer view that is now closed
187          */
onDrawerClosed(View drawerView)188         void onDrawerClosed(View drawerView);
189 
190         /**
191          * Called when a drawer is starting to open.
192          *
193          * @param drawerView Drawer view that is opening
194          */
onDrawerOpening(View drawerView)195         void onDrawerOpening(View drawerView);
196 
197         /**
198          * Called when a drawer is starting to close.
199          *
200          * @param drawerView Drawer view that is closing
201          */
onDrawerClosing(View drawerView)202         void onDrawerClosing(View drawerView);
203 
204         /**
205          * Called when the drawer motion state changes. The new state will
206          * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
207          *
208          * @param newState The new drawer motion state
209          */
onDrawerStateChanged(int newState)210         void onDrawerStateChanged(int newState);
211     }
212 
213     /**
214      * Used to execute when the drawer needs to handle state that the underlying views would like
215      * to handle in a specific way.
216      */
217     public interface DrawerControllerListener {
onBack()218         void onBack();
onScroll()219         boolean onScroll();
220     }
221 
222     /**
223      * Stub/no-op implementations of all methods of {@link DrawerListener}.
224      * Override this if you only care about a few of the available callback methods.
225      */
226     public static abstract class SimpleDrawerListener implements DrawerListener {
227         @Override
onDrawerSlide(View drawerView, float slideOffset)228         public void onDrawerSlide(View drawerView, float slideOffset) {
229         }
230 
231         @Override
onDrawerOpened(View drawerView)232         public void onDrawerOpened(View drawerView) {
233         }
234 
235         @Override
onDrawerClosed(View drawerView)236         public void onDrawerClosed(View drawerView) {
237         }
238 
239         @Override
onDrawerOpening(View drawerView)240         public void onDrawerOpening(View drawerView) {
241         }
242 
243         @Override
onDrawerClosing(View drawerView)244         public void onDrawerClosing(View drawerView) {
245         }
246 
247         @Override
onDrawerStateChanged(int newState)248         public void onDrawerStateChanged(int newState) {
249         }
250     }
251 
252     /**
253      * Sets the color of (or tints) a view (or views).
254      */
255     public interface ViewFader {
setColor(int color)256         void setColor(int color);
257     }
258 
CarDrawerLayout(Context context)259     public CarDrawerLayout(Context context) {
260         this(context, null);
261     }
262 
CarDrawerLayout(Context context, AttributeSet attrs)263     public CarDrawerLayout(Context context, AttributeSet attrs) {
264         this(context, attrs, 0);
265     }
266 
CarDrawerLayout(final Context context, AttributeSet attrs, int defStyle)267     public CarDrawerLayout(final Context context, AttributeSet attrs, int defStyle) {
268         super(context, attrs, defStyle);
269 
270         mViewFaders = new HashSet<>();
271         mEndingViewColor = getResources().getColor(R.color.car_tint);
272 
273         mEdgeHighlightPaint.setColor(getResources().getColor(android.R.color.black));
274 
275         final float density = getResources().getDisplayMetrics().density;
276         final float minVel = MIN_FLING_VELOCITY * density;
277 
278         ViewDragCallback viewDragCallback = new ViewDragCallback();
279         mDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, viewDragCallback);
280         mDragger.setMinVelocity(minVel);
281         viewDragCallback.setDragger(mDragger);
282 
283         ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
284 
285         if (SHADOW_ENABLED) {
286             setDrawerShadow(CarUiResourceLoader.getDrawable(context, "drawer_shadow"));
287         }
288 
289         Resources.Theme theme = context.getTheme();
290         TypedArray ta = theme.obtainStyledAttributes(new int[] {
291                 android.R.attr.colorPrimaryDark
292         });
293         setScrimColor(ta.getColor(0, context.getResources().getColor(R.color.car_grey_900)));
294 
295         mViewFaderInterpolator = new ReversibleInterpolator(
296                 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.25f, 0.25f, 0.5f),
297                 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.43f, 0.14f, 0.43f)
298         );
299         mDrawerFadeInterpolator = new ReversibleInterpolator(
300                 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.625f, 0.25f, 0.125f),
301                 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_LINEAR_IN, 0.58f, 0.14f, 0.28f)
302         );
303 
304         mHasWheel = CarUiResourceLoader.getBoolean(context, "has_wheel", false);
305     }
306 
307     @Override
dispatchKeyEvent(@onNull KeyEvent keyEvent)308     public boolean dispatchKeyEvent(@NonNull KeyEvent keyEvent) {
309         int action = keyEvent.getAction();
310         int keyCode = keyEvent.getKeyCode();
311         final View drawerView = findDrawerView();
312         if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) {
313             if (isDrawerOpen()) {
314                 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
315                         || keyCode == KeyEvent.KEYCODE_SOFT_RIGHT) {
316                     closeDrawer();
317                     return true;
318                 } else if (keyCode == KeyEvent.KEYCODE_BACK
319                         && action == KeyEvent.ACTION_UP
320                         && mDrawerControllerListener != null) {
321                     mDrawerControllerListener.onBack();
322                     return true;
323                 } else {
324                     return drawerView.dispatchKeyEvent(keyEvent);
325                 }
326             }
327         }
328 
329         return mContentView.dispatchKeyEvent(keyEvent);
330     }
331 
332     @Override
dispatchGenericMotionEvent(MotionEvent ev)333     public boolean dispatchGenericMotionEvent(MotionEvent ev) {
334         final View drawerView = findDrawerView();
335         if (drawerView != null
336                 && ev.getAction() == MotionEvent.ACTION_SCROLL
337                 && mDrawerControllerListener != null
338                 && mDrawerControllerListener.onScroll()) {
339             return true;
340         }
341         return super.dispatchGenericMotionEvent(ev);
342     }
343 
344     @Override
onFinishInflate()345     protected void onFinishInflate() {
346         super.onFinishInflate();
347         mHasInflated = true;
348         setAutoDayNightMode();
349 
350         setOnGenericMotionListener(new OnGenericMotionListener() {
351             @Override
352             public boolean onGenericMotion(View view, MotionEvent event) {
353                 if (getChildCount() == 0) {
354                     return false;
355                 }
356                 if (isDrawerOpen()) {
357                     View drawerView = findDrawerView();
358                     ViewGroup viewGroup = (ViewGroup) ((FrameLayout) drawerView).getChildAt(0);
359                     return viewGroup.getChildAt(0).onGenericMotionEvent(event);
360                 }
361                 View contentView = findContentView();
362                 ViewGroup viewGroup = (ViewGroup) ((FrameLayout) contentView).getChildAt(0);
363                 return viewGroup.getChildAt(0).onGenericMotionEvent(event);
364             }
365         });
366     }
367 
368     /**
369      * Set a simple drawable used for the left or right shadow.
370      * The drawable provided must have a nonzero intrinsic width.
371      *
372      * @param shadowDrawable Shadow drawable to use at the edge of a drawer
373      */
setDrawerShadow(Drawable shadowDrawable)374     public void setDrawerShadow(Drawable shadowDrawable) {
375         mShadow = shadowDrawable;
376         invalidate();
377     }
378 
379 
380 
381    /**
382      * Set a color to use for the scrim that obscures primary content while a drawer is open.
383      *
384      * @param color Color to use in 0xAARRGGBB format.
385      */
setScrimColor(int color)386     public void setScrimColor(int color) {
387         mScrimColor = color;
388         invalidate();
389     }
390 
391     /**
392      * Set a listener to be notified of drawer events.
393      *
394      * @param listener Listener to notify when drawer events occur
395      * @see DrawerListener
396      */
setDrawerListener(DrawerListener listener)397     public void setDrawerListener(DrawerListener listener) {
398         mDrawerListener = listener;
399     }
400 
setDrawerControllerListener(DrawerControllerListener listener)401     public void setDrawerControllerListener(DrawerControllerListener listener) {
402         mDrawerControllerListener = listener;
403     }
404 
405     /**
406      * Enable or disable interaction with all drawers.
407      *
408      * <p>This allows the application to restrict the user's ability to open or close
409      * any drawer within this layout. DrawerLayout will still respond to calls to
410      * {@link #openDrawer()}, {@link #closeDrawer()} and friends if a drawer is locked.</p>
411      *
412      * <p>Locking drawers open or closed will implicitly open or close
413      * any drawers as appropriate.</p>
414      *
415      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
416      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
417      */
setDrawerLockMode(int lockMode)418     public void setDrawerLockMode(int lockMode) {
419         LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams();
420         setDrawerLockMode(lockMode, lp.gravity);
421     }
422 
423     /**
424      * Enable or disable interaction with the given drawer.
425      *
426      * <p>This allows the application to restrict the user's ability to open or close
427      * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer()},
428      * {@link #closeDrawer()} and friends if a drawer is locked.</p>
429      *
430      * <p>Locking a drawer open or closed will implicitly open or close
431      * that drawer as appropriate.</p>
432      *
433      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
434      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
435      * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
436      *                    Expresses which drawer to change the mode for.
437      *
438      * @see #LOCK_MODE_UNLOCKED
439      * @see #LOCK_MODE_LOCKED_CLOSED
440      * @see #LOCK_MODE_LOCKED_OPEN
441      */
setDrawerLockMode(int lockMode, int edgeGravity)442     public void setDrawerLockMode(int lockMode, int edgeGravity) {
443         final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
444                 ViewCompat.getLayoutDirection(this));
445         if (absGravity == Gravity.LEFT) {
446             mLockModeLeft = lockMode;
447         } else if (absGravity == Gravity.RIGHT) {
448             mLockModeRight = lockMode;
449         }
450         if (lockMode != LOCK_MODE_UNLOCKED) {
451             // Cancel interaction in progress
452             mDragger.cancel();
453         }
454         switch (lockMode) {
455             case LOCK_MODE_LOCKED_OPEN:
456                 openDrawer();
457                 break;
458             case LOCK_MODE_LOCKED_CLOSED:
459                 closeDrawer();
460                 break;
461             // default: do nothing
462         }
463     }
464 
465     /**
466      * All view faders will be light when the drawer is open and fade to dark and it closes.
467      * NOTE: this will clear any existing view faders.
468      */
setLightMode()469     public void setLightMode() {
470         mStartingViewColor = getResources().getColor(R.color.car_title_light);
471         mEndingViewColor = getResources().getColor(R.color.car_tint);
472         updateViewFaders();
473     }
474 
475     /**
476      * All view faders will be dark when the drawer is open and stay that way when it closes.
477      * NOTE: this will clear any existing view faders.
478      */
setDarkMode()479     public void setDarkMode() {
480         mStartingViewColor = getResources().getColor(R.color.car_title_dark);
481         mEndingViewColor = getResources().getColor(R.color.car_tint);
482         updateViewFaders();
483     }
484 
485     /**
486      * All view faders will be dark during the day and light at night.
487      * NOTE: this will clear any existing view faders.
488      */
setAutoDayNightMode()489     public void setAutoDayNightMode() {
490         mStartingViewColor = getResources().getColor(R.color.car_title);
491         mEndingViewColor = getResources().getColor(R.color.car_tint);
492         updateViewFaders();
493     }
494 
resetViewFaders()495     private void resetViewFaders() {
496         mViewFaders.clear();
497     }
498 
499     /**
500      * Check the lock mode of the given drawer view.
501      *
502      * @param drawerView Drawer view to check lock mode
503      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
504      *         {@link #LOCK_MODE_LOCKED_OPEN}.
505      */
getDrawerLockMode(View drawerView)506     public int getDrawerLockMode(View drawerView) {
507         final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
508         if (absGravity == Gravity.LEFT) {
509             return mLockModeLeft;
510         } else if (absGravity == Gravity.RIGHT) {
511             return mLockModeRight;
512         }
513         return LOCK_MODE_UNLOCKED;
514     }
515 
516     /**
517      * Resolve the shared state of all drawers from the component ViewDragHelpers.
518      * Should be called whenever a ViewDragHelper's state changes.
519      */
updateDrawerState(int activeState)520     private void updateDrawerState(int activeState) {
521         View drawerView = findDrawerView();
522         if (drawerView != null && activeState == STATE_IDLE) {
523             if (onScreen() == 0) {
524                 dispatchOnDrawerClosed(drawerView);
525             } else if (onScreen() == 1) {
526                 dispatchOnDrawerOpened(drawerView);
527             }
528         }
529 
530         if (mDragger.getViewDragState() != mDrawerState) {
531             mDrawerState = mDragger.getViewDragState();
532 
533             if (mDrawerListener != null) {
534                 mDrawerListener.onDrawerStateChanged(mDragger.getViewDragState());
535             }
536         }
537     }
538 
dispatchOnDrawerClosed(View drawerView)539     private void dispatchOnDrawerClosed(View drawerView) {
540         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
541         if (lp.knownOpen) {
542             lp.knownOpen = false;
543             if (mDrawerListener != null) {
544                 mDrawerListener.onDrawerClosed(drawerView);
545             }
546         }
547     }
548 
dispatchOnDrawerOpened(View drawerView)549     private void dispatchOnDrawerOpened(View drawerView) {
550         final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
551         if (!lp.knownOpen) {
552             lp.knownOpen = true;
553             if (mDrawerListener != null) {
554                 mDrawerListener.onDrawerOpened(drawerView);
555             }
556         }
557     }
558 
dispatchOnDrawerSlide(View drawerView, float slideOffset)559     private void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
560         if (mDrawerListener != null) {
561             mDrawerListener.onDrawerSlide(drawerView, slideOffset);
562         }
563     }
564 
dispatchOnDrawerOpening(View drawerView)565     private void dispatchOnDrawerOpening(View drawerView) {
566         if (mDrawerListener != null) {
567             mDrawerListener.onDrawerOpening(drawerView);
568         }
569     }
570 
dispatchOnDrawerClosing(View drawerView)571     private void dispatchOnDrawerClosing(View drawerView) {
572         if (mDrawerListener != null) {
573             mDrawerListener.onDrawerClosing(drawerView);
574         }
575     }
576 
setDrawerViewOffset(View drawerView, float slideOffset)577     private void setDrawerViewOffset(View drawerView, float slideOffset) {
578         if (slideOffset == onScreen()) {
579             return;
580         }
581 
582         LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
583         lp.onScreen = slideOffset;
584         dispatchOnDrawerSlide(drawerView, slideOffset);
585     }
586 
onScreen()587     private float onScreen() {
588         return ((LayoutParams) findDrawerView().getLayoutParams()).onScreen;
589     }
590 
591     /**
592      * @return the absolute gravity of the child drawerView, resolved according
593      *         to the current layout direction
594      */
getDrawerViewAbsoluteGravity(View drawerView)595     private int getDrawerViewAbsoluteGravity(View drawerView) {
596         final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
597         return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
598     }
599 
checkDrawerViewAbsoluteGravity(View drawerView, int checkFor)600     private boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
601         final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
602         return (absGravity & checkFor) == checkFor;
603     }
604 
605     /**
606      * @return the drawer view
607      */
findDrawerView()608     private View findDrawerView() {
609         if (mDrawerView != null) {
610             return mDrawerView;
611         }
612 
613         final int childCount = getChildCount();
614         for (int i = 0; i < childCount; i++) {
615             final View child = getChildAt(i);
616             final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
617             if (childAbsGravity != Gravity.NO_GRAVITY) {
618                 mDrawerView = child;
619                 return child;
620             }
621         }
622         throw new IllegalStateException("No drawer view found.");
623     }
624 
625     /**
626      * @return the content. NOTE: this is the view with no gravity.
627      */
findContentView()628     private View findContentView() {
629         if (mContentView != null) {
630             return mContentView;
631         }
632 
633         final int childCount = getChildCount();
634         for (int i = childCount - 1; i >= 0; --i) {
635             final View child = getChildAt(i);
636             if (isDrawerView(child)) {
637                 continue;
638             }
639             mContentView = child;
640             return child;
641         }
642         throw new IllegalStateException("No content view found.");
643     }
644 
645     @Override
onDetachedFromWindow()646     protected void onDetachedFromWindow() {
647         super.onDetachedFromWindow();
648     }
649 
650     @Override
requestFocus(int direction, Rect rect)651     public boolean requestFocus(int direction, Rect rect) {
652         // Optimally we want to check isInTouchMode(), but that value isn't always correct.
653         if (mHasWheel) {
654             mNeedsFocus = true;
655         }
656         return super.requestFocus(direction, rect);
657     }
658 
659     @Override
onAttachedToWindow()660     protected void onAttachedToWindow() {
661         super.onAttachedToWindow();
662         mFirstLayout = true;
663         // There needs to be a layout pending if we're not going to animate the drawer until the
664         // next layout, so make it so.
665         requestLayout();
666     }
667 
668     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)669     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
670         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
671         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
672         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
673         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
674 
675         if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
676             if (isInEditMode()) {
677                 // Don't crash the layout editor. Consume all of the space if specified
678                 // or pick a magic number from thin air otherwise.
679                 // TODO Better communication with tools of this bogus state.
680                 // It will crash on a real device.
681                 if (widthMode == MeasureSpec.UNSPECIFIED) {
682                     widthSize = 300;
683                 }
684                 else if (heightMode == MeasureSpec.UNSPECIFIED) {
685                     heightSize = 300;
686                 }
687             } else {
688                 throw new IllegalArgumentException(
689                         "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
690             }
691         }
692 
693         setMeasuredDimension(widthSize, heightSize);
694 
695         View view = findContentView();
696         LayoutParams lp = ((LayoutParams) view.getLayoutParams());
697         // Content views get measured at exactly the layout's size.
698         final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
699                 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
700         final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
701                 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
702         view.measure(contentWidthSpec, contentHeightSpec);
703 
704         view = findDrawerView();
705         lp = ((LayoutParams) view.getLayoutParams());
706         final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
707                 lp.leftMargin + lp.rightMargin,
708                 lp.width);
709         final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
710                 lp.topMargin + lp.bottomMargin,
711                 lp.height);
712         view.measure(drawerWidthSpec, drawerHeightSpec);
713     }
714 
715     @Override
onLayout(boolean changed, int l, int t, int r, int b)716     protected void onLayout(boolean changed, int l, int t, int r, int b) {
717         mInLayout = true;
718         final int width = r - l;
719 
720         View contentView = findContentView();
721         View drawerView = findDrawerView();
722 
723         LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams();
724         LayoutParams contentLp = (LayoutParams) contentView.getLayoutParams();
725 
726         int contentRight = contentLp.getMarginStart() + getWidth();
727         contentView.layout(contentRight - contentView.getMeasuredWidth(),
728                 contentLp.topMargin, contentRight,
729                 contentLp.topMargin + contentView.getMeasuredHeight());
730 
731         final int childHeight = drawerView.getMeasuredHeight();
732         int onScreen = (int) (drawerView.getWidth() * drawerLp.onScreen);
733         int offset;
734         if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
735             offset = onScreen - drawerView.getWidth();
736         } else {
737             offset = width - onScreen;
738         }
739         drawerView.layout(drawerLp.getMarginStart() + offset, drawerLp.topMargin,
740                 width - drawerLp.getMarginEnd() + offset,
741                 childHeight + drawerLp.topMargin);
742         updateDrawerAlpha();
743         updateViewFaders();
744         if (mFirstLayout) {
745 
746             // TODO(b/15394507): Normally, onMeasure()/onLayout() are called three times when
747             // you create CarDrawerLayout, but when you pop it back it's only called once which
748             // leaves us in a weird state. This is a pretty ugly hack to fix that.
749             mHandler.post(mInvalidateRunnable);
750 
751             mFirstLayout = false;
752         }
753 
754         if (mNeedsFocus) {
755             if (initializeFocus()) {
756                 mNeedsFocus = false;
757             }
758         }
759 
760         mInLayout = false;
761     }
762 
initializeFocus()763     private boolean initializeFocus() {
764         // Only request focus if the current view that needs focus doesn't already have it. This
765         // prevents some nasty bugs where focus ends up snapping to random elements and also saves
766         // a bunch of cycles in the average case.
767         mDrawerView.setFocusable(false);
768         mContentView.setFocusable(false);
769         boolean needFocus = !mDrawerView.hasFocus() && !mContentView.hasFocus();
770         if (!needFocus) {
771             return true;
772         }
773 
774         // Find something in the hierarchy to give focus to.
775         List<View> focusables;
776         boolean drawerOpen = isDrawerOpen();
777         if (drawerOpen) {
778             focusables = mDrawerView.getFocusables(FOCUS_DOWN);
779         } else {
780             focusables = mContentView.getFocusables(FOCUS_DOWN);
781         }
782 
783         // The 2 else cases here are a catch all for when nothing is focusable in view hierarchy.
784         // If you don't have anything focusable on screen, key events will not be delivered to
785         // the view hierarchy and you end up getting stuck without being able to open / close the
786         // drawer or launch gsa.
787 
788         if (!focusables.isEmpty()) {
789             focusables.get(0).requestFocus();
790             return true;
791         } else if (drawerOpen) {
792             mDrawerView.setFocusable(true);
793         } else {
794             mContentView.setFocusable(true);
795         }
796         return false;
797     }
798 
799     @Override
requestLayout()800     public void requestLayout() {
801         if (!mInLayout) {
802             super.requestLayout();
803         }
804     }
805 
806     @Override
computeScroll()807     public void computeScroll() {
808         if (mDragger.continueSettling(true)) {
809             ViewCompat.postInvalidateOnAnimation(this);
810         }
811     }
812 
hasOpaqueBackground(View v)813     private static boolean hasOpaqueBackground(View v) {
814         final Drawable bg = v.getBackground();
815         return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
816     }
817 
818     @Override
drawChild(Canvas canvas, View child, long drawingTime)819     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
820         final int height = getHeight();
821         final boolean drawingContent = isContentView(child);
822         int clipLeft = findContentView().getLeft();
823         int clipRight = findContentView().getRight();
824         final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
825 
826         final int restoreCount = canvas.save();
827         if (drawingContent) {
828             final int childCount = getChildCount();
829             for (int i = 0; i < childCount; i++) {
830                 final View v = getChildAt(i);
831                 if (v == child || v.getVisibility() != VISIBLE ||
832                         !hasOpaqueBackground(v) || !isDrawerView(v) ||
833                         v.getHeight() < height) {
834                     continue;
835                 }
836 
837                 if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
838                     final int vright = v.getRight();
839                     if (vright > clipLeft) {
840                         clipLeft = vright;
841                     }
842                 } else {
843                     final int vleft = v.getLeft();
844                     if (vleft < clipRight) {
845                         clipRight = vleft;
846                     }
847                 }
848             }
849             canvas.clipRect(clipLeft, 0, clipRight, getHeight());
850         }
851         final boolean result = super.drawChild(canvas, child, drawingTime);
852         canvas.restoreToCount(restoreCount);
853 
854         if (drawingContent) {
855             int scrimAlpha = SCRIM_ENABLED ?
856                     (int) (baseAlpha * Math.max(0, Math.min(1, onScreen())) * MAX_SCRIM_ALPHA) : 0;
857 
858             if (scrimAlpha > 0) {
859                 int color = scrimAlpha << 24 | (mScrimColor & 0xffffff);
860                 mScrimPaint.setColor(color);
861 
862                 canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
863 
864                 canvas.drawRect(clipLeft - 1, 0, clipLeft, getHeight(), mEdgeHighlightPaint);
865             }
866 
867             LayoutParams drawerLp = (LayoutParams) findDrawerView().getLayoutParams();
868             if (mShadow != null
869                     && checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) {
870                 final int offScreen = (int) ((1 - drawerLp.onScreen) * findDrawerView().getWidth());
871                 final int drawerRight = getWidth() - drawerLp.getMarginEnd() - offScreen;
872                 final int shadowWidth = mShadow.getIntrinsicWidth();
873                 final float alpha =
874                         Math.max(0, Math.min((float) drawerRight / mDragger.getEdgeSize(), 1.f));
875                 mShadow.setBounds(drawerRight, child.getTop(),
876                         drawerRight + shadowWidth, child.getBottom());
877                 mShadow.setAlpha((int) (255 * alpha * alpha * alpha));
878                 mShadow.draw(canvas);
879             } else if (mShadow != null
880                     && checkDrawerViewAbsoluteGravity(findDrawerView(),Gravity.RIGHT)) {
881                 final int onScreen = (int) (findDrawerView().getWidth() * drawerLp.onScreen);
882                 final int drawerLeft = drawerLp.getMarginStart() + getWidth() - onScreen;
883                 final int shadowWidth = mShadow.getIntrinsicWidth();
884                 final float alpha =
885                         Math.max(0, Math.min((float) onScreen / mDragger.getEdgeSize(), 1.f));
886                 canvas.save();
887                 canvas.translate(2 * drawerLeft - shadowWidth, 0);
888                 canvas.scale(-1.0f, 1.0f);
889                 mShadow.setBounds(drawerLeft - shadowWidth, child.getTop(),
890                         drawerLeft, child.getBottom());
891                 mShadow.setAlpha((int) (255 * alpha * alpha * alpha * alpha));
892                 mShadow.draw(canvas);
893                 canvas.restore();
894             }
895         }
896         return result;
897     }
898 
isContentView(View child)899     private boolean isContentView(View child) {
900         return child == findContentView();
901     }
902 
isDrawerView(View child)903     private boolean isDrawerView(View child) {
904         return child == findDrawerView();
905     }
906 
updateDrawerAlpha()907     private void updateDrawerAlpha() {
908         float alpha;
909         if (mStartedOpen) {
910             alpha = mDrawerFadeInterpolator.getReverseInterpolation(onScreen());
911         } else {
912             alpha = mDrawerFadeInterpolator.getForwardInterpolation(onScreen());
913         }
914         ViewGroup drawerView = (ViewGroup) findDrawerView();
915         int drawerChildCount = drawerView.getChildCount();
916         for (int i = 0; i < drawerChildCount; i++) {
917             drawerView.getChildAt(i).setAlpha(alpha);
918         }
919     }
920 
921     /**
922      * Add a view fader whose color will be set as the drawer opens and closes.
923      */
addViewFader(ViewFader viewFader)924     public void addViewFader(ViewFader viewFader) {
925         addViewFader(viewFader, mStartingViewColor, mEndingViewColor);
926     }
927 
addViewFader(ViewFader viewFader, int startingColor, int endingColor)928     public void addViewFader(ViewFader viewFader, int startingColor, int endingColor) {
929         mViewFaders.add(new ViewFaderHolder(viewFader, startingColor, endingColor));
930         updateViewFaders();
931     }
932 
removeViewFader(ViewFader viewFader)933     public void removeViewFader(ViewFader viewFader) {
934         for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) {
935             ViewFaderHolder viewFaderHolder = it.next();
936             if (viewFaderHolder.viewFader.equals(viewFader)) {
937                 it.remove();
938             }
939         }
940     }
941 
updateViewFaders()942     private void updateViewFaders() {
943         if (!mHasInflated) {
944             return;
945         }
946 
947         float fadeProgress;
948         if (mStartedOpen) {
949             fadeProgress = mViewFaderInterpolator.getReverseInterpolation(onScreen());
950         } else {
951             fadeProgress = mViewFaderInterpolator.getForwardInterpolation(onScreen());
952         }
953         for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) {
954             ViewFaderHolder viewFaderHolder = it.next();
955             int startingColor = viewFaderHolder.startingColor;
956             int endingColor = viewFaderHolder.endingColor;
957             int alpha = weightedAverage(Color.alpha(startingColor),
958                     Color.alpha(endingColor), fadeProgress);
959             int red = weightedAverage(Color.red(startingColor),
960                     Color.red(endingColor), fadeProgress);
961             int green = weightedAverage(Color.green(startingColor),
962                     Color.green(endingColor), fadeProgress);
963             int blue = weightedAverage(Color.blue(startingColor),
964                     Color.blue(endingColor), fadeProgress);
965             viewFaderHolder.viewFader.setColor(alpha << 24 | red << 16 | green << 8 | blue);
966         }
967     }
968 
weightedAverage(int starting, int ending, float weight)969     private int weightedAverage(int starting, int ending, float weight) {
970         return (int) ((1f - weight) * starting + weight * ending);
971     }
972 
973     @Override
onInterceptTouchEvent(MotionEvent ev)974     public boolean onInterceptTouchEvent(MotionEvent ev) {
975         final int action = MotionEventCompat.getActionMasked(ev);
976 
977         // "|" used deliberately here; both methods should be invoked.
978         final boolean interceptForDrag = mDragger.shouldInterceptTouchEvent(ev);
979 
980         boolean interceptForTap = false;
981 
982         switch (action) {
983             case MotionEvent.ACTION_DOWN: {
984                 final float x = ev.getX();
985                 final float y = ev.getY();
986                 if (onScreen() > 0 && isContentView(mDragger.findTopChildUnder((int) x, (int) y))) {
987                     interceptForTap = true;
988                 }
989                 mChildrenCanceledTouch = false;
990                 break;
991             }
992             case MotionEvent.ACTION_CANCEL:
993             case MotionEvent.ACTION_UP: {
994                 mChildrenCanceledTouch = false;
995             }
996         }
997 
998         return interceptForDrag || interceptForTap || mChildrenCanceledTouch;
999     }
1000 
1001     @Override
onTouchEvent(@onNull MotionEvent ev)1002     public boolean onTouchEvent(@NonNull MotionEvent ev) {
1003         mDragger.processTouchEvent(ev);
1004         final int absGravity = getDrawerViewAbsoluteGravity(findDrawerView());
1005         final int edge;
1006         if (absGravity == Gravity.LEFT) {
1007             edge = ViewDragHelper.EDGE_LEFT;
1008         } else {
1009             edge = ViewDragHelper.EDGE_RIGHT;
1010         }
1011 
1012         // don't allow views behind the drawer to be touched
1013         boolean drawerPartiallyOpen = onScreen() > 0;
1014         return mDragger.isEdgeTouched(edge) ||
1015                 mDragger.getCapturedView() != null ||
1016                 drawerPartiallyOpen;
1017     }
1018 
1019     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)1020     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1021         if (CHILDREN_DISALLOW_INTERCEPT) {
1022             // If we have an edge touch we want to skip this and track it for later instead.
1023             super.requestDisallowInterceptTouchEvent(disallowIntercept);
1024         }
1025 
1026         View drawerView = findDrawerView();
1027         if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1028             super.requestDisallowInterceptTouchEvent(disallowIntercept);
1029         }
1030 
1031         if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) {
1032             super.requestDisallowInterceptTouchEvent(disallowIntercept);
1033         }
1034     }
1035 
1036     /**
1037      * Open the drawer view by animating it into view.
1038      */
openDrawer()1039     public void openDrawer() {
1040         ViewGroup drawerView = (ViewGroup) findDrawerView();
1041         mStartedOpen = false;
1042 
1043         if (hasWindowFocus()) {
1044             int left;
1045             LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams();
1046             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1047                 left = drawerLp.getMarginStart();
1048             } else {
1049                 left = drawerLp.getMarginStart() + getWidth() - drawerView.getWidth();
1050             }
1051             mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop());
1052             dispatchOnDrawerOpening(drawerView);
1053         } else {
1054             final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1055             lp.onScreen = 1.f;
1056             dispatchOnDrawerOpened(drawerView);
1057         }
1058 
1059         ViewGroup contentView = (ViewGroup) findContentView();
1060         contentView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1061         drawerView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS);
1062 
1063         View focusable = drawerView.getChildAt(0);
1064         if (focusable != null) {
1065             focusable.requestFocus();
1066         }
1067         invalidate();
1068     }
1069 
1070     /**
1071      * Close the specified drawer view by animating it into view.
1072      */
closeDrawer()1073     public void closeDrawer() {
1074         ViewGroup drawerView = (ViewGroup) findDrawerView();
1075         if (!isDrawerView(drawerView)) {
1076             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1077         }
1078         mStartedOpen = true;
1079 
1080         // Don't trigger the close drawer animation if drawer is not open.
1081         if (hasWindowFocus() && isDrawerOpen()) {
1082             int left;
1083             LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams();
1084             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1085                 left = drawerLp.getMarginStart() - drawerView.getWidth();
1086             } else {
1087                 left = drawerLp.getMarginStart() + getWidth();
1088             }
1089             mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop());
1090             dispatchOnDrawerClosing(drawerView);
1091         } else {
1092             final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1093             lp.onScreen = 0.f;
1094             dispatchOnDrawerClosed(drawerView);
1095         }
1096 
1097         ViewGroup contentView = (ViewGroup) findContentView();
1098         drawerView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1099         contentView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS);
1100 
1101         if (!isInTouchMode()) {
1102             List<View> focusables = contentView.getFocusables(FOCUS_DOWN);
1103             if (focusables.size() > 0) {
1104                 View candidate = focusables.get(0);
1105                 candidate.requestFocus();
1106             }
1107         }
1108         invalidate();
1109     }
1110 
1111     @Override
addFocusables(@onNull ArrayList<View> views, int direction, int focusableMode)1112     public void addFocusables(@NonNull ArrayList<View> views, int direction, int focusableMode) {
1113         boolean drawerOpen = isDrawerOpen();
1114         if (drawerOpen) {
1115             findDrawerView().addFocusables(views, direction, focusableMode);
1116         } else {
1117             findContentView().addFocusables(views, direction, focusableMode);
1118         }
1119     }
1120 
1121     /**
1122      * Check if the given drawer view is currently in an open state.
1123      * To be considered "open" the drawer must have settled into its fully
1124      * visible state. To check for partial visibility use
1125      * {@link #isDrawerVisible(android.view.View)}.
1126      *
1127      * @return true if the given drawer view is in an open state
1128      * @see #isDrawerVisible(android.view.View)
1129      */
isDrawerOpen()1130     public boolean isDrawerOpen() {
1131         return ((LayoutParams) findDrawerView().getLayoutParams()).knownOpen;
1132     }
1133 
1134     /**
1135      * Check if a given drawer view is currently visible on-screen. The drawer
1136      * may be fully extended or anywhere in between.
1137      *
1138      * @param drawer Drawer view to check
1139      * @return true if the given drawer is visible on-screen
1140      * @see #isDrawerOpen()
1141      */
isDrawerVisible(View drawer)1142     public boolean isDrawerVisible(View drawer) {
1143         if (!isDrawerView(drawer)) {
1144             throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1145         }
1146         return onScreen() > 0;
1147     }
1148 
1149     /**
1150      * Check if a given drawer view is currently visible on-screen. The drawer
1151      * may be fully extended or anywhere in between.
1152      * If there is no drawer with the given gravity this method will return false.
1153      *
1154      * @return true if the given drawer is visible on-screen
1155      */
isDrawerVisible()1156     public boolean isDrawerVisible() {
1157         final View drawerView = findDrawerView();
1158         return drawerView != null && isDrawerVisible(drawerView);
1159     }
1160 
1161     @Override
generateDefaultLayoutParams()1162     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1163         return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1164                 ViewGroup.LayoutParams.MATCH_PARENT);
1165     }
1166 
1167     @Override
generateLayoutParams(ViewGroup.LayoutParams p)1168     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1169         return p instanceof LayoutParams
1170                 ? new LayoutParams((LayoutParams) p)
1171                 : p instanceof MarginLayoutParams
1172                 ? new LayoutParams((MarginLayoutParams) p)
1173                 : new LayoutParams(p);
1174     }
1175 
1176     @Override
checkLayoutParams(ViewGroup.LayoutParams p)1177     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1178         return p instanceof LayoutParams && super.checkLayoutParams(p);
1179     }
1180 
1181     @Override
generateLayoutParams(AttributeSet attrs)1182     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1183         return new LayoutParams(getContext(), attrs);
1184     }
1185 
hasVisibleDrawer()1186     private boolean hasVisibleDrawer() {
1187         return findVisibleDrawer() != null;
1188     }
1189 
findVisibleDrawer()1190     private View findVisibleDrawer() {
1191         final int childCount = getChildCount();
1192         for (int i = 0; i < childCount; i++) {
1193             final View child = getChildAt(i);
1194             if (isDrawerView(child) && isDrawerVisible(child)) {
1195                 return child;
1196             }
1197         }
1198         return null;
1199     }
1200 
1201     @Override
onRestoreInstanceState(Parcelable state)1202     protected void onRestoreInstanceState(Parcelable state) {
1203         SavedState ss = null;
1204         if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
1205             // Class loader mismatch, recreate from parcel.
1206             Parcel stateParcel = Parcel.obtain();
1207             state.writeToParcel(stateParcel, 0);
1208             ss = SavedState.CREATOR.createFromParcel(stateParcel);
1209         } else {
1210             ss = (SavedState) state;
1211         }
1212         super.onRestoreInstanceState(ss.getSuperState());
1213 
1214         if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
1215             openDrawer();
1216         }
1217 
1218         setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
1219         setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
1220     }
1221 
1222     @Override
onSaveInstanceState()1223     protected Parcelable onSaveInstanceState() {
1224         final Parcelable superState = super.onSaveInstanceState();
1225 
1226         final SavedState ss = new SavedState(superState);
1227 
1228         final int childCount = getChildCount();
1229         for (int i = 0; i < childCount; i++) {
1230             final View child = getChildAt(i);
1231             if (!isDrawerView(child)) {
1232                 continue;
1233             }
1234 
1235             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1236             if (lp.knownOpen) {
1237                 ss.openDrawerGravity = lp.gravity;
1238                 // Only one drawer can be open at a time.
1239                 break;
1240             }
1241         }
1242 
1243         ss.lockModeLeft = mLockModeLeft;
1244         ss.lockModeRight = mLockModeRight;
1245 
1246         return ss;
1247     }
1248 
1249     /**
1250      * State persisted across instances
1251      */
1252     protected static class SavedState extends BaseSavedState {
1253         int openDrawerGravity = Gravity.NO_GRAVITY;
1254         int lockModeLeft = LOCK_MODE_UNLOCKED;
1255         int lockModeRight = LOCK_MODE_UNLOCKED;
1256 
SavedState(Parcel in)1257         public SavedState(Parcel in) {
1258             super(in);
1259             openDrawerGravity = in.readInt();
1260             lockModeLeft = in.readInt();
1261             lockModeRight = in.readInt();
1262         }
1263 
SavedState(Parcelable superState)1264         public SavedState(Parcelable superState) {
1265             super(superState);
1266         }
1267 
1268         @Override
writeToParcel(@onNull Parcel dest, int flags)1269         public void writeToParcel(@NonNull Parcel dest, int flags) {
1270             super.writeToParcel(dest, flags);
1271             dest.writeInt(openDrawerGravity);
1272             dest.writeInt(lockModeLeft);
1273             dest.writeInt(lockModeRight);
1274         }
1275 
1276         @SuppressWarnings("hiding")
1277         public static final Creator<SavedState> CREATOR =
1278                 new Creator<SavedState>() {
1279                     @Override
1280                     public SavedState createFromParcel(Parcel source) {
1281                         return new SavedState(source);
1282                     }
1283 
1284                     @Override
1285                     public SavedState[] newArray(int size) {
1286                         return new SavedState[size];
1287                     }
1288                 };
1289     }
1290 
1291     private class ViewDragCallback extends ViewDragHelper.Callback {
1292         @SuppressWarnings("hiding")
1293         private ViewDragHelper mDragger;
1294 
setDragger(ViewDragHelper dragger)1295         public void setDragger(ViewDragHelper dragger) {
1296             mDragger = dragger;
1297         }
1298 
1299         @Override
tryCaptureView(View child, int pointerId)1300         public boolean tryCaptureView(View child, int pointerId) {
1301             CarDrawerLayout.LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams();
1302             int edges = EDGE_DRAG_ENABLED ? ViewDragHelper.EDGE_ALL : 0;
1303             boolean captured = isContentView(child) &&
1304                     getDrawerLockMode(child) == LOCK_MODE_UNLOCKED &&
1305                     (lp.knownOpen || mDragger.isEdgeTouched(edges));
1306             if (captured && lp.knownOpen) {
1307                 mStartedOpen = true;
1308             } else if (captured && !lp.knownOpen) {
1309                 mStartedOpen = false;
1310             }
1311             // We want dragging starting on the content view to drag the drawer. Therefore when
1312             // touch events try to capture the content view, we force capture of the drawer view.
1313             if (captured) {
1314                 mDragger.captureChildView(findDrawerView(), pointerId);
1315             }
1316             return false;
1317         }
1318 
1319         @Override
onViewDragStateChanged(int state)1320         public void onViewDragStateChanged(int state) {
1321             updateDrawerState(state);
1322         }
1323 
1324         @Override
onViewPositionChanged(View changedView, int left, int top, int dx, int dy)1325         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1326             float offset;
1327             View drawerView = findDrawerView();
1328             final int drawerWidth = drawerView.getWidth();
1329             // This reverses the positioning shown in onLayout.
1330             if (checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) {
1331                 offset = (float) (left + drawerWidth) / drawerWidth;
1332             } else {
1333                 offset = (float) (getWidth() - left) / drawerWidth;
1334             }
1335             setDrawerViewOffset(findDrawerView(), offset);
1336 
1337             updateDrawerAlpha();
1338 
1339             updateViewFaders();
1340             invalidate();
1341         }
1342 
1343         @Override
onViewReleased(View releasedChild, float xvel, float yvel)1344         public void onViewReleased(View releasedChild, float xvel, float yvel) {
1345             final View drawerView = findDrawerView();
1346             final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1347             int left;
1348             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1349                 // Open the drawer if they are swiping right or if they are not currently moving but
1350                 // have moved the drawer in the current gesture and released the drawer when it was
1351                 // fully open.
1352                 // Close otherwise.
1353                 left = xvel > 0 ? lp.getMarginStart() : lp.getMarginStart() - drawerView.getWidth();
1354             } else {
1355                 // See comment for left drawer.
1356                 left = xvel < 0 ? lp.getMarginStart() + getWidth() - drawerView.getWidth()
1357                         : lp.getMarginStart() + getWidth();
1358             }
1359 
1360             mDragger.settleCapturedViewAt(left, releasedChild.getTop());
1361             invalidate();
1362         }
1363 
1364         @Override
1365         public boolean onEdgeLock(int edgeFlags) {
1366             if (ALLOW_EDGE_LOCK) {
1367                 if (!isDrawerOpen()) {
1368                     closeDrawer();
1369                 }
1370                 return true;
1371             }
1372             return false;
1373         }
1374 
1375         @Override
1376         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1377             View drawerView = findDrawerView();
1378             if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
1379                 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) {
1380                     drawerView = null;
1381                 }
1382             } else {
1383                 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1384                     drawerView = null;
1385                 }
1386             }
1387 
1388             if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) {
1389                 mDragger.captureChildView(drawerView, pointerId);
1390             }
1391         }
1392 
1393         @Override
1394         public int getViewHorizontalDragRange(View child) {
1395             return child.getWidth();
1396         }
1397 
1398         @Override
1399         public int clampViewPositionHorizontal(View child, int left, int dx) {
1400             final View drawerView = findDrawerView();
1401             LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams();
1402             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1403                 return Math.max(drawerLp.getMarginStart() - drawerView.getWidth(),
1404                         Math.min(left, drawerLp.getMarginStart()));
1405             } else {
1406                 return Math.max(drawerLp.getMarginStart() + getWidth() - drawerView.getWidth(),
1407                         Math.min(left, drawerLp.getMarginStart() + getWidth()));
1408             }
1409         }
1410 
1411         @Override
1412         public int clampViewPositionVertical(View child, int top, int dy) {
1413             return child.getTop();
1414         }
1415     }
1416 
1417     public static class LayoutParams extends MarginLayoutParams {
1418 
1419         public int gravity = Gravity.NO_GRAVITY;
1420         float onScreen;
1421         boolean knownOpen;
1422 
1423         public LayoutParams(Context c, AttributeSet attrs) {
1424             super(c, attrs);
1425 
1426             final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
1427             gravity = a.getInt(0, Gravity.NO_GRAVITY);
1428             a.recycle();
1429         }
1430 
1431         public LayoutParams(int width, int height) {
1432             super(width, height);
1433         }
1434 
1435         public LayoutParams(int width, int height, int gravity) {
1436             this(width, height);
1437             this.gravity = gravity;
1438         }
1439 
1440         public LayoutParams(LayoutParams source) {
1441             super(source);
1442             gravity = source.gravity;
1443         }
1444 
1445         public LayoutParams(ViewGroup.LayoutParams source) {
1446             super(source);
1447         }
1448 
1449         public LayoutParams(MarginLayoutParams source) {
1450             super(source);
1451         }
1452     }
1453 
1454     private static final class ViewFaderHolder {
1455         public final ViewFader viewFader;
1456         public final int startingColor;
1457         public final int endingColor;
1458 
1459         public ViewFaderHolder(ViewFader viewFader, int startingColor, int endingColor) {
1460             this.viewFader = viewFader;
1461             this.startingColor = startingColor;
1462             this.endingColor = endingColor;
1463         }
1464 
1465     }
1466 }
1467