1 /* 2 * Copyright (C) 2020 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.window; 18 19 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 20 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 21 22 import android.content.Context; 23 import android.graphics.PixelFormat; 24 import android.os.Binder; 25 import android.view.DisplayCutout; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.WindowInsets; 32 import android.view.WindowManager; 33 import android.widget.LinearLayout; 34 35 import com.android.systemui.R; 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.statusbar.policy.ConfigurationController; 38 39 import javax.inject.Inject; 40 41 /** 42 * Controls the expansion state of the primary window which will contain all of the fullscreen sysui 43 * behavior. This window still has a collapsed state in order to watch for swipe events to expand 44 * this window for the notification panel. 45 */ 46 @SysUISingleton 47 public class SystemUIOverlayWindowController implements 48 ConfigurationController.ConfigurationListener { 49 50 /** 51 * Touch listener to get touches on the view. 52 */ 53 public interface OnTouchListener { 54 55 /** 56 * Called when a touch happens on the view. 57 */ onTouch(View v, MotionEvent event)58 void onTouch(View v, MotionEvent event); 59 } 60 61 private final Context mContext; 62 private final WindowManager mWindowManager; 63 64 private ViewGroup mBaseLayout; 65 private WindowManager.LayoutParams mLp; 66 private WindowManager.LayoutParams mLpChanged; 67 private boolean mIsAttached = false; 68 private boolean mVisible = false; 69 private boolean mFocusable = false; 70 private boolean mUsingStableInsets = false; 71 72 @Inject SystemUIOverlayWindowController( Context context, WindowManager windowManager, ConfigurationController configurationController)73 public SystemUIOverlayWindowController( 74 Context context, 75 WindowManager windowManager, 76 ConfigurationController configurationController) { 77 mContext = context; 78 mWindowManager = windowManager; 79 80 mLpChanged = new WindowManager.LayoutParams(); 81 mBaseLayout = (ViewGroup) LayoutInflater.from(context) 82 .inflate(R.layout.sysui_overlay_window, /* root= */ null, false); 83 configurationController.addCallback(this); 84 } 85 86 /** 87 * Register to {@link OnTouchListener} 88 */ registerOutsideTouchListener(OnTouchListener listener)89 public void registerOutsideTouchListener(OnTouchListener listener) { 90 mBaseLayout.setOnTouchListener(new View.OnTouchListener() { 91 @Override 92 public boolean onTouch(View v, MotionEvent event) { 93 listener.onTouch(v, event); 94 return false; 95 } 96 }); 97 } 98 99 /** Returns the base view of the primary window. */ getBaseLayout()100 public ViewGroup getBaseLayout() { 101 return mBaseLayout; 102 } 103 104 /** Returns {@code true} if the window is already attached. */ isAttached()105 public boolean isAttached() { 106 return mIsAttached; 107 } 108 109 /** Attaches the window to the window manager. */ attach()110 public void attach() { 111 if (mIsAttached) { 112 return; 113 } 114 mIsAttached = true; 115 // Now that the status bar window encompasses the sliding panel and its 116 // translucent backdrop, the entire thing is made TRANSLUCENT and is 117 // hardware-accelerated. 118 mLp = new WindowManager.LayoutParams( 119 ViewGroup.LayoutParams.MATCH_PARENT, 120 ViewGroup.LayoutParams.MATCH_PARENT, 121 WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE, 122 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 123 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 124 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 125 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 126 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 127 | WindowManager.LayoutParams.FLAG_DIM_BEHIND, 128 PixelFormat.TRANSLUCENT); 129 mLp.token = new Binder(); 130 mLp.gravity = Gravity.TOP; 131 mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 132 mLp.dimAmount = 0f; 133 mLp.setTitle("SystemUIOverlayWindow"); 134 mLp.packageName = mContext.getPackageName(); 135 mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 136 mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 137 138 mWindowManager.addView(mBaseLayout, mLp); 139 mLpChanged.copyFrom(mLp); 140 setWindowVisible(false); 141 } 142 143 /** Sets the types of insets to fit. Note: This should be rarely used. */ setFitInsetsTypes(@indowInsets.Type.InsetsType int types)144 public void setFitInsetsTypes(@WindowInsets.Type.InsetsType int types) { 145 mLpChanged.setFitInsetsTypes(types); 146 mLpChanged.setFitInsetsIgnoringVisibility(mUsingStableInsets); 147 updateWindow(); 148 } 149 150 /** Sets the sides of system bar insets to fit. Note: This should be rarely used. */ setFitInsetsSides(@indowInsets.Side.InsetsSide int sides)151 public void setFitInsetsSides(@WindowInsets.Side.InsetsSide int sides) { 152 mLpChanged.setFitInsetsSides(sides); 153 mLpChanged.setFitInsetsIgnoringVisibility(mUsingStableInsets); 154 updateWindow(); 155 } 156 157 /** Sets the window to the visible state. */ setWindowVisible(boolean visible)158 public void setWindowVisible(boolean visible) { 159 mVisible = visible; 160 if (visible) { 161 mBaseLayout.setVisibility(View.VISIBLE); 162 } else { 163 mBaseLayout.setVisibility(View.INVISIBLE); 164 } 165 updateWindow(); 166 } 167 168 /** Sets the window to be focusable. */ setWindowFocusable(boolean focusable)169 public void setWindowFocusable(boolean focusable) { 170 mFocusable = focusable; 171 if (focusable) { 172 mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 173 } else { 174 mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 175 } 176 updateWindow(); 177 } 178 179 /** Sets the dim behind the window */ setDimBehind(float dimAmount)180 public void setDimBehind(float dimAmount) { 181 mLpChanged.dimAmount = dimAmount; 182 updateWindow(); 183 } 184 185 /** Sets the window to enable IME. */ setWindowNeedsInput(boolean needsInput)186 public void setWindowNeedsInput(boolean needsInput) { 187 if (needsInput) { 188 mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 189 } else { 190 mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 191 } 192 updateWindow(); 193 } 194 195 /** Returns {@code true} if the window is visible */ isWindowVisible()196 public boolean isWindowVisible() { 197 return mVisible; 198 } 199 isWindowFocusable()200 public boolean isWindowFocusable() { 201 return mFocusable; 202 } 203 setUsingStableInsets(boolean useStableInsets)204 protected void setUsingStableInsets(boolean useStableInsets) { 205 mUsingStableInsets = useStableInsets; 206 } 207 updateWindow()208 private void updateWindow() { 209 if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { 210 if (isAttached()) { 211 handleDisplayCutout(); 212 mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 213 mWindowManager.updateViewLayout(mBaseLayout, mLp); 214 } 215 } 216 } 217 218 /** 219 * Certain device configurations may contain a curvature at the top-left or top-right of the 220 * screen. In those cases, overlay window should still render under the cutout in order to cover 221 * the entire screen, but the content of HVAC and Notification Center should be centered around 222 * the app area. 223 * 224 * TODO (b/326495987): Find an alternative approach to update all component together rather than 225 * only measuring and updating HVAC and notifications. 226 */ handleDisplayCutout()227 private void handleDisplayCutout() { 228 DisplayCutout cutout = 229 mWindowManager.getCurrentWindowMetrics().getWindowInsets().getDisplayCutout(); 230 if (cutout != null) { 231 int leftMargin = cutout.getBoundingRectLeft().width(); 232 int rightMargin = cutout.getBoundingRectRight().width(); 233 int appWindowWidth = mBaseLayout.getWidth() - (leftMargin + rightMargin); 234 235 View notificationsPanelView = mBaseLayout.findViewById(R.id.notifications); 236 if (notificationsPanelView != null) { 237 ViewGroup.MarginLayoutParams newLayoutParams = 238 new ViewGroup.MarginLayoutParams(notificationsPanelView.getLayoutParams()); 239 newLayoutParams.width = appWindowWidth; 240 newLayoutParams.leftMargin = leftMargin; 241 newLayoutParams.rightMargin = rightMargin; 242 notificationsPanelView.setLayoutParams(newLayoutParams); 243 } 244 245 View hvacPanelView = mBaseLayout.findViewById(R.id.hvac_panel); 246 if (hvacPanelView != null) { 247 LinearLayout.LayoutParams newLayoutParams = 248 (LinearLayout.LayoutParams) hvacPanelView.getLayoutParams(); 249 newLayoutParams.width = appWindowWidth; 250 newLayoutParams.leftMargin = leftMargin; 251 newLayoutParams.rightMargin = rightMargin; 252 hvacPanelView.setLayoutParams(newLayoutParams); 253 } 254 } 255 } 256 } 257