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.AnimatorInflater; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.graphics.Rect; 26 import android.os.Build; 27 import android.os.Handler; 28 import android.util.Log; 29 import android.view.KeyEvent; 30 import android.view.LayoutInflater; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.WindowInsets; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.systemui.R; 39 import com.android.systemui.car.CarDeviceProvisionedController; 40 import com.android.systemui.car.window.OverlayPanelViewController; 41 import com.android.systemui.car.window.OverlayViewGlobalStateController; 42 import com.android.systemui.dagger.SysUISingleton; 43 import com.android.systemui.dagger.qualifiers.Main; 44 import com.android.systemui.statusbar.policy.ConfigurationController; 45 import com.android.wm.shell.animation.FlingAnimationUtils; 46 47 import javax.inject.Inject; 48 49 @SysUISingleton 50 public class HvacPanelOverlayViewController extends OverlayPanelViewController implements 51 ConfigurationController.ConfigurationListener { 52 private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG; 53 private static final String TAG = HvacPanelOverlayViewController.class.getName(); 54 55 private final Context mContext; 56 private final Resources mResources; 57 private final Handler mHandler; 58 private final HvacController mHvacController; 59 private final float mFullyOpenDimAmount; 60 private final int mAutoDismissDurationMs; 61 62 private boolean mIsUiModeNight; 63 private float mCurrentDimAmount = 0f; 64 @Nullable 65 private Animator mOpenAnimator; 66 @Nullable 67 private Animator mCloseAnimator; 68 69 private HvacPanelView mHvacPanelView; 70 71 private final Runnable mAutoDismiss = () -> { 72 if (isPanelExpanded()) { 73 toggle(); 74 } 75 }; 76 77 @Inject HvacPanelOverlayViewController(Context context, @Main Resources resources, @Main Handler handler, HvacController hvacController, OverlayViewGlobalStateController overlayViewGlobalStateController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, CarDeviceProvisionedController carDeviceProvisionedController, ConfigurationController configurationController)78 public HvacPanelOverlayViewController(Context context, 79 @Main Resources resources, 80 @Main Handler handler, 81 HvacController hvacController, 82 OverlayViewGlobalStateController overlayViewGlobalStateController, 83 FlingAnimationUtils.Builder flingAnimationUtilsBuilder, 84 CarDeviceProvisionedController carDeviceProvisionedController, 85 ConfigurationController configurationController) { 86 super(context, resources, R.id.hvac_panel_stub, overlayViewGlobalStateController, 87 flingAnimationUtilsBuilder, carDeviceProvisionedController); 88 mContext = context; 89 mResources = resources; 90 mHandler = handler; 91 mHvacController = hvacController; 92 configurationController.addCallback(this); 93 mFullyOpenDimAmount = mResources.getFloat(R.fraction.hvac_overlay_window_dim_amount); 94 mAutoDismissDurationMs = mResources.getInteger(R.integer.config_hvacAutoDismissDurationMs); 95 } 96 97 @Override onFinishInflate()98 protected void onFinishInflate() { 99 super.onFinishInflate(); 100 101 View closeButton = getLayout().findViewById(R.id.hvac_panel_close_button); 102 if (closeButton != null) { 103 closeButton.setOnClickListener(v -> dismissHvacPanel()); 104 } 105 106 mHvacPanelView = getLayout().findViewById(R.id.hvac_panel); 107 mHvacController.registerHvacViews(mHvacPanelView); 108 109 mHvacPanelView.setKeyEventHandler((event) -> { 110 if (event.getKeyCode() != KeyEvent.KEYCODE_BACK) { 111 return false; 112 } 113 114 if (event.getAction() == KeyEvent.ACTION_UP && isPanelExpanded()) { 115 dismissHvacPanel(); 116 } 117 return true; 118 }); 119 120 mHvacPanelView.setMotionEventHandler((event -> { 121 setAutoDismissTimeout(); 122 })); 123 124 loadCustomAnimators(); 125 } 126 127 @Override getInsetTypesToFit()128 protected int getInsetTypesToFit() { 129 return WindowInsets.Type.systemBars(); 130 } 131 132 @Override shouldShowStatusBarInsets()133 protected boolean shouldShowStatusBarInsets() { 134 return true; 135 } 136 137 @Override shouldShowNavigationBarInsets()138 protected boolean shouldShowNavigationBarInsets() { 139 return true; 140 } 141 142 @Override shouldAnimateCollapsePanel()143 protected boolean shouldAnimateCollapsePanel() { 144 return true; 145 } 146 147 @Override shouldAnimateExpandPanel()148 protected boolean shouldAnimateExpandPanel() { 149 return true; 150 } 151 152 @Override shouldAllowClosingScroll()153 protected boolean shouldAllowClosingScroll() { 154 return true; 155 } 156 157 @Override getDefaultDimAmount()158 protected float getDefaultDimAmount() { 159 return mCurrentDimAmount; 160 } 161 162 @Override getHandleBarViewId()163 protected Integer getHandleBarViewId() { 164 return R.id.handle_bar; 165 } 166 167 @Override getFocusAreaViewId()168 protected int getFocusAreaViewId() { 169 return R.id.hvac_panel_container; 170 } 171 172 @Override getSettleClosePercentage()173 protected int getSettleClosePercentage() { 174 return mResources.getInteger(R.integer.hvac_panel_settle_close_percentage); 175 } 176 177 @Override onAnimateExpandPanel()178 protected void onAnimateExpandPanel() { 179 setAutoDismissTimeout(); 180 } 181 182 @Override onAnimateCollapsePanel()183 protected void onAnimateCollapsePanel() { 184 removeAutoDismissTimeout(); 185 } 186 187 @Override onCollapseAnimationEnd()188 protected void onCollapseAnimationEnd() { 189 // no-op. 190 } 191 192 @Override onExpandAnimationEnd()193 protected void onExpandAnimationEnd() { 194 // no-op. 195 } 196 197 @Override onOpenScrollStart()198 protected void onOpenScrollStart() { 199 // no-op. 200 } 201 202 @Override onTouchEvent(View view, MotionEvent event)203 protected void onTouchEvent(View view, MotionEvent event) { 204 if (mHvacPanelView == null) { 205 return; 206 } 207 Rect outBounds = new Rect(); 208 mHvacPanelView.getBoundsInWindow(outBounds, /* clipToParent= */ true); 209 if (isPanelExpanded() && (event.getAction() == MotionEvent.ACTION_UP) 210 && isTouchOutside(outBounds, event.getX(), event.getY())) { 211 dismissHvacPanel(); 212 } 213 } 214 215 @Override onScroll(int y)216 protected void onScroll(int y) { 217 super.onScroll(y); 218 219 float percentageOpen = 220 ((float) (mAnimateDirection > 0 ? y : getLayout().getHeight() - y)) 221 / getLayout().getHeight(); 222 mCurrentDimAmount = mFullyOpenDimAmount * percentageOpen; 223 getOverlayViewGlobalStateController().updateWindowDimBehind(this, mCurrentDimAmount); 224 } 225 isTouchOutside(Rect bounds, float x, float y)226 private boolean isTouchOutside(Rect bounds, float x, float y) { 227 return x < bounds.left || x > bounds.right || y < bounds.top || y > bounds.bottom; 228 } 229 dismissHvacPanel()230 private void dismissHvacPanel() { 231 removeAutoDismissTimeout(); 232 mHandler.post(mAutoDismiss); 233 } 234 setAutoDismissTimeout()235 private void setAutoDismissTimeout() { 236 if (mAutoDismissDurationMs > 0) { 237 mHandler.removeCallbacks(mAutoDismiss); 238 mHandler.postDelayed(mAutoDismiss, mAutoDismissDurationMs); 239 } 240 } 241 removeAutoDismissTimeout()242 private void removeAutoDismissTimeout() { 243 if (mAutoDismissDurationMs > 0) { 244 mHandler.removeCallbacks(mAutoDismiss); 245 } 246 } 247 loadCustomAnimators()248 private void loadCustomAnimators() { 249 try { 250 mOpenAnimator = AnimatorInflater.loadAnimator(mContext, R.anim.hvac_open_anim); 251 mOpenAnimator.setTarget(getLayout()); 252 } catch (Resources.NotFoundException e) { 253 if (DEBUG) { 254 Log.d(TAG, "Custom open animator not found - using default"); 255 } 256 } 257 258 try { 259 mCloseAnimator = AnimatorInflater.loadAnimator(mContext, R.anim.hvac_close_anim); 260 mCloseAnimator.setTarget(getLayout()); 261 } catch (Resources.NotFoundException e) { 262 if (DEBUG) { 263 Log.d(TAG, "Custom close animator not found - using default"); 264 } 265 } 266 } 267 268 @Override getCustomAnimator(float from, float to, float velocity, boolean isClosing)269 protected Animator getCustomAnimator(float from, float to, float velocity, boolean isClosing) { 270 Animator animator = isClosing ? mCloseAnimator : mOpenAnimator; 271 if (animator != null) { 272 animator.removeAllListeners(); 273 if (animator instanceof ValueAnimator) { 274 ((ValueAnimator) animator).setFloatValues(from, to); 275 } 276 } 277 278 return animator; 279 } 280 281 @Override onConfigChanged(Configuration newConfig)282 public void onConfigChanged(Configuration newConfig) { 283 boolean isConfigNightMode = newConfig.isNightModeActive(); 284 285 // Only refresh UI on Night mode changes 286 if (isConfigNightMode != mIsUiModeNight) { 287 mIsUiModeNight = isConfigNightMode; 288 289 if (getLayout() == null) return; 290 mHvacPanelView = getLayout().findViewById(R.id.hvac_panel); 291 if (mHvacPanelView == null) return; 292 ViewGroup hvacViewGroupParent = (ViewGroup) mHvacPanelView.getParent(); 293 294 // cache properties of {@link HvacPanelView} 295 int inflatedId = mHvacPanelView.getId(); 296 ViewGroup.LayoutParams layoutParams = mHvacPanelView.getLayoutParams(); 297 HvacPanelView.KeyEventHandler hvacKeyEventHandler = mHvacPanelView 298 .getKeyEventHandler(); 299 int indexOfView = hvacViewGroupParent.indexOfChild(mHvacPanelView); 300 301 // remove {@link HvacPanelView} from its parent and reinflate it 302 hvacViewGroupParent.removeView(mHvacPanelView); 303 HvacPanelView newHvacPanelView = (HvacPanelView) LayoutInflater.from(mContext).inflate( 304 R.layout.hvac_panel, /* root= */ hvacViewGroupParent, 305 /* attachToRoot= */ false); 306 hvacViewGroupParent.addView(newHvacPanelView, indexOfView); 307 mHvacPanelView = newHvacPanelView; 308 309 // reset {@link HvacPanelView} cached properties 310 mHvacPanelView.setId(inflatedId); 311 mHvacPanelView.setLayoutParams(layoutParams); 312 mHvacController.registerHvacViews(mHvacPanelView); 313 mHvacPanelView.setKeyEventHandler(hvacKeyEventHandler); 314 } 315 } 316 } 317