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