1 /*
2  * Copyright (C) 2018 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.systemui.qs.car;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorInflater;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.ValueAnimator;
25 import android.app.Fragment;
26 import android.content.Context;
27 import android.os.Bundle;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.ViewGroup;
32 
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.recyclerview.widget.GridLayoutManager;
36 
37 import com.android.systemui.R;
38 import com.android.systemui.plugins.qs.QS;
39 import com.android.systemui.qs.QSFooter;
40 import com.android.systemui.statusbar.car.UserGridRecyclerView;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * A quick settings fragment for the car. For auto, there is no row for quick settings or ability
47  * to expand the quick settings panel. Instead, the only thing is that displayed is the
48  * status bar, and a static row with access to the user switcher and settings.
49  */
50 public class CarQSFragment extends Fragment implements QS {
51     private View mHeader;
52     private View mUserSwitcherContainer;
53     private CarQSFooter mFooter;
54     private View mFooterUserName;
55     private View mFooterExpandIcon;
56     private UserGridRecyclerView mUserGridView;
57     private AnimatorSet mAnimatorSet;
58     private UserSwitchCallback mUserSwitchCallback;
59 
60     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)61     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
62             Bundle savedInstanceState) {
63         return inflater.inflate(R.layout.car_qs_panel, container, false);
64     }
65 
66     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)67     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
68         super.onViewCreated(view, savedInstanceState);
69         mHeader = view.findViewById(R.id.header);
70         mFooter = view.findViewById(R.id.qs_footer);
71         mFooterUserName = mFooter.findViewById(R.id.user_name);
72         mFooterExpandIcon = mFooter.findViewById(R.id.user_switch_expand_icon);
73 
74         mUserSwitcherContainer = view.findViewById(R.id.user_switcher_container);
75 
76         updateUserSwitcherHeight(0);
77 
78         Context context = getContext();
79         mUserGridView = mUserSwitcherContainer.findViewById(R.id.user_grid);
80         GridLayoutManager layoutManager = new GridLayoutManager(context,
81                 context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
82         mUserGridView.setLayoutManager(layoutManager);
83         mUserGridView.buildAdapter();
84 
85         mUserSwitchCallback = new UserSwitchCallback();
86         mFooter.setUserSwitchCallback(mUserSwitchCallback);
87     }
88 
89     @Override
hideImmediately()90     public void hideImmediately() {
91         getView().setVisibility(View.INVISIBLE);
92     }
93 
94     @Override
setQsExpansion(float qsExpansionFraction, float headerTranslation)95     public void setQsExpansion(float qsExpansionFraction, float headerTranslation) {
96         // If the header is to be completed translated down, then set it to be visible.
97         getView().setVisibility(headerTranslation == 0 ? View.VISIBLE : View.INVISIBLE);
98     }
99 
100     @Override
getHeader()101     public View getHeader() {
102         return mHeader;
103     }
104 
105     @VisibleForTesting
getFooter()106     QSFooter getFooter() {
107         return mFooter;
108     }
109 
110     @Override
setHeaderListening(boolean listening)111     public void setHeaderListening(boolean listening) {
112         mFooter.setListening(listening);
113     }
114 
115     @Override
setListening(boolean listening)116     public void setListening(boolean listening) {
117         mFooter.setListening(listening);
118     }
119 
120     @Override
getQsMinExpansionHeight()121     public int getQsMinExpansionHeight() {
122         return getView().getHeight();
123     }
124 
125     @Override
getDesiredHeight()126     public int getDesiredHeight() {
127         return getView().getHeight();
128     }
129 
130     @Override
setPanelView(HeightListener notificationPanelView)131     public void setPanelView(HeightListener notificationPanelView) {
132         // No quick settings panel.
133     }
134 
135     @Override
setHeightOverride(int desiredHeight)136     public void setHeightOverride(int desiredHeight) {
137         // No ability to expand quick settings.
138     }
139 
140     @Override
setHeaderClickable(boolean qsExpansionEnabled)141     public void setHeaderClickable(boolean qsExpansionEnabled) {
142         // Usually this sets the expand button to be clickable, but there is no quick settings to
143         // expand.
144     }
145 
146     @Override
isCustomizing()147     public boolean isCustomizing() {
148         // No ability to customize the quick settings.
149         return false;
150     }
151 
152     @Override
setOverscrolling(boolean overscrolling)153     public void setOverscrolling(boolean overscrolling) {
154         // No overscrolling to reveal quick settings.
155     }
156 
157     @Override
setExpanded(boolean qsExpanded)158     public void setExpanded(boolean qsExpanded) {
159         // No quick settings to expand
160     }
161 
162     @Override
isShowingDetail()163     public boolean isShowingDetail() {
164         // No detail panel to close.
165         return false;
166     }
167 
168     @Override
closeDetail()169     public void closeDetail() {
170         // No detail panel to close.
171     }
172 
173     @Override
animateHeaderSlidingIn(long delay)174     public void animateHeaderSlidingIn(long delay) {
175         // No header to animate.
176     }
177 
178     @Override
animateHeaderSlidingOut()179     public void animateHeaderSlidingOut() {
180         // No header to animate.
181     }
182 
183     @Override
notifyCustomizeChanged()184     public void notifyCustomizeChanged() {
185         // There is no ability to customize quick settings.
186     }
187 
188     @Override
setContainer(ViewGroup container)189     public void setContainer(ViewGroup container) {
190         // No quick settings, so no container to set.
191     }
192 
193     @Override
setExpandClickListener(OnClickListener onClickListener)194     public void setExpandClickListener(OnClickListener onClickListener) {
195         // No ability to expand the quick settings.
196     }
197 
198     public class UserSwitchCallback {
199         private boolean mShowing;
200 
isShowing()201         public boolean isShowing() {
202             return mShowing;
203         }
204 
show()205         public void show() {
206             mShowing = true;
207             animateHeightChange(true /* opening */);
208         }
209 
hide()210         public void hide() {
211             mShowing = false;
212             animateHeightChange(false /* opening */);
213         }
214     }
215 
updateUserSwitcherHeight(int height)216     private void updateUserSwitcherHeight(int height) {
217         ViewGroup.LayoutParams layoutParams = mUserSwitcherContainer.getLayoutParams();
218         layoutParams.height = height;
219         mUserSwitcherContainer.requestLayout();
220     }
221 
animateHeightChange(boolean opening)222     private void animateHeightChange(boolean opening) {
223         // Animation in progress; cancel it to avoid contention.
224         if (mAnimatorSet != null) {
225             mAnimatorSet.cancel();
226         }
227 
228         List<Animator> allAnimators = new ArrayList<>();
229         ValueAnimator heightAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(),
230                 opening ? R.anim.car_user_switcher_open_animation
231                         : R.anim.car_user_switcher_close_animation);
232         heightAnimator.addUpdateListener(valueAnimator -> {
233             updateUserSwitcherHeight((Integer) valueAnimator.getAnimatedValue());
234         });
235         allAnimators.add(heightAnimator);
236 
237         Animator nameAnimator = AnimatorInflater.loadAnimator(getContext(),
238                 opening ? R.anim.car_user_switcher_open_name_animation
239                         : R.anim.car_user_switcher_close_name_animation);
240         nameAnimator.setTarget(mFooterUserName);
241         allAnimators.add(nameAnimator);
242 
243         Animator iconAnimator = AnimatorInflater.loadAnimator(getContext(),
244                 opening ? R.anim.car_user_switcher_open_icon_animation
245                         : R.anim.car_user_switcher_close_icon_animation);
246         iconAnimator.setTarget(mFooterExpandIcon);
247         allAnimators.add(iconAnimator);
248 
249         mAnimatorSet = new AnimatorSet();
250         mAnimatorSet.addListener(new AnimatorListenerAdapter() {
251             @Override
252             public void onAnimationEnd(Animator animation) {
253                 mAnimatorSet = null;
254             }
255         });
256         mAnimatorSet.playTogether(allAnimators.toArray(new Animator[0]));
257 
258         // Setup all values to the start values in the animations, since there are delays, but need
259         // to have all values start at the beginning.
260         setupInitialValues(mAnimatorSet);
261 
262         mAnimatorSet.start();
263     }
264 
setupInitialValues(Animator anim)265     private void setupInitialValues(Animator anim) {
266         if (anim instanceof AnimatorSet) {
267             for (Animator a : ((AnimatorSet) anim).getChildAnimations()) {
268                 setupInitialValues(a);
269             }
270         } else if (anim instanceof ObjectAnimator) {
271             ((ObjectAnimator) anim).setCurrentFraction(0.0f);
272         }
273     }
274 }
275