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.car.app.menu.CarMenuCallbacks;
     19 import android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.os.Bundle;
     22 import android.support.car.ui.PagedListView;
     23 import android.support.car.ui.R;
     24 import android.support.v7.widget.CardView;
     25 import android.support.v7.widget.RecyclerView;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 import android.view.View;
     29 import android.view.animation.Animation;
     30 import android.view.animation.AnimationUtils;
     31 import android.widget.ProgressBar;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Arrays;
     35 import java.util.LinkedList;
     36 import java.util.List;
     37 import java.util.Queue;
     38 import java.util.Stack;
     39 
     40 import static android.car.app.menu.CarMenuConstants.MenuItemConstants.FLAG_BROWSABLE;
     41 import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_FLAGS;
     42 import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_ID;
     43 import static android.car.app.menu.CarMenuConstants.MenuItemConstants.KEY_TITLE;
     44 
     45 /**
     46  * Controls the drawer for SDK app
     47  */
     48 public class DrawerController
     49         implements CarDrawerLayout.DrawerListener, DrawerApiAdapter.OnItemSelectedListener,
     50         CarDrawerLayout.DrawerControllerListener {
     51     private static final String TAG = "CAR.UI.DrawerController";
     52     // Qualify with full package name to make it less likely there will be a collision
     53     private static final String KEY_IDS = "android.support.car.ui.drawer.sdk.IDS";
     54     private static final String KEY_DRAWERSTATE =
     55             "android.support.car.ui.drawer.sdk.DRAWER_STATE";
     56     private static final String KEY_TITLES = "android.support.car.ui.drawer.sdk.TITLES";
     57     private static final String KEY_ROOT = "android.support.car.ui.drawer.sdk.ROOT";
     58     private static final String KEY_ID_UNAVAILABLE_CATEGORY = "UNAVAILABLE_CATEGORY";
     59     private static final String KEY_CLICK_STACK =
     60             "android.support.car.ui.drawer.sdk.CLICK_STACK";
     61     private static final String KEY_MAX_PAGES =
     62             "android.support.car.ui.drawer.sdk.MAX_PAGES";
     63     private static final String KEY_IS_CAPPED =
     64             "android.support.car.ui.drawer.sdk.IS_CAPPED";
     65 
     66     /** Drawer is in Auto dark/light mode */
     67     private static final int MODE_AUTO = 0;
     68     /** Drawer is in Light mode */
     69     private static final int MODE_LIGHT = 1;
     70     /** Drawer is in Dark mode */
     71     private static final int MODE_DARK = 2;
     72 
     73     private final Stack<String> mSubscriptionIds = new Stack<>();
     74     private final Stack<CharSequence> mTitles = new Stack<>();
     75     private final SubscriptionCallbacks mSubscriptionCallbacks = new SubscriptionCallbacks();
     76     // Named to be consistent with CarDrawerFragment to make copying code easier and less error
     77     // prone
     78     private final CarDrawerLayout mContainer;
     79     private final PagedListView mListView;
     80 //    private final CardView mTruncatedListCardView;
     81     private final ProgressBar mProgressBar;
     82     private final Context mContext;
     83     private final ViewAnimationController mPlvAnimationController;
     84     private final CardView mTruncatedListCardView;
     85     private final Stack<Integer> mClickCountStack = new Stack<>();
     86 
     87     private CarMenuCallbacks mCarMenuCallbacks;
     88     private DrawerApiAdapter mAdapter;
     89     private int mScrimColor = CarDrawerLayout.DEFAULT_SCRIM_COLOR;
     90     private boolean mIsDrawerOpen;
     91     private boolean mIsDrawerAnimating;
     92     private boolean mIsCapped;
     93     private int mItemsNumber;
     94     private int mDrawerMode;
     95     private CharSequence mContentTitle;
     96     private String mRootId;
     97     private boolean mRestartedFromDayNightMode;
     98     private CarUiEntry mUiEntry;
     99 
    100     public DrawerController(CarUiEntry uiEntry, View menuButton, CarDrawerLayout drawerLayout,
    101                             PagedListView listView, CardView cardView) {
    102         //mCarAppLayout = appLayout;
    103         menuButton.setOnClickListener(mMenuClickListener);
    104         mContainer = drawerLayout;
    105         mListView = listView;
    106         mUiEntry = uiEntry;
    107         mTruncatedListCardView = cardView;
    108         mListView.setDefaultItemDecoration(new DrawerMenuListDecoration(mListView.getContext()));
    109         mProgressBar = (ProgressBar) mContainer.findViewById(R.id.progress);
    110         mContext = mListView.getContext();
    111         mPlvAnimationController = new ViewAnimationController(
    112                 mListView, R.anim.car_list_in, R.anim.sdk_list_out, R.anim.car_list_pop_out);
    113         mRootId = null;
    114 
    115         mContainer.setDrawerListener(this);
    116         mContainer.setDrawerControllerListener(this);
    117         setAutoLightDarkMode();
    118     }
    119 
    120 
    121     @Override
    122     public void onDrawerOpened(View drawerView) {
    123         mIsDrawerOpen = true;
    124         mIsDrawerAnimating = false;
    125         mUiEntry.setMenuProgress(1.0f);
    126         // This can be null on day/night mode changes
    127         if (mCarMenuCallbacks != null) {
    128             mCarMenuCallbacks.onCarMenuOpened();
    129         }
    130     }
    131 
    132     @Override
    133     public void onDrawerClosed(View drawerView) {
    134         mIsDrawerOpen = false;
    135         mIsDrawerAnimating = false;
    136         clearMenu();
    137         mUiEntry.setMenuProgress(0);
    138         mUiEntry.setTitle(mContentTitle);
    139         // This can be null on day/night mode changes
    140         if (mCarMenuCallbacks != null) {
    141             mCarMenuCallbacks.onCarMenuClosed();
    142         }
    143     }
    144 
    145     @Override
    146     public void onDrawerStateChanged(int newState) {
    147     }
    148 
    149     @Override
    150     public void onDrawerOpening(View drawerView) {
    151         mIsDrawerAnimating = true;
    152         // This can be null on day/night mode changes
    153         if (mCarMenuCallbacks != null) {
    154             mCarMenuCallbacks.onCarMenuOpening();
    155         }
    156     }
    157 
    158     @Override
    159     public void onDrawerSlide(View drawerView, float slideOffset) {
    160         mUiEntry.setMenuProgress(slideOffset);
    161     }
    162 
    163     @Override
    164     public void onDrawerClosing(View drawerView) {
    165         mIsDrawerAnimating = true;
    166         // This can be null on day/night mode changes
    167         if (mCarMenuCallbacks != null) {
    168             mCarMenuCallbacks.onCarMenuClosing();
    169         }
    170     }
    171 
    172     @Override
    173     public void onItemClicked(Bundle item, int position) {
    174         // Don't allow selection while animating
    175         if (mPlvAnimationController.isAnimating()) {
    176             return;
    177         }
    178         int flags = item.getInt(KEY_FLAGS);
    179         String id = item.getString(KEY_ID);
    180 
    181         // Page number is 0 index, + 1 for the actual click.
    182         int clicksUsed = mListView.getPage(position) + 1;
    183         mClickCountStack.push(clicksUsed);
    184         mListView.setMaxPages(mListView.getMaxPages() - clicksUsed);
    185         mCarMenuCallbacks.onItemClicked(id);
    186         if ((flags & FLAG_BROWSABLE) != 0) {
    187             if (mListView.getMaxPages() == 0) {
    188                 mIsCapped = true;
    189             }
    190             CharSequence title = item.getString(KEY_TITLE);
    191             if (TextUtils.isEmpty(title)) {
    192                 title = mContentTitle;
    193             }
    194             mUiEntry.setTitleText(title);
    195             mTitles.push(title);
    196             if (!mSubscriptionIds.isEmpty()) {
    197                 mPlvAnimationController.enqueueExitAnimation(mClearAdapterRunnable);
    198             }
    199             mProgressBar.setVisibility(View.VISIBLE);
    200             if (!mSubscriptionIds.isEmpty()) {
    201                 mCarMenuCallbacks.unsubscribe(mSubscriptionIds.peek(), mSubscriptionCallbacks);
    202             }
    203             mSubscriptionIds.push(id);
    204             subscribe(id);
    205         } else {
    206             closeDrawer();
    207         }
    208     }
    209 
    210     @Override
    211     public boolean onItemLongClicked(Bundle item) {
    212         return mCarMenuCallbacks.onItemLongClicked(item.getString(KEY_ID));
    213     }
    214 
    215     @Override
    216     public void onBack() {
    217         backOrClose();
    218     }
    219 
    220     @Override
    221     public boolean onScroll() {
    222         // Consume scroll event if we are animating.
    223         return mPlvAnimationController.isAnimating();
    224     }
    225 
    226     public void setTitle(CharSequence title) {
    227         Log.d(TAG, "setTitle in drawer" + title);
    228         if (!TextUtils.isEmpty(title)) {
    229             mContentTitle = title;
    230             mUiEntry.showTitle();
    231             mUiEntry.setTitleText(title);
    232         } else {
    233             mUiEntry.hideTitle();
    234         }
    235     }
    236 
    237     public void setRootAndCallbacks(String rootId, CarMenuCallbacks callbacks) {
    238         mAdapter = new DrawerApiAdapter();
    239         mAdapter.setItemSelectedListener(this);
    240         mListView.setAdapter(mAdapter);
    241         mCarMenuCallbacks = callbacks;
    242         // HACK: Due to the handler, setRootId will be called after onRestoreState.
    243         // If onRestoreState has been called, the root id will already be set. So nothing to do.
    244         if (mSubscriptionIds.isEmpty()) {
    245             setRootId(rootId);
    246         } else {
    247             subscribe(mSubscriptionIds.peek());
    248             openDrawer();
    249         }
    250     }
    251 
    252     public void saveState(Bundle out) {
    253         out.putStringArray(KEY_IDS, mSubscriptionIds.toArray(new String[mSubscriptionIds.size()]));
    254         out.putStringArray(KEY_TITLES, mTitles.toArray(new String[mTitles.size()]));
    255         out.putString(KEY_ROOT, mRootId);
    256         out.putBoolean(KEY_DRAWERSTATE, mIsDrawerOpen);
    257         out.putIntegerArrayList(KEY_CLICK_STACK, new ArrayList<Integer>(mClickCountStack));
    258         out.putBoolean(KEY_IS_CAPPED, mIsCapped);
    259         out.putInt(KEY_MAX_PAGES, mListView.getMaxPages());
    260     }
    261 
    262     public void restoreState(Bundle in) {
    263         if (in != null) {
    264             // Restore subscribed CarMenu ids
    265             String[] ids = in.getStringArray(KEY_IDS);
    266             mSubscriptionIds.clear();
    267             if (ids != null) {
    268                 mSubscriptionIds.addAll(Arrays.asList(ids));
    269             }
    270             // Restore drawer titles if there are any
    271             String[] titles = in.getStringArray(KEY_TITLES);
    272             mTitles.clear();
    273             if (titles != null) {
    274                 mTitles.addAll(Arrays.asList(titles));
    275             }
    276             if (!mTitles.isEmpty()) {
    277                 mUiEntry.setTitleText(mTitles.peek());
    278             }
    279             mRootId = in.getString(KEY_ROOT);
    280             mIsDrawerOpen = in.getBoolean(KEY_DRAWERSTATE);
    281             ArrayList<Integer> clickCount = in.getIntegerArrayList(KEY_CLICK_STACK);
    282             mClickCountStack.clear();
    283             if (clickCount != null) {
    284                 mClickCountStack.addAll(clickCount);
    285             }
    286             mIsCapped = in.getBoolean(KEY_IS_CAPPED);
    287             mListView.setMaxPages(in.getInt(KEY_MAX_PAGES));
    288             if (!mRestartedFromDayNightMode && mIsDrawerOpen) {
    289                 closeDrawer();
    290             }
    291         }
    292     }
    293 
    294     public void setScrimColor(int color) {
    295         mScrimColor = color;
    296         mContainer.setScrimColor(color);
    297         updateViewFaders();
    298     }
    299 
    300     public void setAutoLightDarkMode() {
    301         mDrawerMode = MODE_AUTO;
    302         mContainer.setAutoDayNightMode();
    303         updateViewFaders();
    304     }
    305 
    306     public void setLightMode() {
    307         mDrawerMode = MODE_LIGHT;
    308         mContainer.setLightMode();
    309         updateViewFaders();
    310     }
    311 
    312     public void setDarkMode() {
    313         mDrawerMode = MODE_DARK;
    314         mContainer.setDarkMode();
    315         updateViewFaders();
    316     }
    317 
    318     public void openDrawer() {
    319         // If we have no root, then we can't open the drawer.
    320         if (mRootId == null) {
    321             return;
    322         }
    323         mContainer.openDrawer();
    324     }
    325 
    326     public void closeDrawer() {
    327         if (mRootId == null) {
    328             return;
    329         }
    330         mTruncatedListCardView.setVisibility(View.GONE);
    331         mPlvAnimationController.stopAndClearAnimations();
    332         mContainer.closeDrawer();
    333         mUiEntry.setTitle(mContentTitle);
    334     }
    335 
    336     public void setDrawerEnabled(boolean enabled) {
    337         if (enabled) {
    338             mContainer.setDrawerLockMode(CarDrawerLayout.LOCK_MODE_UNLOCKED);
    339         } else {
    340             mContainer.setDrawerLockMode(CarDrawerLayout.LOCK_MODE_LOCKED_CLOSED);
    341         }
    342     }
    343 
    344     public void showMenu(String id, String title) {
    345         // The app wants to show the menu associated with the given id. Create a fake item using the
    346         // given inputs and then pretend as if the user clicked on the item, so that the drawer
    347         // will subscribe to that menu id, set the title appropriately, and properly handle the
    348         // subscription stack.
    349         Bundle bundle = new Bundle();
    350         bundle.putString(KEY_ID, id);
    351         bundle.putString(KEY_TITLE, title);
    352         bundle.putInt(KEY_FLAGS, FLAG_BROWSABLE);
    353         onItemClicked(bundle, 0 /* position */);
    354     }
    355 
    356     public void setRootId(String rootId) {
    357         mRootId = rootId;
    358     }
    359 
    360     public void setRestartedFromDayNightMode(boolean restarted) {
    361         mRestartedFromDayNightMode = restarted;
    362     }
    363 
    364     public boolean isTruncatedList() {
    365         return mItemsNumber > mAdapter.getMaxItemsNumber();
    366     }
    367 
    368     private void clearMenu() {
    369         if (!mSubscriptionIds.isEmpty()) {
    370             mCarMenuCallbacks.unsubscribe(mSubscriptionIds.peek(), mSubscriptionCallbacks);
    371             mSubscriptionIds.clear();
    372             mTitles.clear();
    373         }
    374         mListView.setVisibility(View.GONE);
    375         mListView.resetMaxPages();
    376         mClickCountStack.clear();
    377         mIsCapped = false;
    378     }
    379 
    380     /**
    381      * Check if the drawer is inside of a CarAppLayout and add the relevant views if it is,
    382      * automagically add view faders for the correct views
    383      */
    384     private void updateViewFaders() {
    385         mContainer.removeViewFader(mStatusViewViewFader);
    386         mContainer.addViewFader(mStatusViewViewFader);
    387     }
    388 
    389     private void subscribe(String id) {
    390         mProgressBar.setVisibility(View.VISIBLE);
    391         mCarMenuCallbacks.subscribe(id, mSubscriptionCallbacks);
    392     }
    393 
    394     private final CarDrawerLayout.ViewFader mStatusViewViewFader = new CarDrawerLayout.ViewFader() {
    395         @Override
    396         public void setColor(int color) {
    397             mUiEntry.setMenuButtonColor(color);
    398         }
    399     };
    400 
    401     private void backOrClose() {
    402         if (mSubscriptionIds.size() > 1) {
    403             mPlvAnimationController.enqueueBackAnimation(mClearAdapterRunnable);
    404             mProgressBar.setVisibility(View.VISIBLE);
    405             mCarMenuCallbacks.unsubscribe(mSubscriptionIds.pop(),
    406                     mSubscriptionCallbacks);
    407             subscribe(mSubscriptionIds.peek());
    408             // Restore the title for this menu level.
    409             mTitles.pop();
    410             CharSequence title = mTitles.peek();
    411             if (TextUtils.isEmpty(title)) {
    412                 title = mContentTitle;
    413             }
    414             mUiEntry.setTitleText(title);
    415         } else {
    416             closeDrawer();
    417         }
    418     }
    419 
    420     private final View.OnClickListener mMenuClickListener = new View.OnClickListener() {
    421         @Override
    422         public void onClick(View view) {
    423             if (mIsDrawerAnimating || mCarMenuCallbacks.onMenuClicked()) {
    424                 return;
    425             }
    426             // Check if drawer has root set.
    427             if (mRootId == null) {
    428                 return;
    429             }
    430             mTruncatedListCardView.setVisibility(View.GONE);
    431             if (mIsDrawerOpen) {
    432                 if (!mClickCountStack.isEmpty()) {
    433                     mListView.setMaxPages(mListView.getMaxPages() + mClickCountStack.pop());
    434                 }
    435                 mIsCapped = false;
    436                 backOrClose();
    437             } else {
    438                 mSubscriptionIds.push(mRootId);
    439                 mTitles.push(mContentTitle);
    440                 subscribe(mRootId);
    441                 openDrawer();
    442             }
    443         }
    444     };
    445 
    446     private final Runnable mClearAdapterRunnable = new Runnable() {
    447         @Override
    448         public void run() {
    449             mListView.setVisibility(View.GONE);
    450         }
    451     };
    452 
    453     public void updateDayNightMode() {
    454         mContainer.findViewById(R.id.drawer).setBackgroundColor(
    455                 mContext.getResources().getColor(R.color.car_card));
    456         mListView.setAutoDayNightMode();
    457         switch (mDrawerMode) {
    458             case MODE_AUTO:
    459                 setAutoLightDarkMode();
    460                 break;
    461             case MODE_LIGHT:
    462                 setLightMode();
    463                 break;
    464             case MODE_DARK:
    465                 setDarkMode();
    466                 break;
    467         }
    468         updateViewFaders();
    469         RecyclerView rv = mListView.getRecyclerView();
    470         for (int i = 0; i < mAdapter.getItemCount(); ++i) {
    471             mAdapter.setDayNightModeColors(rv.findViewHolderForAdapterPosition(i));
    472         }
    473     }
    474 
    475     private static class ViewAnimationController implements Animation.AnimationListener {
    476         private final Animation mExitAnim;
    477         private final Animation mEnterAnim;
    478         private final Animation mBackAnim;
    479         private final View mView;
    480         private final Context mContext;
    481         private final Queue<Animation> mQueue = new LinkedList<>();
    482 
    483         private Runnable mOnEnterAnimStartRunnable;
    484         private Runnable mOnExitAnimCompleteRunnable;
    485 
    486         private Animation mCurrentAnimation;
    487 
    488         public ViewAnimationController(View view, int enter, int exit, int back) {
    489             mView = view;
    490             mContext = view.getContext();
    491 
    492             mEnterAnim = AnimationUtils.loadAnimation(mContext, enter);
    493             mExitAnim = AnimationUtils.loadAnimation(mContext, exit);
    494             mBackAnim = AnimationUtils.loadAnimation(mContext, back);
    495 
    496             mExitAnim.setAnimationListener(this);
    497             mEnterAnim.setAnimationListener(this);
    498             mBackAnim.setAnimationListener(this);
    499         }
    500 
    501         @Override
    502         public void onAnimationStart(Animation animation) {
    503             if (animation == mEnterAnim && mOnEnterAnimStartRunnable != null) {
    504                 mOnEnterAnimStartRunnable.run();
    505                 mOnEnterAnimStartRunnable = null;
    506             }
    507         }
    508 
    509         @Override
    510         public  void onAnimationEnd(Animation animation) {
    511             if ((animation == mExitAnim || animation == mBackAnim)
    512                     && mOnExitAnimCompleteRunnable != null) {
    513                 mOnExitAnimCompleteRunnable.run();
    514                 mOnExitAnimCompleteRunnable = null;
    515             }
    516             Animation nextAnimation = mQueue.poll();
    517             if (nextAnimation != null) {
    518                 mCurrentAnimation = animation;
    519                 mView.startAnimation(nextAnimation);
    520             } else {
    521                 mCurrentAnimation = null;
    522             }
    523        }
    524 
    525         @Override
    526         public void onAnimationRepeat(Animation animation) {
    527 
    528         }
    529 
    530         public void enqueueEnterAnimation(Runnable r) {
    531             if (r != null) {
    532                 mOnEnterAnimStartRunnable = r;
    533             }
    534             enqueueAnimation(mEnterAnim);
    535         }
    536 
    537         public void enqueueExitAnimation(Runnable r) {
    538             // If the view isn't visible, don't play the exit animation.
    539             // It will cause flicker.
    540             if (mView.getVisibility() != View.VISIBLE) {
    541                 return;
    542             }
    543             if (r != null) {
    544                 mOnExitAnimCompleteRunnable = r;
    545             }
    546             enqueueAnimation(mExitAnim);
    547         }
    548 
    549         public void enqueueBackAnimation(Runnable r) {
    550             // If the view isn't visible, don't play the back animation.
    551             if (mView.getVisibility() != View.VISIBLE) {
    552                 return;
    553             }
    554             if (r != null) {
    555                 mOnExitAnimCompleteRunnable = r;
    556             }
    557             enqueueAnimation(mBackAnim);
    558         }
    559 
    560         public synchronized void stopAndClearAnimations() {
    561             if (mExitAnim.hasStarted()) {
    562                 mExitAnim.cancel();
    563             }
    564 
    565             if (mEnterAnim.hasStarted()) {
    566                 mEnterAnim.cancel();
    567             }
    568 
    569             mQueue.clear();
    570             mCurrentAnimation = null;
    571         }
    572 
    573         public boolean isAnimating() {
    574             return mCurrentAnimation != null;
    575         }
    576 
    577         private synchronized void enqueueAnimation(final Animation animation) {
    578             if (mQueue.contains(animation)) {
    579                 return;
    580             }
    581             if (mCurrentAnimation != null) {
    582                 mQueue.add(animation);
    583             } else {
    584                 mCurrentAnimation = animation;
    585                 mView.startAnimation(animation);
    586             }
    587         }
    588     }
    589 
    590     private class SubscriptionCallbacks extends android.car.app.menu.SubscriptionCallbacks {
    591         private final Object mItemLock = new Object();
    592         private volatile List<Bundle> mItems;
    593 
    594         @Override
    595         public void onChildrenLoaded(String parentId, final List<Bundle> items) {
    596             if (mSubscriptionIds.isEmpty() || parentId.equals(mSubscriptionIds.peek())) {
    597                 // Add unavailable category explanation at the first item of menu.
    598                 if (mIsCapped) {
    599                     Bundle extra = new Bundle();
    600                     extra.putString(KEY_ID, KEY_ID_UNAVAILABLE_CATEGORY);
    601                     items.add(0, extra);
    602                 }
    603                 mItems = items;
    604                 mItemsNumber = mItems.size();
    605                 mProgressBar.setVisibility(View.GONE);
    606                 mPlvAnimationController.enqueueEnterAnimation(new Runnable() {
    607                     @Override
    608                     public void run() {
    609                         synchronized (mItemLock) {
    610                             mAdapter.setItems(mItems, mIsCapped);
    611                             mListView.setVisibility(View.VISIBLE);
    612                             mItems = null;
    613                         }
    614                         mListView.scrollToPosition(mAdapter.getFirstItemIndex());
    615                     }
    616                 });
    617             }
    618         }
    619 
    620         @Override
    621         public void onError(String id) {
    622             // TODO: do something useful here.
    623         }
    624 
    625         @Override
    626         public void onChildChanged(String parentId, Bundle bundle) {
    627             if (!mSubscriptionIds.isEmpty() && parentId.equals(mSubscriptionIds.peek())) {
    628                 // List is still animating, so adapter hasn't been updated. Update the list that
    629                 // needs to be set.
    630                 String id = bundle.getString(KEY_ID);
    631                 synchronized (mItemLock) {
    632                     if (mItems != null) {
    633                         for (Bundle item : mItems) {
    634                             if (item.getString(KEY_ID).equals(id)) {
    635                                 item.putAll(bundle);
    636                                 break;
    637                             }
    638                         }
    639                         return;
    640                     }
    641                 }
    642                 RecyclerView rv = mListView.getRecyclerView();
    643                 RecyclerView.ViewHolder holder = rv.findViewHolderForItemId(id.hashCode());
    644                 mAdapter.onChildChanged(holder, bundle);
    645             }
    646         }
    647     }
    648 
    649     private class DrawerMenuListDecoration extends PagedListView.Decoration {
    650 
    651         public DrawerMenuListDecoration(Context context) {
    652             super(context);
    653         }
    654 
    655         @Override
    656         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    657             if (mAdapter != null && mAdapter.isEmptyPlaceholder()) {
    658                 return;
    659             }
    660             super.onDrawOver(c, parent, state);
    661         }
    662     }
    663 }
    664