• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.car.hvac;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.app.UiModeManager;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.Rect;
26 import android.os.Handler;
27 import android.view.GestureDetector;
28 import android.view.MotionEvent;
29 import android.view.View;
30 
31 import com.android.systemui.R;
32 import com.android.systemui.car.CarDeviceProvisionedController;
33 import com.android.systemui.car.window.OverlayViewGlobalStateController;
34 import com.android.systemui.dagger.SysUISingleton;
35 import com.android.systemui.dagger.qualifiers.Main;
36 import com.android.systemui.statusbar.policy.ConfigurationController;
37 import com.android.wm.shell.animation.FlingAnimationUtils;
38 
39 import javax.inject.Inject;
40 
41 /**
42  * An extension of {@link HvacPanelOverlayViewController} which auto dismisses the panel if there
43  * is no activity for some configured amount of time.
44  */
45 @SysUISingleton
46 public class AutoDismissHvacPanelOverlayViewController extends HvacPanelOverlayViewController {
47 
48     private final Resources mResources;
49     private final Handler mHandler;
50     private final Context mContext;
51 
52     private HvacPanelView mHvacPanelView;
53     private int mAutoDismissDurationMs;
54     private float mPreviousHandleBarPositionY;
55     private boolean mIsDragging;
56     private ObjectAnimator mAnimation;
57 
58     private final Runnable mAutoDismiss = () -> {
59         if (isPanelExpanded()) {
60             toggle();
61         }
62     };
63 
64     @Inject
AutoDismissHvacPanelOverlayViewController(Context context, @Main Resources resources, HvacController hvacController, OverlayViewGlobalStateController overlayViewGlobalStateController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, CarDeviceProvisionedController carDeviceProvisionedController, @Main Handler handler, ConfigurationController configurationController, UiModeManager uiModeManager)65     public AutoDismissHvacPanelOverlayViewController(Context context,
66             @Main Resources resources,
67             HvacController hvacController,
68             OverlayViewGlobalStateController overlayViewGlobalStateController,
69             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
70             CarDeviceProvisionedController carDeviceProvisionedController,
71             @Main Handler handler,
72             ConfigurationController configurationController,
73             UiModeManager uiModeManager) {
74         super(context, resources, hvacController, overlayViewGlobalStateController,
75                 flingAnimationUtilsBuilder, carDeviceProvisionedController, configurationController,
76                 uiModeManager);
77         mResources = resources;
78         mHandler = handler;
79         mContext = context;
80     }
81 
82     @Override
onFinishInflate()83     protected void onFinishInflate() {
84         super.onFinishInflate();
85 
86         mAutoDismissDurationMs = mResources.getInteger(R.integer.config_hvacAutoDismissDurationMs);
87 
88         mHvacPanelView = getLayout().findViewById(R.id.hvac_panel);
89         mHvacPanelView.setMotionEventHandler(event -> {
90             if (!isPanelExpanded()) {
91                 return;
92             }
93 
94             mHandler.removeCallbacks(mAutoDismiss);
95             mHandler.postDelayed(mAutoDismiss, mAutoDismissDurationMs);
96         });
97     }
98 
99     @Override
onAnimateExpandPanel()100     protected void onAnimateExpandPanel() {
101         super.onAnimateExpandPanel();
102 
103         mHandler.postDelayed(mAutoDismiss, mAutoDismissDurationMs);
104     }
105 
106     @Override
onAnimateCollapsePanel()107     protected void onAnimateCollapsePanel() {
108         super.onAnimateCollapsePanel();
109 
110         mHandler.removeCallbacks(mAutoDismiss);
111     }
112 
113     @Override
animate(float from, float to, float velocity, boolean isClosing)114     protected void animate(float from, float to, float velocity, boolean isClosing) {
115         if (isAnimating()) {
116             return;
117         }
118         mIsAnimating = true;
119         setIsTracking(true);
120 
121         ObjectAnimator animation = ObjectAnimator
122                 .ofFloat(getLayout(), "translationY", from, to);
123 
124         animation.addListener(new AnimatorListenerAdapter() {
125             @Override
126             public void onAnimationEnd(Animator animation) {
127                 super.onAnimationEnd(animation);
128                 mIsAnimating = false;
129                 setIsTracking(false);
130                 mOpeningVelocity = DEFAULT_FLING_VELOCITY;
131                 mClosingVelocity = DEFAULT_FLING_VELOCITY;
132                 if (isClosing) {
133                     resetPanelVisibility();
134                 } else {
135                     onExpandAnimationEnd();
136                     setPanelExpanded(true);
137                     setViewClipBounds((int) to);
138                 }
139             }
140         });
141         getFlingAnimationUtils().apply(animation, from, to, Math.abs(velocity));
142         animation.start();
143     }
144 
145     @Override
getCurrentStartPosition(Rect clipBounds)146     protected int getCurrentStartPosition(Rect clipBounds) {
147         if (mIsDragging) {
148             return (int) mPreviousHandleBarPositionY;
149         }
150 
151         return mAnimateDirection > 0 ? clipBounds.bottom : clipBounds.top;
152     }
153 
154     @Override
setUpHandleBar()155     protected void setUpHandleBar() {
156         Integer handleBarViewId = getHandleBarViewId();
157         if (handleBarViewId == null) return;
158         View handleBar = getLayout().findViewById(handleBarViewId);
159         if (handleBar == null) return;
160         GestureDetector handleBarCloseGestureDetector =
161                 new GestureDetector(mContext, new HandleBarCloseGestureListener());
162         handleBar.setOnTouchListener((v, event) -> {
163             int action = event.getAction();
164             switch (action & MotionEvent.ACTION_MASK) {
165                 case MotionEvent.ACTION_UP:
166                     maybeCompleteAnimation(event);
167                     mPreviousHandleBarPositionY = 0;
168                     mIsDragging = false;
169                     // Intentionally not breaking here, since handleBarClosureGestureDetector's
170                     // onTouchEvent should still be called with MotionEvent.ACTION_UP.
171                 default:
172                     handleBarCloseGestureDetector.onTouchEvent(event);
173                     return true;
174             }
175         });
176     }
177 
178     /**
179      * A GestureListener to be installed on the handle bar.
180      */
181     private class HandleBarCloseGestureListener extends GestureDetector.SimpleOnGestureListener {
182 
183         @Override
onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY)184         public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
185                 float distanceY) {
186 
187             mIsDragging = true;
188             calculatePercentageFromEndingEdge(event2.getRawY());
189             calculatePercentageCursorPositionOnScreen(event2.getRawY());
190             // To prevent the jump in the clip bounds while closing the panel using
191             // the handle bar, we should calculate the height using the diff of event1 and event2.
192             // This will help the notification shade to clip smoothly as the event2 value changes
193             // as event1 value will be fixed.
194             float diff = mAnimateDirection * (event1.getRawY() - event2.getRawY());
195             float y = mAnimateDirection > 0
196                     ? getLayout().getHeight() - diff
197                     : diff;
198             // Ensure the position is within the overlay panel.
199             y = Math.max(0, Math.min(y, getLayout().getHeight()));
200             ObjectAnimator animation = ObjectAnimator
201                     .ofFloat(getLayout(), "translationY", mPreviousHandleBarPositionY, y);
202             mPreviousHandleBarPositionY = y;
203             if (mAnimation != null && mAnimation.isRunning()) {
204                 mAnimation.cancel();
205             }
206             animation.start();
207             mAnimation = animation;
208             return true;
209         }
210     }
211 }
212