• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.documentsui;
18 
19 import static com.android.documentsui.base.SharedMinimal.VERBOSE;
20 
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.graphics.Outline;
24 import android.graphics.drawable.ColorDrawable;
25 import android.graphics.drawable.Drawable;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewOutlineProvider;
29 import android.view.Window;
30 import android.view.WindowManager;
31 import android.widget.FrameLayout;
32 
33 import androidx.annotation.ColorRes;
34 import androidx.annotation.Nullable;
35 import androidx.appcompat.widget.Toolbar;
36 import androidx.core.content.ContextCompat;
37 
38 import com.android.documentsui.base.RootInfo;
39 import com.android.documentsui.base.State;
40 import com.android.documentsui.base.UserId;
41 import com.android.documentsui.dirlist.AnimationView;
42 import com.android.documentsui.util.VersionUtils;
43 
44 import com.google.android.material.appbar.AppBarLayout;
45 import com.google.android.material.appbar.CollapsingToolbarLayout;
46 
47 import java.util.function.IntConsumer;
48 
49 /**
50  * A facade over the portions of the app and drawer toolbars.
51  */
52 public class NavigationViewManager implements AppBarLayout.OnOffsetChangedListener {
53 
54     private static final String TAG = "NavigationViewManager";
55 
56     private final DrawerController mDrawer;
57     private final Toolbar mToolbar;
58     private final BaseActivity mActivity;
59     private final View mHeader;
60     private final State mState;
61     private final NavigationViewManager.Environment mEnv;
62     private final Breadcrumb mBreadcrumb;
63     private final ProfileTabs mProfileTabs;
64     private final View mSearchBarView;
65     private final CollapsingToolbarLayout mCollapsingBarLayout;
66     private final Drawable mDefaultActionBarBackground;
67     private final ViewOutlineProvider mDefaultOutlineProvider;
68     private final ViewOutlineProvider mSearchBarOutlineProvider;
69     private final boolean mShowSearchBar;
70 
71     private boolean mIsActionModeActivated = false;
72     private @ColorRes int mDefaultStatusBarColorResId;
73 
NavigationViewManager( BaseActivity activity, DrawerController drawer, State state, NavigationViewManager.Environment env, Breadcrumb breadcrumb, View tabLayoutContainer, UserIdManager userIdManager)74     public NavigationViewManager(
75             BaseActivity activity,
76             DrawerController drawer,
77             State state,
78             NavigationViewManager.Environment env,
79             Breadcrumb breadcrumb,
80             View tabLayoutContainer,
81             UserIdManager userIdManager) {
82 
83         mActivity = activity;
84         mToolbar = activity.findViewById(R.id.toolbar);
85         mHeader = activity.findViewById(R.id.directory_header);
86         mDrawer = drawer;
87         mState = state;
88         mEnv = env;
89         mBreadcrumb = breadcrumb;
90         mBreadcrumb.setup(env, state, this::onNavigationItemSelected);
91         mProfileTabs = new ProfileTabs(tabLayoutContainer, mState, userIdManager, mEnv, activity);
92 
93         mToolbar.setNavigationOnClickListener(
94                 new View.OnClickListener() {
95                     @Override
96                     public void onClick(View v) {
97                         onNavigationIconClicked();
98                     }
99                 });
100         mSearchBarView = activity.findViewById(R.id.searchbar_title);
101         mCollapsingBarLayout = activity.findViewById(R.id.collapsing_toolbar);
102         mDefaultActionBarBackground = mToolbar.getBackground();
103         mDefaultOutlineProvider = mToolbar.getOutlineProvider();
104         mShowSearchBar = activity.getResources().getBoolean(R.bool.show_search_bar);
105 
106         final int[] styledAttrs = {android.R.attr.statusBarColor};
107         TypedArray a = mActivity.obtainStyledAttributes(styledAttrs);
108         mDefaultStatusBarColorResId = a.getResourceId(0, -1);
109         if (mDefaultStatusBarColorResId == -1) {
110             Log.w(TAG, "Retrieve statusBarColorResId from theme failed, assigned default");
111             mDefaultStatusBarColorResId = R.color.app_background_color;
112         }
113         a.recycle();
114 
115         final Resources resources = mToolbar.getResources();
116         final int radius = resources.getDimensionPixelSize(R.dimen.search_bar_radius);
117         final int marginStart =
118                 resources.getDimensionPixelSize(R.dimen.search_bar_background_margin_start);
119         final int marginEnd =
120                 resources.getDimensionPixelSize(R.dimen.search_bar_background_margin_end);
121         mSearchBarOutlineProvider = new ViewOutlineProvider() {
122             @Override
123             public void getOutline(View view, Outline outline) {
124                 outline.setRoundRect(marginStart, 0,
125                         view.getWidth() - marginEnd, view.getHeight(), radius);
126             }
127         };
128     }
129 
130     @Override
onOffsetChanged(AppBarLayout appBarLayout, int offset)131     public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
132         if (!VersionUtils.isAtLeastS()) {
133             return;
134         }
135 
136         // For S+ Only. Change toolbar color dynamically based on scroll offset.
137         // Usually this can be done in xml using app:contentScrim and app:statusBarScrim, however
138         // in our case since we also put directory_header.xml inside the CollapsingToolbarLayout,
139         // the scrim will also cover the directory header. Long term need to think about how to
140         // move directory_header out of the AppBarLayout.
141 
142         Window window = mActivity.getWindow();
143         View actionBar = window.getDecorView().findViewById(R.id.action_mode_bar);
144         int dynamicHeaderColor = ContextCompat.getColor(mActivity,
145                 offset == 0 ? mDefaultStatusBarColorResId : R.color.color_surface_header);
146         if (actionBar != null) {
147             // Action bar needs to be updated separately for selection mode.
148             actionBar.setBackgroundColor(dynamicHeaderColor);
149         }
150 
151         window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
152         window.setStatusBarColor(dynamicHeaderColor);
153         if (shouldShowSearchBar()) {
154             // Do not change search bar background.
155         } else {
156             mToolbar.setBackground(new ColorDrawable(dynamicHeaderColor));
157         }
158     }
159 
setSearchBarClickListener(View.OnClickListener listener)160     public void setSearchBarClickListener(View.OnClickListener listener) {
161         mSearchBarView.setOnClickListener(listener);
162     }
163 
getProfileTabsAddons()164     public ProfileTabsAddons getProfileTabsAddons() {
165         return mProfileTabs;
166     }
167 
168     /**
169      * Sets a listener to the profile tabs.
170      */
setProfileTabsListener(ProfileTabs.Listener listener)171     public void setProfileTabsListener(ProfileTabs.Listener listener) {
172         mProfileTabs.setListener(listener);
173     }
174 
onNavigationIconClicked()175     private void onNavigationIconClicked() {
176         if (mDrawer.isPresent()) {
177             mDrawer.setOpen(true);
178         }
179     }
180 
onNavigationItemSelected(int position)181     void onNavigationItemSelected(int position) {
182         boolean changed = false;
183         while (mState.stack.size() > position + 1) {
184             changed = true;
185             mState.stack.pop();
186         }
187         if (changed) {
188             mEnv.refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
189         }
190     }
191 
getSelectedUser()192     public UserId getSelectedUser() {
193         return mProfileTabs.getSelectedUser();
194     }
195 
setActionModeActivated(boolean actionModeActivated)196     public void setActionModeActivated(boolean actionModeActivated) {
197         mIsActionModeActivated = actionModeActivated;
198         update();
199     }
200 
update()201     public void update() {
202         updateScrollFlag();
203         updateToolbar();
204         mProfileTabs.updateView();
205 
206         // TODO: Looks to me like this block is never getting hit.
207         if (mEnv.isSearchExpanded()) {
208             mToolbar.setTitle(null);
209             mBreadcrumb.show(false);
210             return;
211         }
212 
213         mDrawer.setTitle(mEnv.getDrawerTitle());
214 
215         mToolbar.setNavigationIcon(getActionBarIcon());
216         mToolbar.setNavigationContentDescription(R.string.drawer_open);
217 
218         if (shouldShowSearchBar()) {
219             mBreadcrumb.show(false);
220             mToolbar.setTitle(null);
221             mSearchBarView.setVisibility(View.VISIBLE);
222         } else {
223             mSearchBarView.setVisibility(View.GONE);
224             String title = mState.stack.size() <= 1
225                     ? mEnv.getCurrentRoot().title : mState.stack.getTitle();
226             if (VERBOSE) Log.v(TAG, "New toolbar title is: " + title);
227             mToolbar.setTitle(title);
228             mBreadcrumb.show(true);
229             mBreadcrumb.postUpdate();
230         }
231     }
232 
updateScrollFlag()233     private void updateScrollFlag() {
234         if (mCollapsingBarLayout == null) {
235             return;
236         }
237 
238         AppBarLayout.LayoutParams lp =
239                 (AppBarLayout.LayoutParams) mCollapsingBarLayout.getLayoutParams();
240         lp.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
241                 | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
242         mCollapsingBarLayout.setLayoutParams(lp);
243     }
244 
updateToolbar()245     private void updateToolbar() {
246         if (mCollapsingBarLayout == null) {
247             // Tablet mode does not use CollapsingBarLayout
248             // (res/layout-sw720dp/directory_app_bar.xml or res/layout/fixed_layout.xml)
249             if (shouldShowSearchBar()) {
250                 mToolbar.setBackgroundResource(R.drawable.search_bar_background);
251                 mToolbar.setOutlineProvider(mSearchBarOutlineProvider);
252             } else {
253                 mToolbar.setBackground(mDefaultActionBarBackground);
254                 mToolbar.setOutlineProvider(null);
255             }
256             return;
257         }
258 
259         CollapsingToolbarLayout.LayoutParams toolbarLayoutParams =
260                 (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
261 
262         int headerTopOffset = 0;
263         if (shouldShowSearchBar() && !mIsActionModeActivated) {
264             mToolbar.setBackgroundResource(R.drawable.search_bar_background);
265             mToolbar.setOutlineProvider(mSearchBarOutlineProvider);
266             int searchBarMargin = mToolbar.getResources().getDimensionPixelSize(
267                     R.dimen.search_bar_margin);
268             toolbarLayoutParams.setMargins(searchBarMargin, searchBarMargin, searchBarMargin,
269                     searchBarMargin);
270             mToolbar.setLayoutParams(toolbarLayoutParams);
271             mToolbar.setElevation(
272                     mToolbar.getResources().getDimensionPixelSize(R.dimen.search_bar_elevation));
273             headerTopOffset = toolbarLayoutParams.height + searchBarMargin * 2;
274         } else {
275             mToolbar.setBackground(mDefaultActionBarBackground);
276             mToolbar.setOutlineProvider(mDefaultOutlineProvider);
277             int actionBarMargin = mToolbar.getResources().getDimensionPixelSize(
278                     R.dimen.action_bar_margin);
279             toolbarLayoutParams.setMargins(0, 0, 0, /* bottom= */ actionBarMargin);
280             mToolbar.setLayoutParams(toolbarLayoutParams);
281             mToolbar.setElevation(
282                     mToolbar.getResources().getDimensionPixelSize(R.dimen.action_bar_elevation));
283             headerTopOffset = toolbarLayoutParams.height + actionBarMargin;
284         }
285 
286         if (!mIsActionModeActivated) {
287             FrameLayout.LayoutParams headerLayoutParams =
288                     (FrameLayout.LayoutParams) mHeader.getLayoutParams();
289             headerLayoutParams.setMargins(0, /* top= */ headerTopOffset, 0, 0);
290             mHeader.setLayoutParams(headerLayoutParams);
291         }
292     }
293 
shouldShowSearchBar()294     private boolean shouldShowSearchBar() {
295         return mState.stack.isRecents() && !mEnv.isSearchExpanded() && mShowSearchBar;
296     }
297 
298     // Hamburger if drawer is present, else sad nullness.
299     private @Nullable
getActionBarIcon()300     Drawable getActionBarIcon() {
301         if (mDrawer.isPresent()) {
302             return mToolbar.getContext().getDrawable(R.drawable.ic_hamburger);
303         } else {
304             return null;
305         }
306     }
307 
revealRootsDrawer(boolean open)308     void revealRootsDrawer(boolean open) {
309         mDrawer.setOpen(open);
310     }
311 
312     interface Breadcrumb {
setup(Environment env, State state, IntConsumer listener)313         void setup(Environment env, State state, IntConsumer listener);
314 
show(boolean visibility)315         void show(boolean visibility);
316 
postUpdate()317         void postUpdate();
318     }
319 
320     interface Environment {
321         @Deprecated
322             // Use CommonAddones#getCurrentRoot
getCurrentRoot()323         RootInfo getCurrentRoot();
324 
getDrawerTitle()325         String getDrawerTitle();
326 
327         @Deprecated
328             // Use CommonAddones#refreshCurrentRootAndDirectory
refreshCurrentRootAndDirectory(int animation)329         void refreshCurrentRootAndDirectory(int animation);
330 
isSearchExpanded()331         boolean isSearchExpanded();
332     }
333 }
334