1 /*
2  * Copyright (C) 2023 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.wm;
18 
19 import android.annotation.Nullable;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.os.Build;
23 import android.os.Handler;
24 import android.os.RemoteException;
25 import android.util.Log;
26 import android.view.Display;
27 import android.view.IDisplayWindowInsetsController;
28 import android.view.IWindowManager;
29 import android.view.InsetsSource;
30 import android.view.InsetsSourceControl;
31 import android.view.InsetsState;
32 import android.view.WindowInsets;
33 import android.view.inputmethod.ImeTracker;
34 
35 import androidx.annotation.BinderThread;
36 import androidx.annotation.MainThread;
37 
38 import com.android.internal.statusbar.IStatusBarService;
39 import com.android.systemui.car.systembar.CarSystemBar;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.statusbar.CommandQueue;
42 
43 import java.util.HashSet;
44 import java.util.Set;
45 
46 /**
47  * b/259604616, This controller is created as a workaround for NavBar issues in concurrent
48  * {@link CarSystemBar}/SystemUI.
49  * Problem: CarSystemBar relies on {@link IStatusBarService},
50  * which can register only one process to listen for the {@link CommandQueue} events.
51  * Solution: {@link MDSystemBarsController} intercepts Insets change event by registering the
52  * {@link BinderThread} with
53  * {@link IWindowManager#setDisplayWindowInsetsController(int, IDisplayWindowInsetsController)} and
54  * notifies its listener for both Primary and Secondary SystemUI
55  * process.
56  */
57 public class MDSystemBarsController {
58 
59     private static final String TAG = MDSystemBarsController.class.getSimpleName();
60     private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG;
61     private Set<Listener> mListeners;
62     private int mDisplayId = Display.INVALID_DISPLAY;
63     private InsetsState mCurrentInsetsState;
64     private final IWindowManager mIWindowManager;
65     private final Handler mMainHandler;
66     private final Context mContext;
67 
MDSystemBarsController( IWindowManager wmService, @Main Handler mainHandler, Context context)68     public MDSystemBarsController(
69             IWindowManager wmService,
70             @Main Handler mainHandler,
71             Context context) {
72         mIWindowManager = wmService;
73         mMainHandler = mainHandler;
74         mContext = context;
75     }
76 
77     /**
78      * Adds a listener for the display.
79      * Adding a listener to a Display, replaces previous binder callback to this
80      * displayId
81      * {@link IWindowManager#setDisplayWindowInsetsController(int, IDisplayWindowInsetsController)}
82      * A SystemUI process should only register to a single display with displayId
83      * {@link Context#getDisplayId()}
84      *
85      * Note: {@link  Context#getDisplayId()} will return the {@link Context#DEVICE_ID_DEFAULT}, if
86      * called in the constructor. As this component's constructor is called before the DisplayId
87      * gets assigned to the context.
88      *
89      * @param listener SystemBar Inset events
90      */
91     @MainThread
addListener(Listener listener)92     public void addListener(Listener listener) {
93         if (mDisplayId != Display.INVALID_DISPLAY && mDisplayId != mContext.getDisplayId()) {
94             Log.e(TAG, "Unexpected Display Id change");
95             mListeners = null;
96             mCurrentInsetsState = null;
97             unregisterWindowInsetController(mDisplayId);
98         }
99         if (mListeners != null) {
100             mListeners.add(listener);
101             return;
102         }
103         mDisplayId = mContext.getDisplayId();
104         mListeners = new HashSet<>();
105         mListeners.add(listener);
106         registerWindowInsetController(mDisplayId);
107     }
108 
registerWindowInsetController(int displayId)109     private void registerWindowInsetController(int displayId) {
110         if (DEBUG) {
111             Log.d(TAG, "Registering a WindowInsetController with Display: " + displayId);
112         }
113         try {
114             mIWindowManager.setDisplayWindowInsetsController(displayId,
115                     new DisplayWindowInsetsControllerImpl());
116         } catch (RemoteException e) {
117             Log.w(TAG, "Unable to set insets controller on display " + displayId);
118         }
119     }
120 
unregisterWindowInsetController(int displayId)121     private void unregisterWindowInsetController(int displayId) {
122         if (DEBUG) {
123             Log.d(TAG, "Unregistering a WindowInsetController with Display: " + displayId);
124         }
125         try {
126             mIWindowManager.setDisplayWindowInsetsController(displayId, null);
127         } catch (RemoteException e) {
128             Log.w(TAG, "Unable to remove insets controller on display " + displayId);
129         }
130     }
131 
132     @BinderThread
133     private class DisplayWindowInsetsControllerImpl
134             extends IDisplayWindowInsetsController.Stub {
135         @Override
topFocusedWindowChanged(ComponentName component, @WindowInsets.Type.InsetsType int requestedVisibleTypes)136         public void topFocusedWindowChanged(ComponentName component,
137                 @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
138             //no-op
139         }
140 
141         @Override
insetsChanged(InsetsState insetsState)142         public void insetsChanged(InsetsState insetsState) {
143             if (insetsState == null || insetsState.equals(mCurrentInsetsState)) {
144                 return;
145             }
146             mCurrentInsetsState = insetsState;
147             if (mListeners == null) {
148                 return;
149             }
150             boolean show = insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME,
151                     WindowInsets.Type.ime());
152             mMainHandler.post(() -> {
153                 for (Listener l : mListeners) {
154                     l.onKeyboardVisibilityChanged(show);
155                 }
156             });
157         }
158 
159         @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)160         public void insetsControlChanged(InsetsState insetsState,
161                 InsetsSourceControl[] activeControls) {
162             //no-op
163         }
164 
165         @Override
showInsets(@indowInsets.Type.InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)166         public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
167                 @Nullable ImeTracker.Token statsToken) {
168             //no-op
169         }
170 
171         @Override
hideInsets(@indowInsets.Type.InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)172         public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
173                 @Nullable ImeTracker.Token statsToken) {
174             //no-op
175         }
176 
177         @Override
setImeInputTargetRequestedVisibility(boolean visible)178         public void setImeInputTargetRequestedVisibility(boolean visible) {
179             //no-op
180         }
181     }
182 
183     /**
184      * Remove a listener for a display
185      *
186      * @param listener SystemBar Inset events Listener
187      * @return if set contains such a listener, returns {@code true} otherwise false
188      */
removeListener(Listener listener)189     public boolean removeListener(Listener listener) {
190         if (mListeners == null) {
191             return false;
192         }
193         return mListeners.remove(listener);
194     }
195 
196     /**
197      * Listener for SystemBar insets events
198      */
199     public interface Listener {
200         /**
201          * show/hide keyboard
202          */
onKeyboardVisibilityChanged(boolean showing)203         void onKeyboardVisibilityChanged(boolean showing);
204     }
205 }
206