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