blob: b0eaf1488f2115f6a105e074993243fe2e95c724 [file] [log] [blame]
/*
* Copyright (C) 2017 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.server.wm;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
import android.os.Parcel;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
/**
* SurfaceControl extension that has black background behind navigation bar area for fullscreen
* letterboxed apps.
*/
class SurfaceControlWithBackground extends SurfaceControl {
// SurfaceControl that holds the background.
private SurfaceControl mBackgroundControl;
// Flag that defines whether the background should be shown.
private boolean mVisible;
// Way to communicate with corresponding window.
private WindowSurfaceController mWindowSurfaceController;
// Rect to hold task bounds when computing metrics for background.
private Rect mTmpContainerRect = new Rect();
// Last metrics applied to the main SurfaceControl.
private float mLastWidth, mLastHeight;
private float mLastDsDx = 1, mLastDsDy = 1;
private float mLastX, mLastY;
// SurfaceFlinger doesn't support crop rectangles where width or height is non-positive.
// If we just set an empty crop it will behave as if there is no crop at all.
// To fix this we explicitly hide the surface and won't let it to be shown.
private boolean mHiddenForCrop = false;
public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
super(other);
mBackgroundControl = other.mBackgroundControl;
mVisible = other.mVisible;
mWindowSurfaceController = other.mWindowSurfaceController;
}
public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
int flags, int windowType, int ownerUid,
WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
super(s, name, w, h, format, flags, windowType, ownerUid);
// We should only show background behind app windows that are letterboxed in a task.
if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
|| !windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
return;
}
mWindowSurfaceController = windowSurfaceController;
mLastWidth = w;
mLastHeight = h;
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
flags | SurfaceControl.FX_SURFACE_DIM);
}
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.setAlpha(alpha);
}
@Override
public void setLayer(int zorder) {
super.setLayer(zorder);
if (mBackgroundControl == null) {
return;
}
// TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
mBackgroundControl.setLayer(zorder - 1);
}
@Override
public void setPosition(float x, float y) {
super.setPosition(x, y);
if (mBackgroundControl == null) {
return;
}
mLastX = x;
mLastY = y;
updateBgPosition();
}
private void updateBgPosition() {
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
}
@Override
public void setSize(int w, int h) {
super.setSize(w, h);
if (mBackgroundControl == null) {
return;
}
mLastWidth = w;
mLastHeight = h;
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
}
@Override
public void setWindowCrop(Rect crop) {
super.setWindowCrop(crop);
if (mBackgroundControl == null) {
return;
}
calculateBgCrop(crop);
mBackgroundControl.setWindowCrop(mTmpContainerRect);
mHiddenForCrop = mTmpContainerRect.isEmpty();
updateBackgroundVisibility();
}
@Override
public void setFinalCrop(Rect crop) {
super.setFinalCrop(crop);
if (mBackgroundControl == null) {
return;
}
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
mBackgroundControl.setFinalCrop(mTmpContainerRect);
}
/**
* Compute background crop based on current animation progress for main surface control and
* update {@link #mTmpContainerRect} with new values.
*/
private void calculateBgCrop(Rect crop) {
// Track overall progress of animation by computing cropped portion of status bar.
final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
if (d > 1.f) {
// We're running expand animation from launcher, won't compute custom bg crop here.
mTmpContainerRect.setEmpty();
return;
}
// Compute new scaled width and height for background that will depend on current animation
// progress. Those consist of current crop rect for the main surface + scaled areas outside
// of letterboxed area.
// TODO: Because the progress is computed with low precision we're getting smaller values
// for background width/height then screen size at the end of the animation. Will round when
// the value is smaller then some empiric epsilon. However, this should be fixed by
// computing correct frames for letterboxed windows in WindowState.
d = d < 0.025f ? 0 : d;
mWindowSurfaceController.getContainerRect(mTmpContainerRect);
int backgroundWidth = 0, backgroundHeight = 0;
// Compute additional offset for the background when app window is positioned not at (0,0).
// E.g. landscape with navigation bar on the left.
final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
int offsetX = (int)((winFrame.left - mTmpContainerRect.left) * mLastDsDx),
offsetY = (int) ((winFrame.top - mTmpContainerRect.top) * mLastDsDy);
// Position and size background.
final int bgPosition = mWindowSurfaceController.mAnimator.mService.getNavBarPosition();
switch (bgPosition) {
case NAV_BAR_LEFT:
backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
backgroundHeight = crop.height();
offsetX += crop.left - backgroundWidth;
offsetY += crop.top;
break;
case NAV_BAR_RIGHT:
backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
backgroundHeight = crop.height();
offsetX += crop.right;
offsetY += crop.top;
break;
case NAV_BAR_BOTTOM:
backgroundWidth = crop.width();
backgroundHeight = (int) ((mTmpContainerRect.height() - mLastHeight) * (1 - d)
+ 0.5);
offsetX += crop.left;
offsetY += crop.bottom;
break;
}
mTmpContainerRect.set(offsetX, offsetY, offsetX + backgroundWidth,
offsetY + backgroundHeight);
}
@Override
public void setLayerStack(int layerStack) {
super.setLayerStack(layerStack);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.setLayerStack(layerStack);
}
@Override
public void setOpaque(boolean isOpaque) {
super.setOpaque(isOpaque);
updateBackgroundVisibility();
}
@Override
public void setSecure(boolean isSecure) {
super.setSecure(isSecure);
}
@Override
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
super.setMatrix(dsdx, dtdx, dtdy, dsdy);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
mLastDsDx = dsdx;
mLastDsDy = dsdy;
updateBgPosition();
}
@Override
public void hide() {
super.hide();
mVisible = false;
updateBackgroundVisibility();
}
@Override
public void show() {
super.show();
mVisible = true;
updateBackgroundVisibility();
}
@Override
public void destroy() {
super.destroy();
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.destroy();
}
@Override
public void release() {
super.release();
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.release();
}
@Override
public void setTransparentRegionHint(Region region) {
super.setTransparentRegionHint(region);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.setTransparentRegionHint(region);
}
@Override
public void deferTransactionUntil(IBinder handle, long frame) {
super.deferTransactionUntil(handle, frame);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.deferTransactionUntil(handle, frame);
}
@Override
public void deferTransactionUntil(Surface barrier, long frame) {
super.deferTransactionUntil(barrier, frame);
if (mBackgroundControl == null) {
return;
}
mBackgroundControl.deferTransactionUntil(barrier, frame);
}
private void updateBackgroundVisibility() {
if (mBackgroundControl == null) {
return;
}
final AppWindowToken appWindowToken = mWindowSurfaceController.mAnimator.mWin.mAppToken;
if (!mHiddenForCrop && mVisible && appWindowToken != null && appWindowToken.fillsParent()) {
mBackgroundControl.show();
} else {
mBackgroundControl.hide();
}
}
}