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