1 package com.android.launcher3; 2 3 import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE; 4 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; 5 6 import android.annotation.TargetApi; 7 import android.content.Context; 8 import android.content.res.Resources; 9 import android.graphics.Canvas; 10 import android.graphics.Insets; 11 import android.graphics.Rect; 12 import android.os.Build; 13 import android.util.AttributeSet; 14 import android.view.ViewDebug; 15 import android.view.WindowInsets; 16 17 import androidx.annotation.RequiresApi; 18 19 import com.android.launcher3.graphics.SysUiScrim; 20 import com.android.launcher3.statemanager.StatefulActivity; 21 import com.android.launcher3.uioverrides.ApiWrapper; 22 23 import java.util.Collections; 24 import java.util.List; 25 26 public class LauncherRootView extends InsettableFrameLayout { 27 28 private final Rect mTempRect = new Rect(); 29 30 private final StatefulActivity mActivity; 31 32 @ViewDebug.ExportedProperty(category = "launcher") 33 private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT = 34 Collections.singletonList(new Rect()); 35 36 private WindowStateListener mWindowStateListener; 37 @ViewDebug.ExportedProperty(category = "launcher") 38 private boolean mDisallowBackGesture; 39 @ViewDebug.ExportedProperty(category = "launcher") 40 private boolean mForceHideBackArrow; 41 42 private final SysUiScrim mSysUiScrim; 43 LauncherRootView(Context context, AttributeSet attrs)44 public LauncherRootView(Context context, AttributeSet attrs) { 45 super(context, attrs); 46 mActivity = StatefulActivity.fromContext(context); 47 mSysUiScrim = new SysUiScrim(this); 48 } 49 handleSystemWindowInsets(Rect insets)50 private void handleSystemWindowInsets(Rect insets) { 51 // Update device profile before notifying the children. 52 mActivity.getDeviceProfile().updateInsets(insets); 53 boolean resetState = !insets.equals(mInsets); 54 setInsets(insets); 55 56 if (resetState) { 57 mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */); 58 } 59 } 60 61 @Override onApplyWindowInsets(WindowInsets insets)62 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 63 if (Utilities.ATLEAST_R) { 64 insets = updateInsetsDueToTaskbar(insets); 65 Insets systemWindowInsets = insets.getInsetsIgnoringVisibility( 66 WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); 67 mTempRect.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right, 68 systemWindowInsets.bottom); 69 } else { 70 mTempRect.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), 71 insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); 72 } 73 handleSystemWindowInsets(mTempRect); 74 return insets; 75 } 76 77 /** 78 * Taskbar provides nav bar and tappable insets. However, taskbar is not attached immediately, 79 * and can be destroyed and recreated. Thus, instead of relying on taskbar being present to 80 * get its insets, we calculate them ourselves so they are stable regardless of whether taskbar 81 * is currently attached. 82 * 83 * @param oldInsets The system-provided insets, which we are modifying. 84 * @return The updated insets. 85 */ 86 @RequiresApi(api = Build.VERSION_CODES.R) updateInsetsDueToTaskbar(WindowInsets oldInsets)87 private WindowInsets updateInsetsDueToTaskbar(WindowInsets oldInsets) { 88 if (!ApiWrapper.TASKBAR_DRAWN_IN_PROCESS) { 89 // 3P launchers based on Launcher3 should still be inset like normal. 90 return oldInsets; 91 } 92 93 WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets); 94 95 DeviceProfile dp = mActivity.getDeviceProfile(); 96 Resources resources = getResources(); 97 98 Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars()); 99 Rect newNavInsets = new Rect(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right, 100 oldNavInsets.bottom); 101 102 if (dp.isLandscape) { 103 boolean isGesturalMode = ResourceUtils.getIntegerByName( 104 "config_navBarInteractionMode", 105 resources, 106 INVALID_RESOURCE_HANDLE) == 2; 107 if (dp.isTablet || isGesturalMode) { 108 newNavInsets.bottom = ResourceUtils.getNavbarSize( 109 "navigation_bar_height_landscape", resources); 110 } else { 111 int navWidth = ResourceUtils.getNavbarSize("navigation_bar_width", resources); 112 if (dp.isSeascape()) { 113 newNavInsets.left = navWidth; 114 } else { 115 newNavInsets.right = navWidth; 116 } 117 } 118 } else { 119 newNavInsets.bottom = ResourceUtils.getNavbarSize("navigation_bar_height", resources); 120 } 121 updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(newNavInsets)); 122 updatedInsetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), 123 Insets.of(newNavInsets)); 124 125 mActivity.updateWindowInsets(updatedInsetsBuilder, oldInsets); 126 127 return updatedInsetsBuilder.build(); 128 } 129 130 @Override setInsets(Rect insets)131 public void setInsets(Rect insets) { 132 // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by 133 // modifying child layout params. 134 if (!insets.equals(mInsets)) { 135 super.setInsets(insets); 136 mSysUiScrim.onInsetsChanged(insets); 137 } 138 } 139 dispatchInsets()140 public void dispatchInsets() { 141 mActivity.getDeviceProfile().updateInsets(mInsets); 142 super.setInsets(mInsets); 143 } 144 setWindowStateListener(WindowStateListener listener)145 public void setWindowStateListener(WindowStateListener listener) { 146 mWindowStateListener = listener; 147 } 148 149 @Override onWindowFocusChanged(boolean hasWindowFocus)150 public void onWindowFocusChanged(boolean hasWindowFocus) { 151 super.onWindowFocusChanged(hasWindowFocus); 152 if (mWindowStateListener != null) { 153 mWindowStateListener.onWindowFocusChanged(hasWindowFocus); 154 } 155 } 156 157 @Override onWindowVisibilityChanged(int visibility)158 protected void onWindowVisibilityChanged(int visibility) { 159 super.onWindowVisibilityChanged(visibility); 160 if (mWindowStateListener != null) { 161 mWindowStateListener.onWindowVisibilityChanged(visibility); 162 } 163 } 164 165 @Override dispatchDraw(Canvas canvas)166 protected void dispatchDraw(Canvas canvas) { 167 mSysUiScrim.draw(canvas); 168 super.dispatchDraw(canvas); 169 } 170 171 @Override onLayout(boolean changed, int l, int t, int r, int b)172 protected void onLayout(boolean changed, int l, int t, int r, int b) { 173 super.onLayout(changed, l, t, r, b); 174 SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b); 175 setDisallowBackGesture(mDisallowBackGesture); 176 mSysUiScrim.setSize(r - l, b - t); 177 } 178 179 @TargetApi(Build.VERSION_CODES.Q) setForceHideBackArrow(boolean forceHideBackArrow)180 public void setForceHideBackArrow(boolean forceHideBackArrow) { 181 this.mForceHideBackArrow = forceHideBackArrow; 182 setDisallowBackGesture(mDisallowBackGesture); 183 } 184 185 @TargetApi(Build.VERSION_CODES.Q) setDisallowBackGesture(boolean disallowBackGesture)186 public void setDisallowBackGesture(boolean disallowBackGesture) { 187 if (!Utilities.ATLEAST_Q || SEPARATE_RECENTS_ACTIVITY.get()) { 188 return; 189 } 190 mDisallowBackGesture = disallowBackGesture; 191 setSystemGestureExclusionRects((mForceHideBackArrow || mDisallowBackGesture) 192 ? SYSTEM_GESTURE_EXCLUSION_RECT 193 : Collections.emptyList()); 194 } 195 getSysUiScrim()196 public SysUiScrim getSysUiScrim() { 197 return mSysUiScrim; 198 } 199 200 public interface WindowStateListener { 201 onWindowFocusChanged(boolean hasFocus)202 void onWindowFocusChanged(boolean hasFocus); 203 onWindowVisibilityChanged(int visibility)204 void onWindowVisibilityChanged(int visibility); 205 } 206 } 207