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