Home | History | Annotate | Download | only in provider
      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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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 {
    218         void onBack();
    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
    228         public void onDrawerSlide(View drawerView, float slideOffset) {
    229         }
    230 
    231         @Override
    232         public void onDrawerOpened(View drawerView) {
    233         }
    234 
    235         @Override
    236         public void onDrawerClosed(View drawerView) {
    237         }
    238 
    239         @Override
    240         public void onDrawerOpening(View drawerView) {
    241         }
    242 
    243         @Override
    244         public void onDrawerClosing(View drawerView) {
    245         }
    246 
    247         @Override
    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 {
    256         void setColor(int color);
    257     }
    258 
    259     public CarDrawerLayout(Context context) {
    260         this(context, null);
    261     }
    262 
    263     public CarDrawerLayout(Context context, AttributeSet attrs) {
    264         this(context, attrs, 0);
    265     }
    266 
    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
    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
    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
    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      */
    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      */
    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      */
    397     public void setDrawerListener(DrawerListener listener) {
    398         mDrawerListener = listener;
    399     }
    400 
    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      */
    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      */
    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      */
    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      */
    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      */
    489     public void setAutoDayNightMode() {
    490         mStartingViewColor = getResources().getColor(R.color.car_title);
    491         mEndingViewColor = getResources().getColor(R.color.car_tint);
    492         updateViewFaders();
    493     }
    494 
    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      */
    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      */
    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 
    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 
    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 
    559     private void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
    560         if (mDrawerListener != null) {
    561             mDrawerListener.onDrawerSlide(drawerView, slideOffset);
    562         }
    563     }
    564 
    565     private void dispatchOnDrawerOpening(View drawerView) {
    566         if (mDrawerListener != null) {
    567             mDrawerListener.onDrawerOpening(drawerView);
    568         }
    569     }
    570 
    571     private void dispatchOnDrawerClosing(View drawerView) {
    572         if (mDrawerListener != null) {
    573             mDrawerListener.onDrawerClosing(drawerView);
    574         }
    575     }
    576 
    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 
    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      */
    595     private int getDrawerViewAbsoluteGravity(View drawerView) {
    596         final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
    597         return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
    598     }
    599 
    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      */
    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      */
    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
    646     protected void onDetachedFromWindow() {
    647         super.onDetachedFromWindow();
    648     }
    649 
    650     @Override
    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
    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
    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
    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 
    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
    800     public void requestLayout() {
    801         if (!mInLayout) {
    802             super.requestLayout();
    803         }
    804     }
    805 
    806     @Override
    807     public void computeScroll() {
    808         if (mDragger.continueSettling(true)) {
    809             ViewCompat.postInvalidateOnAnimation(this);
    810         }
    811     }
    812 
    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
    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 
    899     private boolean isContentView(View child) {
    900         return child == findContentView();
    901     }
    902 
    903     private boolean isDrawerView(View child) {
    904         return child == findDrawerView();
    905     }
    906 
    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      */
    924     public void addViewFader(ViewFader viewFader) {
    925         addViewFader(viewFader, mStartingViewColor, mEndingViewColor);
    926     }
    927 
    928     public void addViewFader(ViewFader viewFader, int startingColor, int endingColor) {
    929         mViewFaders.add(new ViewFaderHolder(viewFader, startingColor, endingColor));
    930         updateViewFaders();
    931     }
    932 
    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 
    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 
    969     private int weightedAverage(int starting, int ending, float weight) {
    970         return (int) ((1f - weight) * starting + weight * ending);
    971     }
    972 
    973     @Override
    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
   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
   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      */
   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      */
   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
   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      */
   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      */
   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      */
   1156     public boolean isDrawerVisible() {
   1157         final View drawerView = findDrawerView();
   1158         return drawerView != null && isDrawerVisible(drawerView);
   1159     }
   1160 
   1161     @Override
   1162     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
   1163         return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
   1164                 ViewGroup.LayoutParams.MATCH_PARENT);
   1165     }
   1166 
   1167     @Override
   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
   1177     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   1178         return p instanceof LayoutParams && super.checkLayoutParams(p);
   1179     }
   1180 
   1181     @Override
   1182     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   1183         return new LayoutParams(getContext(), attrs);
   1184     }
   1185 
   1186     private boolean hasVisibleDrawer() {
   1187         return findVisibleDrawer() != null;
   1188     }
   1189 
   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
   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
   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 
   1257         public SavedState(Parcel in) {
   1258             super(in);
   1259             openDrawerGravity = in.readInt();
   1260             lockModeLeft = in.readInt();
   1261             lockModeRight = in.readInt();
   1262         }
   1263 
   1264         public SavedState(Parcelable superState) {
   1265             super(superState);
   1266         }
   1267 
   1268         @Override
   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 
   1295         public void setDragger(ViewDragHelper dragger) {
   1296             mDragger = dragger;
   1297         }
   1298 
   1299         @Override
   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
   1320         public void onViewDragStateChanged(int state) {
   1321             updateDrawerState(state);
   1322         }
   1323 
   1324         @Override
   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
   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