Home | History | Annotate | Download | only in ui
      1 /*******************************************************************************
      2  *      Copyright (C) 2012 Google Inc.
      3  *      Licensed to The Android Open Source Project.
      4  *
      5  *      Licensed under the Apache License, Version 2.0 (the "License");
      6  *      you may not use this file except in compliance with the License.
      7  *      You may obtain a copy of the License at
      8  *
      9  *           http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *      Unless required by applicable law or agreed to in writing, software
     12  *      distributed under the License is distributed on an "AS IS" BASIS,
     13  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *      See the License for the specific language governing permissions and
     15  *      limitations under the License.
     16  *******************************************************************************/
     17 
     18 package com.android.mail.ui;
     19 
     20 import android.animation.Animator;
     21 import android.animation.AnimatorListenerAdapter;
     22 import android.animation.TimeInterpolator;
     23 import android.app.Activity;
     24 import android.content.Context;
     25 import android.content.res.Resources;
     26 import android.support.v4.widget.DrawerLayout;
     27 import android.util.AttributeSet;
     28 import android.view.Gravity;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.ViewParent;
     32 import android.view.animation.AnimationUtils;
     33 import android.widget.FrameLayout;
     34 
     35 import com.android.mail.R;
     36 import com.android.mail.ui.ViewMode.ModeChangeListener;
     37 import com.android.mail.utils.LogUtils;
     38 import com.android.mail.utils.Utils;
     39 import com.google.common.annotations.VisibleForTesting;
     40 
     41 /**
     42  * This is a custom layout that manages the possible views of Gmail's large screen (read: tablet)
     43  * activity, and the transitions between them.
     44  *
     45  * This is not intended to be a generic layout; it is specific to the {@code Fragment}s
     46  * available in {@link MailActivity} and assumes their existence. It merely configures them
     47  * according to the specific <i>modes</i> the {@link Activity} can be in.
     48  *
     49  * Currently, the layout differs in three dimensions: orientation, two aspects of view modes.
     50  * This results in essentially three states: One where the folders are on the left and conversation
     51  * list is on the right, and two states where the conversation list is on the left: one in which
     52  * it's collapsed and another where it is not.
     53  *
     54  * In folder or conversation list view, conversations are hidden and folders and conversation lists
     55  * are visible. This is the case in both portrait and landscape
     56  *
     57  * In Conversation List or Conversation View, folders are hidden, and conversation lists and
     58  * conversation view is visible. This is the case in both portrait and landscape.
     59  *
     60  * In the Gmail source code, this was called TriStateSplitLayout
     61  */
     62 final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
     63 
     64     private static final String LOG_TAG = "TwoPaneLayout";
     65     private static final long SLIDE_DURATION_MS = 300;
     66 
     67     private final double mConversationListWeight;
     68     private final double mFolderListWeight;
     69     private final TimeInterpolator mSlideInterpolator;
     70     /**
     71      * True if and only if the conversation list is collapsible in the current device configuration.
     72      * See {@link #isConversationListCollapsed()} to see whether it is currently collapsed
     73      * (based on the current view mode).
     74      */
     75     private final boolean mListCollapsible;
     76 
     77     /**
     78      * The current mode that the tablet layout is in. This is a constant integer that holds values
     79      * that are {@link ViewMode} constants like {@link ViewMode#CONVERSATION}.
     80      */
     81     private int mCurrentMode = ViewMode.UNKNOWN;
     82     /**
     83      * This mode represents the current positions of the three panes. This is split out from the
     84      * current mode to give context to state transitions.
     85      */
     86     private int mPositionedMode = ViewMode.UNKNOWN;
     87 
     88     private AbstractActivityController mController;
     89     private LayoutListener mListener;
     90     private boolean mIsSearchResult;
     91 
     92     private DrawerLayout mDrawerLayout;
     93 
     94     private View mMiscellaneousView;
     95     private View mConversationView;
     96     private View mFoldersView;
     97     private View mListView;
     98 
     99     public static final int MISCELLANEOUS_VIEW_ID = R.id.miscellaneous_pane;
    100 
    101     private final Runnable mTransitionCompleteRunnable = new Runnable() {
    102         @Override
    103         public void run() {
    104             onTransitionComplete();
    105         }
    106     };
    107     /**
    108      * A special view used during animation of the conversation list.
    109      * <p>
    110      * The conversation list changes width when switching view modes, so to visually smooth out
    111      * the transition, we cross-fade the old and new widths. During the transition, a bitmap of the
    112      * old conversation list is kept here, and this view moves in tandem with the real list view,
    113      * but its opacity gradually fades out to give way to the new width.
    114      */
    115     private ConversationListCopy mListCopyView;
    116 
    117     /**
    118      * During a mode transition, this value is the final width for {@link #mListCopyView}. We want
    119      * to avoid changing its width during the animation, as it should match the initial width of
    120      * {@link #mListView}.
    121      */
    122     private Integer mListCopyWidthOnComplete;
    123 
    124     private final boolean mIsExpansiveLayout;
    125     private boolean mDrawerInitialSetupComplete;
    126 
    127     public TwoPaneLayout(Context context) {
    128         this(context, null);
    129     }
    130 
    131     public TwoPaneLayout(Context context, AttributeSet attrs) {
    132         super(context, attrs);
    133 
    134         final Resources res = getResources();
    135 
    136         // The conversation list might be visible now, depending on the layout: in portrait we
    137         // don't show the conversation list, but in landscape we do.  This information is stored
    138         // in the constants
    139         mListCollapsible = res.getBoolean(R.bool.list_collapsible);
    140 
    141         mSlideInterpolator = AnimationUtils.loadInterpolator(context,
    142                 android.R.interpolator.decelerate_cubic);
    143 
    144         final int folderListWeight = res.getInteger(R.integer.folder_list_weight);
    145         final int convListWeight = res.getInteger(R.integer.conversation_list_weight);
    146         final int convViewWeight = res.getInteger(R.integer.conversation_view_weight);
    147         mFolderListWeight = (double) folderListWeight
    148                 / (folderListWeight + convListWeight);
    149         mConversationListWeight = (double) convListWeight
    150                 / (convListWeight + convViewWeight);
    151 
    152         mIsExpansiveLayout = res.getBoolean(R.bool.use_expansive_tablet_ui);
    153         mDrawerInitialSetupComplete = false;
    154     }
    155 
    156     @Override
    157     protected void onFinishInflate() {
    158         super.onFinishInflate();
    159 
    160         mFoldersView = findViewById(R.id.content_pane);
    161         mListView = findViewById(R.id.conversation_list_pane);
    162         mListCopyView = (ConversationListCopy) findViewById(R.id.conversation_list_copy);
    163         mConversationView = findViewById(R.id.conversation_pane);
    164         mMiscellaneousView = findViewById(MISCELLANEOUS_VIEW_ID);
    165 
    166         // all panes start GONE in initial UNKNOWN mode to avoid drawing misplaced panes
    167         mCurrentMode = ViewMode.UNKNOWN;
    168         mFoldersView.setVisibility(GONE);
    169         mListView.setVisibility(GONE);
    170         mListCopyView.setVisibility(GONE);
    171         mConversationView.setVisibility(GONE);
    172         mMiscellaneousView.setVisibility(GONE);
    173     }
    174 
    175     @VisibleForTesting
    176     public void setController(AbstractActivityController controller, boolean isSearchResult) {
    177         mController = controller;
    178         mListener = controller;
    179         mIsSearchResult = isSearchResult;
    180     }
    181 
    182     public void setDrawerLayout(DrawerLayout drawerLayout) {
    183         mDrawerLayout = drawerLayout;
    184     }
    185 
    186     @Override
    187     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    188         LogUtils.d(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onMeasure()", this);
    189         setupPaneWidths(MeasureSpec.getSize(widthMeasureSpec));
    190         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    191     }
    192 
    193     @Override
    194     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    195         LogUtils.d(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onLayout()", this);
    196         if (changed || mCurrentMode != mPositionedMode) {
    197             positionPanes(getMeasuredWidth());
    198         }
    199         super.onLayout(changed, l, t, r, b);
    200     }
    201 
    202     /**
    203      * Sizes up the three sliding panes. This method will ensure that the LayoutParams of the panes
    204      * have the correct widths set for the current overall size and view mode.
    205      *
    206      * @param parentWidth this view's new width
    207      */
    208     private void setupPaneWidths(int parentWidth) {
    209         final int foldersWidth = computeFolderListWidth(parentWidth);
    210         final int foldersFragmentWidth;
    211         if (isDrawerView(mFoldersView)) {
    212             foldersFragmentWidth = getResources().getDimensionPixelSize(R.dimen.drawer_width);
    213         } else {
    214             foldersFragmentWidth = foldersWidth;
    215         }
    216         final int convWidth = computeConversationWidth(parentWidth);
    217 
    218         setPaneWidth(mFoldersView, foldersFragmentWidth);
    219 
    220         // only adjust the fixed conversation view width when my width changes
    221         if (parentWidth != getMeasuredWidth()) {
    222             LogUtils.i(LOG_TAG, "setting up new TPL, w=%d fw=%d cv=%d", parentWidth,
    223                     foldersWidth, convWidth);
    224 
    225             setPaneWidth(mMiscellaneousView, convWidth);
    226             setPaneWidth(mConversationView, convWidth);
    227         }
    228 
    229         final int currListWidth = getPaneWidth(mListView);
    230         int listWidth = currListWidth;
    231         switch (mCurrentMode) {
    232             case ViewMode.AD:
    233             case ViewMode.CONVERSATION:
    234             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    235                 if (!mListCollapsible) {
    236                     listWidth = parentWidth - convWidth;
    237                 }
    238                 break;
    239             case ViewMode.CONVERSATION_LIST:
    240             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
    241             case ViewMode.SEARCH_RESULTS_LIST:
    242                 listWidth = parentWidth - foldersWidth;
    243                 break;
    244             default:
    245                 break;
    246         }
    247         LogUtils.d(LOG_TAG, "conversation list width change, w=%d", listWidth);
    248         setPaneWidth(mListView, listWidth);
    249 
    250         if ((mCurrentMode != mPositionedMode && mPositionedMode != ViewMode.UNKNOWN)
    251                 || mListCopyWidthOnComplete != null) {
    252             mListCopyWidthOnComplete = listWidth;
    253         } else {
    254             setPaneWidth(mListCopyView, listWidth);
    255         }
    256     }
    257 
    258     /**
    259      * Positions the three sliding panes at the correct X offset (using {@link View#setX(float)}).
    260      * When switching from list->conversation mode or vice versa, animate the change in X.
    261      *
    262      * @param width
    263      */
    264     private void positionPanes(int width) {
    265         if (mPositionedMode == mCurrentMode) {
    266             return;
    267         }
    268 
    269         boolean hasPositions = false;
    270         int convX = 0, listX = 0, foldersX = 0;
    271 
    272         switch (mCurrentMode) {
    273             case ViewMode.AD:
    274             case ViewMode.CONVERSATION:
    275             case ViewMode.SEARCH_RESULTS_CONVERSATION: {
    276                 final int foldersW = getPaneWidth(mFoldersView);
    277                 final int listW;
    278                 listW = getPaneWidth(mListView);
    279 
    280                 if (mListCollapsible) {
    281                     convX = 0;
    282                     listX = -listW;
    283                     foldersX = listX - foldersW;
    284                 } else {
    285                     convX = listW;
    286                     listX = 0;
    287                     foldersX = -foldersW;
    288                 }
    289                 hasPositions = true;
    290                 LogUtils.i(LOG_TAG, "conversation mode layout, x=%d/%d/%d", foldersX, listX, convX);
    291                 break;
    292             }
    293             case ViewMode.CONVERSATION_LIST:
    294             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
    295             case ViewMode.SEARCH_RESULTS_LIST: {
    296                 convX = width;
    297                 listX = getPaneWidth(mFoldersView);
    298                 foldersX = 0;
    299 
    300                 hasPositions = true;
    301                 LogUtils.i(LOG_TAG, "conv-list mode layout, x=%d/%d/%d", foldersX, listX, convX);
    302                 break;
    303             }
    304             default:
    305                 break;
    306         }
    307 
    308         if (hasPositions) {
    309             animatePanes(foldersX, listX, convX);
    310         }
    311 
    312         mPositionedMode = mCurrentMode;
    313     }
    314 
    315     private final AnimatorListenerAdapter mPaneAnimationListener = new AnimatorListenerAdapter() {
    316         @Override
    317         public void onAnimationEnd(Animator animation) {
    318             mListCopyView.unbind();
    319             useHardwareLayer(false);
    320             fixupListCopyWidth();
    321             onTransitionComplete();
    322         }
    323         @Override
    324         public void onAnimationCancel(Animator animation) {
    325             mListCopyView.unbind();
    326             useHardwareLayer(false);
    327         }
    328     };
    329 
    330     /**
    331      * @param foldersX
    332      * @param listX
    333      * @param convX
    334      */
    335     private void animatePanes(int foldersX, int listX, int convX) {
    336         // If positioning has not yet happened, we don't need to animate panes into place.
    337         // This happens on first layout, rotate, and when jumping straight to a conversation from
    338         // a view intent.
    339         if (mPositionedMode == ViewMode.UNKNOWN) {
    340             mConversationView.setX(convX);
    341             mMiscellaneousView.setX(convX);
    342             mListView.setX(listX);
    343             if (!isDrawerView(mFoldersView)) {
    344                 mFoldersView.setX(foldersX);
    345             }
    346 
    347             // listeners need to know that the "transition" is complete, even if one is not run.
    348             // defer notifying listeners because we're in a layout pass, and they might do layout.
    349             post(mTransitionCompleteRunnable);
    350             return;
    351         }
    352 
    353         final boolean useListCopy = getPaneWidth(mListView) != getPaneWidth(mListCopyView);
    354 
    355         if (useListCopy) {
    356             // freeze the current list view before it gets redrawn
    357             mListCopyView.bind(mListView);
    358             mListCopyView.setX(mListView.getX());
    359 
    360             mListCopyView.setAlpha(1.0f);
    361             mListView.setAlpha(0.0f);
    362         }
    363 
    364         useHardwareLayer(true);
    365 
    366         if (ViewMode.isAdMode(mCurrentMode)) {
    367             mMiscellaneousView.animate().x(convX);
    368         } else {
    369             mConversationView.animate().x(convX);
    370         }
    371 
    372         if (!isDrawerView(mFoldersView)) {
    373             mFoldersView.animate().x(foldersX);
    374         }
    375         if (useListCopy) {
    376             mListCopyView.animate().x(listX).alpha(0.0f);
    377         }
    378         mListView.animate()
    379             .x(listX)
    380             .alpha(1.0f)
    381             .setListener(mPaneAnimationListener);
    382         configureAnimations(mConversationView, mFoldersView, mListView, mListCopyView,
    383                 mMiscellaneousView);
    384     }
    385 
    386     private void configureAnimations(View... views) {
    387         for (View v : views) {
    388             if (isDrawerView(v)) {
    389                 continue;
    390             }
    391             v.animate()
    392                 .setInterpolator(mSlideInterpolator)
    393                 .setDuration(SLIDE_DURATION_MS);
    394         }
    395     }
    396 
    397     private void useHardwareLayer(boolean useHardware) {
    398         final int layerType = useHardware ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE;
    399         if (!isDrawerView(mFoldersView)) {
    400             mFoldersView.setLayerType(layerType, null);
    401         }
    402         mListView.setLayerType(layerType, null);
    403         mListCopyView.setLayerType(layerType, null);
    404         mConversationView.setLayerType(layerType, null);
    405         mMiscellaneousView.setLayerType(layerType, null);
    406         if (useHardware) {
    407             // these buildLayer calls are safe because layout is the only way we get here
    408             // (i.e. these views must already be attached)
    409             if (!isDrawerView(mFoldersView)) {
    410                 mFoldersView.buildLayer();
    411             }
    412             mListView.buildLayer();
    413             mListCopyView.buildLayer();
    414             mConversationView.buildLayer();
    415             mMiscellaneousView.buildLayer();
    416         }
    417     }
    418 
    419     private void fixupListCopyWidth() {
    420         if (mListCopyWidthOnComplete == null ||
    421                 getPaneWidth(mListCopyView) == mListCopyWidthOnComplete) {
    422             mListCopyWidthOnComplete = null;
    423             return;
    424         }
    425         LogUtils.i(LOG_TAG, "onAnimationEnd of list view, setting copy width to %d",
    426                 mListCopyWidthOnComplete);
    427         setPaneWidth(mListCopyView, mListCopyWidthOnComplete);
    428         mListCopyWidthOnComplete = null;
    429     }
    430 
    431     private void onTransitionComplete() {
    432         if (mController.isDestroyed()) {
    433             // quit early if the hosting activity was destroyed before the animation finished
    434             LogUtils.i(LOG_TAG, "IN TPL.onTransitionComplete, activity destroyed->quitting early");
    435             return;
    436         }
    437 
    438         switch (mCurrentMode) {
    439             case ViewMode.CONVERSATION:
    440             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    441                 dispatchConversationVisibilityChanged(true);
    442                 dispatchConversationListVisibilityChange(!isConversationListCollapsed());
    443 
    444                 break;
    445             case ViewMode.CONVERSATION_LIST:
    446             case ViewMode.SEARCH_RESULTS_LIST:
    447                 dispatchConversationVisibilityChanged(false);
    448                 dispatchConversationListVisibilityChange(true);
    449 
    450                 break;
    451             case ViewMode.AD:
    452                 dispatchConversationVisibilityChanged(false);
    453                 dispatchConversationListVisibilityChange(!isConversationListCollapsed());
    454 
    455                 break;
    456             default:
    457                 break;
    458         }
    459     }
    460 
    461     /**
    462      * Computes the width of the conversation list in stable state of the current mode.
    463      */
    464     public int computeConversationListWidth() {
    465         return computeConversationListWidth(getMeasuredWidth());
    466     }
    467 
    468     /**
    469      * Computes the width of the conversation list in stable state of the current mode.
    470      */
    471     private int computeConversationListWidth(int totalWidth) {
    472         switch (mCurrentMode) {
    473             case ViewMode.CONVERSATION_LIST:
    474             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
    475             case ViewMode.SEARCH_RESULTS_LIST:
    476                 return totalWidth - computeFolderListWidth(totalWidth);
    477             case ViewMode.AD:
    478             case ViewMode.CONVERSATION:
    479             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    480                 return (int) (totalWidth * mConversationListWeight);
    481         }
    482         return 0;
    483     }
    484 
    485     public int computeConversationWidth() {
    486         return computeConversationWidth(getMeasuredWidth());
    487     }
    488 
    489     /**
    490      * Computes the width of the conversation pane in stable state of the
    491      * current mode.
    492      */
    493     private int computeConversationWidth(int totalWidth) {
    494         if (mListCollapsible) {
    495             return totalWidth;
    496         } else {
    497             return totalWidth - (int) (totalWidth * mConversationListWeight);
    498         }
    499     }
    500 
    501     /**
    502      * Computes the width of the folder list in stable state of the current mode.
    503      */
    504     private int computeFolderListWidth(int parentWidth) {
    505         if (mIsSearchResult) {
    506             return 0;
    507         } else if (isDrawerView(mFoldersView)) {
    508             return 0;
    509         } else {
    510             return (int) (parentWidth * mFolderListWeight);
    511         }
    512     }
    513 
    514     private void dispatchConversationListVisibilityChange(boolean visible) {
    515         if (mListener != null) {
    516             mListener.onConversationListVisibilityChanged(visible);
    517         }
    518     }
    519 
    520     private void dispatchConversationVisibilityChanged(boolean visible) {
    521         if (mListener != null) {
    522             mListener.onConversationVisibilityChanged(visible);
    523         }
    524     }
    525 
    526     // does not apply to drawer children. will return zero for those.
    527     private int getPaneWidth(View pane) {
    528         return isDrawerView(pane) ? 0 : pane.getLayoutParams().width;
    529     }
    530 
    531     private boolean isDrawerView(View child) {
    532         return child != null && child.getParent() == mDrawerLayout;
    533     }
    534 
    535     /**
    536      * @return Whether or not the conversation list is visible on screen.
    537      */
    538     public boolean isConversationListCollapsed() {
    539         return !ViewMode.isListMode(mCurrentMode) && mListCollapsible;
    540     }
    541 
    542     @Override
    543     public void onViewModeChanged(int newMode) {
    544         // make all initially GONE panes visible only when the view mode is first determined
    545         if (mCurrentMode == ViewMode.UNKNOWN) {
    546             mFoldersView.setVisibility(VISIBLE);
    547             mListView.setVisibility(VISIBLE);
    548             mListCopyView.setVisibility(VISIBLE);
    549         }
    550 
    551         if (ViewMode.isAdMode(newMode)) {
    552             mMiscellaneousView.setVisibility(VISIBLE);
    553             mConversationView.setVisibility(GONE);
    554         } else {
    555             mConversationView.setVisibility(VISIBLE);
    556             mMiscellaneousView.setVisibility(GONE);
    557         }
    558 
    559         // set up the drawer as appropriate for the configuration
    560         final ViewParent foldersParent = mFoldersView.getParent();
    561         if (mIsExpansiveLayout && foldersParent != this) {
    562             if (foldersParent != mDrawerLayout) {
    563                 throw new IllegalStateException("invalid Folders fragment parent: " +
    564                         foldersParent);
    565             }
    566             mDrawerLayout.removeView(mFoldersView);
    567             addView(mFoldersView, 0);
    568             mFoldersView.findViewById(R.id.folders_pane_edge).setVisibility(VISIBLE);
    569             mFoldersView.setBackgroundDrawable(null);
    570         } else if (!mIsExpansiveLayout && foldersParent == this) {
    571             removeView(mFoldersView);
    572             mDrawerLayout.addView(mFoldersView);
    573             final DrawerLayout.LayoutParams lp =
    574                     (DrawerLayout.LayoutParams) mFoldersView.getLayoutParams();
    575             lp.gravity = Gravity.START;
    576             mFoldersView.setLayoutParams(lp);
    577             mFoldersView.findViewById(R.id.folders_pane_edge).setVisibility(GONE);
    578             mFoldersView.setBackgroundResource(R.color.list_background_color);
    579         }
    580 
    581         // detach the pager immediately from its data source (to prevent processing updates)
    582         if (ViewMode.isConversationMode(mCurrentMode)) {
    583             mController.disablePagerUpdates();
    584         }
    585 
    586         mDrawerInitialSetupComplete = true;
    587         mCurrentMode = newMode;
    588         LogUtils.i(LOG_TAG, "onViewModeChanged(%d)", newMode);
    589 
    590         // do all the real work in onMeasure/onLayout, when panes are sized and positioned for the
    591         // current width/height anyway
    592         requestLayout();
    593     }
    594 
    595     public boolean isModeChangePending() {
    596         return mPositionedMode != mCurrentMode;
    597     }
    598 
    599     private void setPaneWidth(View pane, int w) {
    600         final ViewGroup.LayoutParams lp = pane.getLayoutParams();
    601         if (lp.width == w) {
    602             return;
    603         }
    604         lp.width = w;
    605         pane.setLayoutParams(lp);
    606         if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
    607             final String s;
    608             if (pane == mFoldersView) {
    609                 s = "folders";
    610             } else if (pane == mListView) {
    611                 s = "conv-list";
    612             } else if (pane == mConversationView) {
    613                 s = "conv-view";
    614             } else if (pane == mMiscellaneousView) {
    615                 s = "misc-view";
    616             } else {
    617                 s = "???:" + pane;
    618             }
    619             LogUtils.d(LOG_TAG, "TPL: setPaneWidth, w=%spx pane=%s", w, s);
    620         }
    621     }
    622 
    623     public boolean isDrawerEnabled() {
    624         return !mIsExpansiveLayout && mDrawerInitialSetupComplete;
    625     }
    626 
    627     public boolean isExpansiveLayout() {
    628         return mIsExpansiveLayout;
    629     }
    630 }
    631