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