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