blob: bcf3b0cbfc867161f6e5ec49b8e10da5204745d9 [file] [log] [blame]
/*
* Copyright (C) 2019 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.statusbar.window;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.tappableElement;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.IWindowManager;
import android.view.InsetsFrameProvider;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
import java.util.Optional;
import javax.inject.Inject;
/**
* Encapsulates all logic for the status bar window state management.
*/
@SysUISingleton
public class StatusBarWindowController {
private static final String TAG = "StatusBarWindowController";
private static final boolean DEBUG = false;
private final Context mContext;
private final WindowManager mWindowManager;
private final IWindowManager mIWindowManager;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
private int mBarHeight = -1;
private final State mCurrentState = new State();
private boolean mIsAttached;
private final ViewGroup mStatusBarWindowView;
private final FragmentService mFragmentService;
// The container in which we should run launch animations started from the status bar and
// expanding into the opening window.
private final ViewGroup mLaunchAnimationContainer;
private WindowManager.LayoutParams mLp;
private final WindowManager.LayoutParams mLpChanged;
private final Binder mInsetsSourceOwner = new Binder();
@Inject
public StatusBarWindowController(
Context context,
@StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
WindowManager windowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
FragmentService fragmentService,
@Main Resources resources,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
mWindowManager = windowManager;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
mStatusBarWindowView = statusBarWindowView;
mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams();
if (mBarHeight < 0) {
mBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
unfoldTransitionProgressProvider.ifPresent(
unfoldProgressProvider -> unfoldProgressProvider.addCallback(
new JankMonitorTransitionProgressListener(
/* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
public int getStatusBarHeight() {
return mBarHeight;
}
/**
* Rereads the status bar height and reapplys the current state if the height
* is different.
*/
public void refreshStatusBarHeight() {
int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext);
if (mBarHeight != heightFromConfig) {
mBarHeight = heightFromConfig;
apply(mCurrentState);
}
if (DEBUG) Log.v(TAG, "defineSlots");
}
/**
* Adds the status bar view to the window manager.
*/
public void attach() {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
Trace.beginSection("StatusBarWindowController.getBarLayoutParams");
mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
Trace.endSection();
mWindowManager.addView(mStatusBarWindowView, mLp);
mLpChanged.copyFrom(mLp);
mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations);
calculateStatusBarLocationsForAllRotations();
mIsAttached = true;
apply(mCurrentState);
}
/** Adds the given view to the status bar window view. */
public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
/** Returns the status bar window's background view. */
public View getBackgroundView() {
return mStatusBarWindowView.findViewById(R.id.status_bar_container);
}
/** Returns a fragment host manager for the status bar window view. */
public FragmentHostManager getFragmentHostManager() {
return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
/**
* Provides an updated animation controller if we're animating a view in the status bar.
*
* This is needed because we have to make sure that the status bar window matches the full
* screen during the animation and that we are expanding the view below the other status bar
* text.
*
* @param rootView the root view of the animation
* @param animationController the default animation controller to use
* @return If the animation is on a view in the status bar, returns an Optional containing an
* updated animation controller that handles status-bar-related animation details. Returns an
* empty optional if the animation is *not* on a view in the status bar.
*/
public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar(
View rootView, ActivityLaunchAnimator.Controller animationController) {
if (rootView != mStatusBarWindowView) {
return Optional.empty();
}
animationController.setLaunchContainer(mLaunchAnimationContainer);
return Optional.of(new DelegateLaunchAnimatorController(animationController) {
@Override
public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
setLaunchAnimationRunning(true);
}
@Override
public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
setLaunchAnimationRunning(false);
}
});
}
private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
lp.paramsForRotation = new WindowManager.LayoutParams[4];
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
}
return lp;
}
private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
height,
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
lp.token = new Binder();
lp.gravity = Gravity.TOP;
lp.setFitInsetsTypes(0 /* types */);
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
final InsetsFrameProvider gestureInsetsProvider =
new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures());
final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.display_cutout_touchable_region_size);
if (safeTouchRegionHeight > 0) {
gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe(
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
gestureInsetsProvider
};
return lp;
}
private void calculateStatusBarLocationsForAllRotations() {
Rect[] bounds = new Rect[4];
final DisplayCutout displayCutout = mContext.getDisplay().getCutout();
bounds[0] = mContentInsetsProvider
.getBoundingRectForPrivacyChipForRotation(ROTATION_NONE, displayCutout);
bounds[1] = mContentInsetsProvider
.getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE, displayCutout);
bounds[2] = mContentInsetsProvider
.getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN, displayCutout);
bounds[3] = mContentInsetsProvider
.getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE, displayCutout);
try {
mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds);
} catch (RemoteException e) {
//Swallow
}
}
/** Set force status bar visible. */
public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
mCurrentState.mForceStatusBarVisible = forceStatusBarVisible;
apply(mCurrentState);
}
/**
* Sets whether an ongoing process requires the status bar to be forced visible.
*
* This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
* process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
* false but this method is set to true, then the status bar **will** be visible.
*
* TODO(b/195839150): We should likely merge this method and
* {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
*/
public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
}
/**
* Set whether a launch animation is currently running. If true, this will ensure that the
* window matches its parent height so that the animation is not clipped by the normal status
* bar height.
*/
private void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) {
if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) {
return;
}
mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning;
apply(mCurrentState);
}
private void applyHeight(State state) {
mLpChanged.height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
mLpChanged.paramsForRotation[rot].height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT :
SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
}
}
private void apply(State state) {
if (!mIsAttached) {
return;
}
applyForceStatusBarVisibleFlag(state);
applyHeight(state);
if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarWindowView, mLp);
}
}
private static class State {
boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
boolean mOngoingProcessRequiresStatusBarVisible;
}
private void applyForceStatusBarVisibleFlag(State state) {
if (state.mForceStatusBarVisible
|| state.mIsLaunchAnimationRunning
// Don't force-show the status bar if the user has already dismissed it.
|| state.mOngoingProcessRequiresStatusBarVisible) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else {
mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
}
}
}