/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.wm; import static android.content.Intent.ACTION_OVERLAY_CHANGED; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.car.users.CarSystemUIUserUtil.isSecondaryMUMDSystemUI; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; import android.view.IWindowManager; import android.view.InsetsController; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import androidx.annotation.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import java.util.Arrays; /** * Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to * give system bar control to SystemUI. * {@link R.bool#config_remoteInsetsControllerControlsSystemBars} determines whether this controller * takes control or not. */ public class DisplaySystemBarsController implements DisplayController.OnDisplaysChangedListener { private static final String TAG = DisplaySystemBarsController.class.getSimpleName(); private static final int STATE_NON_IMMERSIVE = systemBars(); private static final int STATE_IMMERSIVE_WITH_NAV_BAR = navigationBars(); private static final int STATE_IMMERSIVE_WITH_STATUS_BAR = statusBars(); private static final int STATE_IMMERSIVE = 0; private static final boolean DEBUG = Log.isLoggable(DisplayController.class.getSimpleName(), Log.DEBUG); protected final Context mContext; protected final IWindowManager mWmService; protected final DisplayInsetsController mDisplayInsetsController; protected final Handler mHandler; private final int[] mDefaultVisibilities = new int[]{WindowInsets.Type.systemBars(), 0}; private final int[] mImmersiveWithNavBarVisibilities = new int[]{ WindowInsets.Type.navigationBars() | WindowInsets.Type.captionBar() | WindowInsets.Type.systemOverlays(), WindowInsets.Type.statusBars() }; private final int[] mImmersiveWithStatusBarVisibilities = new int[]{ WindowInsets.Type.statusBars() | WindowInsets.Type.captionBar() | WindowInsets.Type.systemOverlays(), WindowInsets.Type.navigationBars() }; private final int[] mImmersiveVisibilities = new int[]{0, WindowInsets.Type.systemBars()}; @VisibleForTesting SparseArray mPerDisplaySparseArray; @InsetsType private int mWindowRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); @InsetsType private int mAppRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); @InsetsType private int mImmersiveState = systemBars(); public DisplaySystemBarsController( Context context, IWindowManager wmService, DisplayController displayController, DisplayInsetsController displayInsetsController, @Main Handler mainHandler) { mContext = context; mWmService = wmService; mDisplayInsetsController = displayInsetsController; mHandler = mainHandler; if (!isSecondaryMUMDSystemUI()) { // This WM controller should only be initialized once for the primary SystemUI, as it // will affect insets on all displays. // TODO(b/262773276): support per-user remote inset controllers displayController.addDisplayWindowListener(this); } } @Override public void onDisplayAdded(int displayId) { PerDisplay pd = new PerDisplay(displayId); pd.register(); // Lazy loading policy control filters instead of during boot. if (mPerDisplaySparseArray == null) { mPerDisplaySparseArray = new SparseArray<>(); BarControlPolicy.reloadFromSetting(mContext); BarControlPolicy.registerContentObserver(mContext, mHandler, () -> { int size = mPerDisplaySparseArray.size(); for (int i = 0; i < size; i++) { mPerDisplaySparseArray.valueAt(i).updateDisplayWindowRequestedVisibleTypes(); } }); } mPerDisplaySparseArray.put(displayId, pd); } @Override public void onDisplayRemoved(int displayId) { PerDisplay pd = mPerDisplaySparseArray.get(displayId); pd.unregister(); mPerDisplaySparseArray.remove(displayId); } class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener { private static final String OVERLAY_FILTER_DATA_SCHEME = "package"; int mDisplayId; InsetsController mInsetsController; @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); String mPackageName; int mBehavior = 0; PerDisplay(int displayId) { mDisplayId = displayId; InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class); mInsetsController = new InsetsController( new DisplaySystemBarsInsetsControllerHost(mHandler, requestedVisibleTypes -> { mRequestedVisibleTypes = requestedVisibleTypes; updateDisplayWindowRequestedVisibleTypes(); }, inputMethodManager) ); mBehavior = mContext.getResources().getInteger( R.integer.config_systemBarPersistency); registerOverlayChangeBroadcastReceiver(); } public void register() { mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this); } public void unregister() { mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this); } @Override public void insetsChanged(InsetsState insetsState) { mInsetsController.onStateChanged(insetsState); updateDisplayWindowRequestedVisibleTypes(); } @Override public void hideInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { mInsetsController.hide(types, /* fromIme = */ false, statsToken); } } @Override public void showInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { mInsetsController.show(types, /* fromIme= */ false, statsToken); } } @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { InsetsSourceControl[] nonImeControls = null; // Need to filter out IME control to prevent control after leash is released if (activeControls != null) { nonImeControls = Arrays.stream(activeControls).filter( c -> c.getType() != WindowInsets.Type.ime()).toArray( InsetsSourceControl[]::new); } mInsetsController.onControlsChanged(nonImeControls); } @Override public void topFocusedWindowChanged(ComponentName component, @InsetsType int requestedVisibleTypes) { String packageName = component != null ? component.getPackageName() : null; boolean showNavRequest = (requestedVisibleTypes & navigationBars()) == navigationBars(); boolean showStatusRequest = (requestedVisibleTypes & statusBars()) == statusBars(); boolean maybeUpdate = (mWindowRequestedVisibleTypes != requestedVisibleTypes || ( mPackageName != null && !mPackageName.equals(packageName))); if (DEBUG) { Slog.d(TAG, "topFocusedWindowChanged behavior = " + mBehavior + ", component = " + component + ", requestedVisibleTypes = " + requestedVisibleTypes + ", showNavRequest = " + showNavRequest + ", showStatusRequest = " + showStatusRequest + ", mWindowRequestedVisibleTypes = " + mWindowRequestedVisibleTypes + ", mPackageName = " + mPackageName + ", maybeUpdate = " + maybeUpdate); } if (maybeUpdate) { if (mBehavior == 1) { mImmersiveState = 0; if (showNavRequest) { mImmersiveState |= navigationBars(); } if (showStatusRequest) { mImmersiveState |= statusBars(); } } else if (mBehavior == 2) { mImmersiveState = navigationBars(); if (showStatusRequest) { mImmersiveState |= statusBars(); } } } else { mImmersiveState = STATE_NON_IMMERSIVE; } mPackageName = packageName; updateDisplayWindowRequestedVisibleTypes(); } @Override public void setImeInputTargetRequestedVisibility(boolean visible) { // TODO } private void registerOverlayChangeBroadcastReceiver() { IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); overlayFilter.addDataScheme(OVERLAY_FILTER_DATA_SCHEME); overlayFilter.addDataSchemeSpecificPart(mContext.getPackageName(), PatternMatcher.PATTERN_LITERAL); BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Slog.d(TAG, "topFocusedWindowChanged behavior = "); mBehavior = mContext.getResources().getInteger( R.integer.config_systemBarPersistency); Slog.d(TAG, "Refresh system bar persistency behavior on overlay change" + mBehavior); } }; mContext.registerReceiverAsUser(receiver, UserHandle.ALL, overlayFilter, /* broadcastPermission= */null, /* handler= */ null); } protected void updateDisplayWindowRequestedVisibleTypes() { if (mPackageName == null) { return; } int[] barVisibilities; if (mImmersiveState == STATE_IMMERSIVE_WITH_NAV_BAR) { barVisibilities = mImmersiveWithNavBarVisibilities; } else if (mImmersiveState == STATE_IMMERSIVE_WITH_STATUS_BAR) { barVisibilities = mImmersiveWithStatusBarVisibilities; } else if (mImmersiveState == STATE_IMMERSIVE) { barVisibilities = mImmersiveVisibilities; } else if (mImmersiveState == STATE_NON_IMMERSIVE) { barVisibilities = mDefaultVisibilities; } else { barVisibilities = mDefaultVisibilities; } if (DEBUG) { Slog.d(TAG, "mImmersiveState = " + mImmersiveState + "barVisibilities to " + Arrays.toString(barVisibilities)); } updateRequestedVisibleTypes(barVisibilities[0], /* visible= */ true); updateRequestedVisibleTypes(barVisibilities[1], /* visible= */ false); if (mAppRequestedVisibleTypes == mRequestedVisibleTypes) { return; } mAppRequestedVisibleTypes = mRequestedVisibleTypes; showInsets(barVisibilities[0], /* fromIme= */ false, /* statsToken= */ null); hideInsets(barVisibilities[1], /* fromIme= */ false, /* statsToken = */ null); try { mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId, mRequestedVisibleTypes); } catch (RemoteException e) { Slog.w(TAG, "Unable to update window manager service."); } } protected void updateRequestedVisibleTypes(@InsetsType int types, boolean visible) { mRequestedVisibleTypes = visible ? (mRequestedVisibleTypes | types) : (mRequestedVisibleTypes & ~types); } } }