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 
DrawerController(CarUiEntry uiEntry, View menuButton, CarDrawerLayout drawerLayout, PagedListView listView, CardView cardView)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
onDrawerOpened(View drawerView)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
onDrawerClosed(View drawerView)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
onDrawerStateChanged(int newState)146     public void onDrawerStateChanged(int newState) {
147     }
148 
149     @Override
onDrawerOpening(View drawerView)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
onDrawerSlide(View drawerView, float slideOffset)159     public void onDrawerSlide(View drawerView, float slideOffset) {
160         mUiEntry.setMenuProgress(slideOffset);
161     }
162 
163     @Override
onDrawerClosing(View drawerView)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
onItemClicked(Bundle item, int position)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
onItemLongClicked(Bundle item)211     public boolean onItemLongClicked(Bundle item) {
212         return mCarMenuCallbacks.onItemLongClicked(item.getString(KEY_ID));
213     }
214 
215     @Override
onBack()216     public void onBack() {
217         backOrClose();
218     }
219 
220     @Override
onScroll()221     public boolean onScroll() {
222         // Consume scroll event if we are animating.
223         return mPlvAnimationController.isAnimating();
224     }
225 
setTitle(CharSequence title)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 
setRootAndCallbacks(String rootId, CarMenuCallbacks callbacks)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 
saveState(Bundle out)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 
restoreState(Bundle in)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 
setScrimColor(int color)294     public void setScrimColor(int color) {
295         mScrimColor = color;
296         mContainer.setScrimColor(color);
297         updateViewFaders();
298     }
299 
setAutoLightDarkMode()300     public void setAutoLightDarkMode() {
301         mDrawerMode = MODE_AUTO;
302         mContainer.setAutoDayNightMode();
303         updateViewFaders();
304     }
305 
setLightMode()306     public void setLightMode() {
307         mDrawerMode = MODE_LIGHT;
308         mContainer.setLightMode();
309         updateViewFaders();
310     }
311 
setDarkMode()312     public void setDarkMode() {
313         mDrawerMode = MODE_DARK;
314         mContainer.setDarkMode();
315         updateViewFaders();
316     }
317 
openDrawer()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 
closeDrawer()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 
setDrawerEnabled(boolean enabled)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 
showMenu(String id, String title)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 
setRootId(String rootId)356     public void setRootId(String rootId) {
357         mRootId = rootId;
358     }
359 
setRestartedFromDayNightMode(boolean restarted)360     public void setRestartedFromDayNightMode(boolean restarted) {
361         mRestartedFromDayNightMode = restarted;
362     }
363 
isTruncatedList()364     public boolean isTruncatedList() {
365         return mItemsNumber > mAdapter.getMaxItemsNumber();
366     }
367 
clearMenu()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      */
updateViewFaders()384     private void updateViewFaders() {
385         mContainer.removeViewFader(mStatusViewViewFader);
386         mContainer.addViewFader(mStatusViewViewFader);
387     }
388 
subscribe(String id)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 
backOrClose()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 
updateDayNightMode()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 
ViewAnimationController(View view, int enter, int exit, int back)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
onAnimationStart(Animation animation)502         public void onAnimationStart(Animation animation) {
503             if (animation == mEnterAnim && mOnEnterAnimStartRunnable != null) {
504                 mOnEnterAnimStartRunnable.run();
505                 mOnEnterAnimStartRunnable = null;
506             }
507         }
508 
509         @Override
onAnimationEnd(Animation animation)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
onAnimationRepeat(Animation animation)526         public void onAnimationRepeat(Animation animation) {
527 
528         }
529 
enqueueEnterAnimation(Runnable r)530         public void enqueueEnterAnimation(Runnable r) {
531             if (r != null) {
532                 mOnEnterAnimStartRunnable = r;
533             }
534             enqueueAnimation(mEnterAnim);
535         }
536 
enqueueExitAnimation(Runnable r)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 
enqueueBackAnimation(Runnable r)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 
stopAndClearAnimations()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 
isAnimating()573         public boolean isAnimating() {
574             return mCurrentAnimation != null;
575         }
576 
enqueueAnimation(final Animation animation)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
onChildrenLoaded(String parentId, final List<Bundle> items)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
onError(String id)621         public void onError(String id) {
622             // TODO: do something useful here.
623         }
624 
625         @Override
onChildChanged(String parentId, Bundle bundle)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 
DrawerMenuListDecoration(Context context)651         public DrawerMenuListDecoration(Context context) {
652             super(context);
653         }
654 
655         @Override
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)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