• 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.v4.widget.DrawerLayout;
24 import android.support.v7.app.ActionBarDrawerToggle;
25 import android.support.v7.app.AppCompatActivity;
26 import android.support.v7.widget.Toolbar;
27 import android.view.Gravity;
28 import android.view.LayoutInflater;
29 import android.view.MenuItem;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.ProgressBar;
33 
34 import com.android.car.stream.ui.R;
35 import com.android.car.view.PagedListView;
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 
97     @Override
onStop()98     protected void onStop() {
99         super.onStop();
100         mDrawerLayout.closeDrawer(Gravity.LEFT, false /* animation */);
101     }
102 
setToolbarTitleFrom(CarDrawerAdapter adapter)103     private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
104         if (adapter.getTitle() != null) {
105             mToolbar.setTitle(adapter.getTitle());
106         } else {
107             throw new RuntimeException("CarDrawerAdapter subclass must supply title via " +
108                 " setTitle()");
109         }
110         adapter.setTitleChangeListener(mToolbar::setTitle);
111     }
112 
113     /**
114      * Set main content to display in this Activity. It will be added to R.id.content_frame in
115      * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
116      *
117      * @param view View to display as main content.
118      */
setMainContent(View view)119     public void setMainContent(View view) {
120         ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
121         parent.addView(view);
122     }
123 
124     /**
125      * Set main content to display in this Activity. It will be added to R.id.content_frame in
126      * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
127      *
128      * @param resourceId Layout to display as main content.
129      */
setMainContent(@ayoutRes int resourceId)130     public void setMainContent(@LayoutRes int resourceId) {
131         ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
132         LayoutInflater inflater = getLayoutInflater();
133         inflater.inflate(resourceId, parent, true);
134     }
135 
136     /**
137      * @return Adapter for root content of the Drawer.
138      */
getRootAdapter()139     protected abstract CarDrawerAdapter getRootAdapter();
140 
141     /**
142      * Used to pass in next level of items to display in the Drawer, including updated title. It is
143      * pushed on top of the existing adapter in a stack. Navigating up from this level later will
144      * pop this adapter off and surface contents of the next adapter at the top of the stack (and
145      * its title).
146      *
147      * @param adapter Adapter for next level of content in the drawer.
148      */
switchToAdapter(CarDrawerAdapter adapter)149     public final void switchToAdapter(CarDrawerAdapter adapter) {
150         mAdapterStack.peek().setTitleChangeListener(null);
151         mAdapterStack.push(adapter);
152         setTitleAndSwitchToAdapter(adapter);
153     }
154 
155     /**
156      * Close the drawer if open.
157      */
closeDrawer()158     public void closeDrawer() {
159         if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
160             mDrawerLayout.closeDrawer(Gravity.LEFT);
161         }
162     }
163 
164     /**
165      * Used to open the drawer.
166      */
openDrawer()167     public void openDrawer() {
168         if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
169             mDrawerLayout.openDrawer(Gravity.LEFT);
170         }
171     }
172 
173     /**
174      * @param listener Listener to be notified of Drawer events.
175      */
addDrawerListener(@onNull DrawerLayout.DrawerListener listener)176     public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
177         mDrawerLayout.addDrawerListener(listener);
178     }
179 
180     /**
181      * @param listener Listener to be notified of Drawer events.
182      */
removeDrawerListener(@onNull DrawerLayout.DrawerListener listener)183     public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
184         mDrawerLayout.removeDrawerListener(listener);
185     }
186 
187     /**
188      * Used to switch between the Drawer PagedListView and the "loading" progress-bar while the next
189      * level's adapter contents are being fetched.
190      *
191      * @param enable If true, the progress-bar is displayed. If false, the Drawer PagedListView is
192      *               added.
193      */
showLoadingProgressBar(boolean enable)194     public void showLoadingProgressBar(boolean enable) {
195         mDrawerList.setVisibility(enable ? View.INVISIBLE : View.VISIBLE);
196         mProgressBar.setVisibility(enable ? View.VISIBLE : View.GONE);
197     }
198 
199     /**
200      * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
201      * content/fragments inside here.
202      *
203      * @return Id of FrameLayout where main content of the subclass Activity can be added.
204      */
getContentContainerId()205     protected int getContentContainerId() {
206         return R.id.content_frame;
207     }
208 
setupDrawerToggling()209     private void setupDrawerToggling() {
210         mDrawerToggle = new ActionBarDrawerToggle(
211                 this,                  /* host Activity */
212                 mDrawerLayout,         /* DrawerLayout object */
213                 R.string.car_drawer_open,
214                 R.string.car_drawer_close
215         );
216         mDrawerLayout.addDrawerListener(mDrawerToggle);
217         mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
218             @Override
219             public void onDrawerSlide(View drawerView, float slideOffset) {
220                 setTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
221             }
222             @Override
223             public void onDrawerOpened(View drawerView) {}
224             @Override
225             public void onDrawerClosed(View drawerView) {
226                 // If drawer is closed for any reason, revert stack/drawer to initial root state.
227                 cleanupStackAndShowRoot();
228                 scrollToPosition(0);
229             }
230             @Override
231             public void onDrawerStateChanged(int newState) {}
232         });
233         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
234         getSupportActionBar().setHomeButtonEnabled(true);
235     }
236 
setTitleAndArrowColor(boolean drawerOpen)237     private void setTitleAndArrowColor(boolean drawerOpen) {
238         // When drawer open, use car_title, which resolves to appropriate color depending on
239         // day-night mode. When drawer is closed, we always use light color.
240         int titleColorResId =  drawerOpen ?
241                 R.color.car_title : R.color.car_title_light;
242         int titleColor = getColor(titleColorResId);
243         mToolbar.setTitleTextColor(titleColor);
244         mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
245     }
246 
247     @Override
onPostCreate(Bundle savedInstanceState)248     protected void onPostCreate(Bundle savedInstanceState) {
249         super.onPostCreate(savedInstanceState);
250         // Sync the toggle state after onRestoreInstanceState has occurred.
251         mDrawerToggle.syncState();
252 
253         // In case we're restarting after a config change (e.g. day, night switch), set colors
254         // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
255         // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
256         setTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
257     }
258 
259     @Override
onConfigurationChanged(Configuration newConfig)260     public void onConfigurationChanged(Configuration newConfig) {
261         super.onConfigurationChanged(newConfig);
262         // Pass any configuration change to the drawer toggls
263         mDrawerToggle.onConfigurationChanged(newConfig);
264     }
265 
266     @Override
onOptionsItemSelected(MenuItem item)267     public boolean onOptionsItemSelected(MenuItem item) {
268         // Handle home-click and see if we can navigate up in the drawer.
269         if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
270             return true;
271         }
272 
273         // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
274         if (mDrawerToggle.onOptionsItemSelected(item)) {
275             return true;
276         }
277 
278         return super.onOptionsItemSelected(item);
279     }
280 
setTitleAndSwitchToAdapter(CarDrawerAdapter adapter)281     private void setTitleAndSwitchToAdapter(CarDrawerAdapter adapter) {
282         setToolbarTitleFrom(adapter);
283         // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
284         // car_menu_list_item_normal, car_menu_list_item_small and car_list_empty layouts.
285         mDrawerList.getRecyclerView().setAdapter(adapter);
286         scrollToPosition(0);
287     }
288 
scrollToPosition(int position)289     public void scrollToPosition(int position) {
290         mDrawerList.getRecyclerView().smoothScrollToPosition(position);
291     }
292 
maybeHandleUpClick()293     private boolean maybeHandleUpClick() {
294         if (mAdapterStack.size() > 1) {
295             CarDrawerAdapter adapter = mAdapterStack.pop();
296             adapter.setTitleChangeListener(null);
297             adapter.cleanup();
298             setTitleAndSwitchToAdapter(mAdapterStack.peek());
299             return true;
300         }
301         return false;
302     }
303 
304     /** Clears stack down to root adapter and switches to root adapter. */
cleanupStackAndShowRoot()305     private void cleanupStackAndShowRoot() {
306         while (mAdapterStack.size() > 1) {
307             CarDrawerAdapter adapter = mAdapterStack.pop();
308             adapter.setTitleChangeListener(null);
309             adapter.cleanup();
310         }
311         setTitleAndSwitchToAdapter(mAdapterStack.peek());
312     }
313 }
314