• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.car.app;
18 
19 import android.content.res.Configuration;
20 import android.os.Bundle;
21 import android.support.annotation.LayoutRes;
22 import android.support.annotation.NonNull;
23 import android.support.car.ui.PagedListView;
24 import android.support.v4.widget.DrawerLayout;
25 import android.support.v7.app.ActionBarDrawerToggle;
26 import android.support.v7.app.AppCompatActivity;
27 import android.support.v7.widget.Toolbar;
28 import android.view.Gravity;
29 import android.view.LayoutInflater;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.ProgressBar;
34 
35 import com.android.car.stream.ui.R;
36 
37 import java.util.Stack;
38 
39 /**
40  * Common base Activity for car apps that need to present a Drawer.
41  * <p>
42  * This Activity manages the overall layout. To use it sub-classes need to:
43  * <ul>
44  *     <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.</li>
45  *     <li>Add their main content using {@link #setMainContent(int)} or
46  *     {@link #setMainContent(View)}. They can also add fragments to the main-content container by
47  *     obtaining its id using {@link #getContentContainerId()}</li>
48  * </ul>
49  * This class will take care of drawer toggling and display.
50  * <p>
51  * The rootAdapter can implement nested-navigation, in its click-handling, by passing the
52  * CarDrawerAdapter for the next level to {@link #switchToAdapter(CarDrawerAdapter)}. This
53  * activity will maintain a stack of such adapters. When the user navigates up, it will pop the top
54  * adapter off and display its contents again.
55  * <p>
56  * Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
57  * derivative.
58  * <p>
59  * NOTE: This version is based on a regular Activity unlike car-support-lib's CarDrawerActivity
60  * which is based on CarActivity.
61  */
62 public abstract class CarDrawerActivity extends AppCompatActivity {
63     private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
64 
65     private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
66     private DrawerLayout mDrawerLayout;
67     private PagedListView mDrawerList;
68     private ProgressBar mProgressBar;
69     private View mDrawerContent;
70     private Toolbar mToolbar;
71     private ActionBarDrawerToggle mDrawerToggle;
72 
73     @Override
onCreate(Bundle savedInstanceState)74     protected void onCreate(Bundle savedInstanceState) {
75         super.onCreate(savedInstanceState);
76 
77         setContentView(R.layout.car_drawer_activity);
78         mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
79         mDrawerContent = findViewById(R.id.drawer_content);
80         mDrawerList = (PagedListView)findViewById(R.id.drawer_list);
81         // Let drawer list show unlimited pages of items.
82         mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
83         mProgressBar = (ProgressBar)findViewById(R.id.drawer_progress);
84 
85         mToolbar = (Toolbar) findViewById(R.id.main_toolbar);
86         setSupportActionBar(mToolbar);
87 
88         // Init drawer adapter stack.
89         CarDrawerAdapter rootAdapter = getRootAdapter();
90         mAdapterStack.push(rootAdapter);
91         setToolbarTitleFrom(rootAdapter);
92         mDrawerList.setAdapter(rootAdapter);
93 
94         setupDrawerToggling();
95     }
96 
setToolbarTitleFrom(CarDrawerAdapter adapter)97     private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
98         if (adapter.getTitle() != null) {
99             mToolbar.setTitle(adapter.getTitle());
100         } else {
101             throw new RuntimeException("CarDrawerAdapter subclass must supply title via " +
102                 " setTitle()");
103         }
104         adapter.setTitleChangeListener(mToolbar::setTitle);
105     }
106 
107     /**
108      * Set main content to display in this Activity. It will be added to R.id.content_frame in
109      * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
110      *
111      * @param view View to display as main content.
112      */
setMainContent(View view)113     public void setMainContent(View view) {
114         ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
115         parent.addView(view);
116     }
117 
118     /**
119      * Set main content to display in this Activity. It will be added to R.id.content_frame in
120      * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
121      *
122      * @param resourceId Layout to display as main content.
123      */
setMainContent(@ayoutRes int resourceId)124     public void setMainContent(@LayoutRes int resourceId) {
125         ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
126         LayoutInflater inflater = getLayoutInflater();
127         inflater.inflate(resourceId, parent, true);
128     }
129 
130     /**
131      * @return Adapter for root content of the Drawer.
132      */
getRootAdapter()133     protected abstract CarDrawerAdapter getRootAdapter();
134 
135     /**
136      * Used to pass in next level of items to display in the Drawer, including updated title. It is
137      * pushed on top of the existing adapter in a stack. Navigating up from this level later will
138      * pop this adapter off and surface contents of the next adapter at the top of the stack (and
139      * its title).
140      *
141      * @param adapter Adapter for next level of content in the drawer.
142      */
switchToAdapter(CarDrawerAdapter adapter)143     public final void switchToAdapter(CarDrawerAdapter adapter) {
144         mAdapterStack.peek().setTitleChangeListener(null);
145         mAdapterStack.push(adapter);
146         setTitleAndSwitchToAdapter(adapter);
147     }
148 
149     /**
150      * Close the drawer if open.
151      */
closeDrawer()152     public void closeDrawer() {
153         if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
154             mDrawerLayout.closeDrawer(Gravity.LEFT);
155         }
156     }
157 
158     /**
159      * Used to open the drawer.
160      */
openDrawer()161     public void openDrawer() {
162         if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
163             mDrawerLayout.openDrawer(Gravity.LEFT);
164         }
165     }
166 
167     /**
168      * @param listener Listener to be notified of Drawer events.
169      */
addDrawerListener(@onNull DrawerLayout.DrawerListener listener)170     public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
171         mDrawerLayout.addDrawerListener(listener);
172     }
173 
174     /**
175      * @param listener Listener to be notified of Drawer events.
176      */
removeDrawerListener(@onNull DrawerLayout.DrawerListener listener)177     public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
178         mDrawerLayout.removeDrawerListener(listener);
179     }
180 
181     /**
182      * Used to switch between the Drawer PagedListView and the "loading" progress-bar while the next
183      * level's adapter contents are being fetched.
184      *
185      * @param enable If true, the progress-bar is displayed. If false, the Drawer PagedListView is
186      *               added.
187      */
showLoadingProgressBar(boolean enable)188     public void showLoadingProgressBar(boolean enable) {
189         mDrawerList.setVisibility(enable ? View.INVISIBLE : View.VISIBLE);
190         mProgressBar.setVisibility(enable ? View.VISIBLE : View.GONE);
191     }
192 
193     /**
194      * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
195      * content/fragments inside here.
196      *
197      * @return Id of FrameLayout where main content of the subclass Activity can be added.
198      */
getContentContainerId()199     protected int getContentContainerId() {
200         return R.id.content_frame;
201     }
202 
setupDrawerToggling()203     private void setupDrawerToggling() {
204         mDrawerToggle = new ActionBarDrawerToggle(
205                 this,                  /* host Activity */
206                 mDrawerLayout,         /* DrawerLayout object */
207                 // The string id's below are for accessibility. However
208                 // since they won't be used in cars, we just pass car_drawer_unused.
209                 R.string.car_drawer_unused,
210                 R.string.car_drawer_unused
211         );
212         mDrawerLayout.addDrawerListener(mDrawerToggle);
213         mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
214             @Override
215             public void onDrawerSlide(View drawerView, float slideOffset) {
216                 setTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
217             }
218             @Override
219             public void onDrawerOpened(View drawerView) {}
220             @Override
221             public void onDrawerClosed(View drawerView) {
222                 // If drawer is closed for any reason, revert stack/drawer to initial root state.
223                 cleanupStackAndShowRoot();
224                 mDrawerList.getRecyclerView().scrollToPosition(0);
225             }
226             @Override
227             public void onDrawerStateChanged(int newState) {}
228         });
229         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
230         getSupportActionBar().setHomeButtonEnabled(true);
231     }
232 
setTitleAndArrowColor(boolean drawerOpen)233     private void setTitleAndArrowColor(boolean drawerOpen) {
234         // When drawer open, use car_title, which resolves to appropriate color depending on
235         // day-night mode. When drawer is closed, we always use light color.
236         int titleColorResId =  drawerOpen ?
237                 R.color.car_title : R.color.car_title_light;
238         int titleColor = getColor(titleColorResId);
239         mToolbar.setTitleTextColor(titleColor);
240         mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
241     }
242 
243     @Override
onPostCreate(Bundle savedInstanceState)244     protected void onPostCreate(Bundle savedInstanceState) {
245         super.onPostCreate(savedInstanceState);
246         // Sync the toggle state after onRestoreInstanceState has occurred.
247         mDrawerToggle.syncState();
248 
249         // In case we're restarting after a config change (e.g. day, night switch), set colors
250         // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
251         // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
252         setTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
253     }
254 
255     @Override
onConfigurationChanged(Configuration newConfig)256     public void onConfigurationChanged(Configuration newConfig) {
257         super.onConfigurationChanged(newConfig);
258         // Pass any configuration change to the drawer toggls
259         mDrawerToggle.onConfigurationChanged(newConfig);
260     }
261 
262     @Override
onOptionsItemSelected(MenuItem item)263     public boolean onOptionsItemSelected(MenuItem item) {
264         // Handle home-click and see if we can navigate up in the drawer.
265         if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
266             return true;
267         }
268 
269         // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
270         if (mDrawerToggle.onOptionsItemSelected(item)) {
271             return true;
272         }
273 
274         return super.onOptionsItemSelected(item);
275     }
276 
setTitleAndSwitchToAdapter(CarDrawerAdapter adapter)277     private void setTitleAndSwitchToAdapter(CarDrawerAdapter adapter) {
278         setToolbarTitleFrom(adapter);
279         // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
280         // car_menu_list_item_normal, car_menu_list_item_small and car_list_empty layouts.
281         mDrawerList.getRecyclerView().setAdapter(adapter);
282         mDrawerList.getRecyclerView().scrollToPosition(0);
283     }
284 
maybeHandleUpClick()285     private boolean maybeHandleUpClick() {
286         if (mAdapterStack.size() > 1) {
287             CarDrawerAdapter adapter = mAdapterStack.pop();
288             adapter.setTitleChangeListener(null);
289             adapter.cleanup();
290             setTitleAndSwitchToAdapter(mAdapterStack.peek());
291             return true;
292         }
293         return false;
294     }
295 
296     /** Clears stack down to root adapter and switches to root adapter. */
cleanupStackAndShowRoot()297     private void cleanupStackAndShowRoot() {
298         while (mAdapterStack.size() > 1) {
299             CarDrawerAdapter adapter = mAdapterStack.pop();
300             adapter.setTitleChangeListener(null);
301             adapter.cleanup();
302         }
303         setTitleAndSwitchToAdapter(mAdapterStack.peek());
304     }
305 }
306