blob: 5e71bcbe557e12165f5d696fe325de5f558ef32d [file] [log] [blame]
/*
* Copyright (C) 2016 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 static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
import static android.view.SurfaceControl.HIDDEN;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TASK_SNAPSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.getColorViewLeftInset;
import static com.android.internal.policy.DecorView.getColorViewTopInset;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.GraphicBuffer;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.view.IWindowSession;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DecorView;
import com.android.internal.view.BaseIWindow;
/**
* This class represents a starting window that shows a snapshot.
* <p>
* DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING METHODS OF THIS CLASS!
*/
class TaskSnapshotSurface implements StartingSurface {
private static final long SIZE_MISMATCH_MINIMUM_TIME_MS = 450;
/**
* When creating the starting window, we use the exact same layout flags such that we end up
* with a window with the exact same dimensions etc. However, these flags are not used in layout
* and might cause other side effects so we exclude them.
*/
private static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
| FLAG_NOT_TOUCHABLE
| FLAG_NOT_TOUCH_MODAL
| FLAG_ALT_FOCUSABLE_IM
| FLAG_NOT_FOCUSABLE
| FLAG_HARDWARE_ACCELERATED
| FLAG_IGNORE_CHEEK_PRESSES
| FLAG_LOCAL_FOCUS_MODE
| FLAG_SLIPPERY
| FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH
| FLAG_SCALED
| FLAG_SECURE;
private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM;
private static final int MSG_REPORT_DRAW = 0;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
private final Window mWindow;
private final Surface mSurface;
private SurfaceControl mChildSurfaceControl;
private final IWindowSession mSession;
private final WindowManagerService mService;
private final Rect mTaskBounds;
private final Rect mStableInsets = new Rect();
private final Rect mContentInsets = new Rect();
private final Rect mFrame = new Rect();
private TaskSnapshot mSnapshot;
private final CharSequence mTitle;
private boolean mHasDrawn;
private long mShownTime;
private final Handler mHandler;
private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
private final int mStatusBarColor;
@VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
TaskSnapshot snapshot) {
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
final Window window = new Window();
final IWindowSession session = WindowManagerGlobal.getWindowSession();
window.setSession(session);
final Surface surface = new Surface();
final Rect tmpRect = new Rect();
final Rect tmpFrame = new Rect();
final Rect taskBounds;
final Rect tmpContentInsets = new Rect();
final Rect tmpStableInsets = new Rect();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
int backgroundColor = WHITE;
int statusBarColor = 0;
int navigationBarColor = 0;
final int sysUiVis;
final int windowFlags;
final int windowPrivateFlags;
synchronized (service.mWindowMap) {
final WindowState mainWindow = token.findMainWindow();
if (mainWindow == null) {
Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for token="
+ token);
return null;
}
sysUiVis = mainWindow.getSystemUiVisibility();
windowFlags = mainWindow.getAttrs().flags;
windowPrivateFlags = mainWindow.getAttrs().privateFlags;
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.format = snapshot.getSnapshot().getFormat();
layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
| FLAG_NOT_FOCUSABLE
| FLAG_NOT_TOUCHABLE;
layoutParams.privateFlags = PRIVATE_FLAG_TASK_SNAPSHOT;
layoutParams.token = token.token;
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = LayoutParams.MATCH_PARENT;
layoutParams.systemUiVisibility = sysUiVis;
final Task task = token.getTask();
if (task != null) {
layoutParams.setTitle(String.format(TITLE_FORMAT, task.mTaskId));
final TaskDescription taskDescription = task.getTaskDescription();
if (taskDescription != null) {
backgroundColor = taskDescription.getBackgroundColor();
statusBarColor = taskDescription.getStatusBarColor();
navigationBarColor = taskDescription.getNavigationBarColor();
}
taskBounds = new Rect();
task.getBounds(taskBounds);
} else {
taskBounds = null;
}
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
View.VISIBLE, token.getDisplayContent().getDisplayId(), tmpRect, tmpRect,
tmpRect, null);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
return null;
}
} catch (RemoteException e) {
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect,
tmpMergedConfiguration, surface);
} catch (RemoteException e) {
// Local call.
}
snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets);
snapshotSurface.drawSnapshot();
return snapshotSurface;
}
@VisibleForTesting
TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
Rect taskBounds) {
mService = service;
mHandler = new Handler(mService.mH.getLooper());
mSession = WindowManagerGlobal.getWindowSession();
mWindow = window;
mSurface = surface;
mSnapshot = snapshot;
mTitle = title;
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mTaskBounds = taskBounds;
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
mStatusBarColor = statusBarColor;
}
@Override
public void remove() {
synchronized (mService.mWindowMap) {
final long now = SystemClock.uptimeMillis();
if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) {
mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS);
return;
}
}
try {
mSession.remove(mWindow);
} catch (RemoteException e) {
// Local call.
}
}
@VisibleForTesting
void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) {
mFrame.set(frame);
mContentInsets.set(contentInsets);
mStableInsets.set(stableInsets);
mSizeMismatch = (mFrame.width() != mSnapshot.getSnapshot().getWidth()
|| mFrame.height() != mSnapshot.getSnapshot().getHeight());
mSystemBarBackgroundPainter.setInsets(contentInsets, stableInsets);
}
private void drawSnapshot() {
final GraphicBuffer buffer = mSnapshot.getSnapshot();
if (mSizeMismatch) {
// The dimensions of the buffer and the window don't match, so attaching the buffer
// will fail. Better create a child window with the exact dimensions and fill the parent
// window with the background color!
drawSizeMismatchSnapshot(buffer);
} else {
drawSizeMatchSnapshot(buffer);
}
synchronized (mService.mWindowMap) {
mShownTime = SystemClock.uptimeMillis();
mHasDrawn = true;
}
reportDrawn();
// In case window manager leaks us, make sure we don't retain the snapshot.
mSnapshot = null;
}
private void drawSizeMatchSnapshot(GraphicBuffer buffer) {
mSurface.attachAndQueueBuffer(buffer);
mSurface.release();
}
private void drawSizeMismatchSnapshot(GraphicBuffer buffer) {
final SurfaceSession session = new SurfaceSession(mSurface);
// Keep a reference to it such that it doesn't get destroyed when finalized.
mChildSurfaceControl = new SurfaceControl(session,
mTitle + " - task-snapshot-surface",
buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN);
Surface surface = new Surface();
surface.copyFrom(mChildSurfaceControl);
// Clip off ugly navigation bar.
final Rect crop = calculateSnapshotCrop();
final Rect frame = calculateSnapshotFrame(crop);
SurfaceControl.openTransaction();
try {
// We can just show the surface here as it will still be hidden as the parent is
// still hidden.
mChildSurfaceControl.show();
mChildSurfaceControl.setWindowCrop(crop);
mChildSurfaceControl.setPosition(frame.left, frame.top);
} finally {
SurfaceControl.closeTransaction();
}
surface.attachAndQueueBuffer(buffer);
surface.release();
final Canvas c = mSurface.lockCanvas(null);
drawBackgroundAndBars(c, frame);
mSurface.unlockCanvasAndPost(c);
mSurface.release();
}
@VisibleForTesting
Rect calculateSnapshotCrop() {
final Rect rect = new Rect();
rect.set(0, 0, mSnapshot.getSnapshot().getWidth(), mSnapshot.getSnapshot().getHeight());
final Rect insets = mSnapshot.getContentInsets();
// Let's remove all system decorations except the status bar, but only if the task is at the
// very top of the screen.
rect.inset(insets.left, mTaskBounds.top != 0 ? insets.top : 0, insets.right, insets.bottom);
return rect;
}
@VisibleForTesting
Rect calculateSnapshotFrame(Rect crop) {
final Rect frame = new Rect(crop);
// By default, offset it to to top/left corner
frame.offsetTo(-crop.left, -crop.top);
// However, we also need to make space for the navigation bar on the left side.
final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left,
mContentInsets.left);
frame.offset(colorViewLeftInset, 0);
return frame;
}
@VisibleForTesting
void drawBackgroundAndBars(Canvas c, Rect frame) {
final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
final boolean fillHorizontally = c.getWidth() > frame.right;
final boolean fillVertically = c.getHeight() > frame.bottom;
if (fillHorizontally) {
c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
c.getWidth(), fillVertically
? frame.bottom
: c.getHeight(),
mBackgroundPaint);
}
if (fillVertically) {
c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
}
mSystemBarBackgroundPainter.drawDecors(c, frame);
}
private void reportDrawn() {
try {
mSession.finishDrawing(mWindow);
} catch (RemoteException e) {
// Local call.
}
}
private static Handler sHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REPORT_DRAW:
final boolean hasDrawn;
final TaskSnapshotSurface surface = (TaskSnapshotSurface) msg.obj;
synchronized (surface.mService.mWindowMap) {
hasDrawn = surface.mHasDrawn;
}
if (hasDrawn) {
surface.reportDrawn();
}
break;
}
}
};
@VisibleForTesting
static class Window extends BaseIWindow {
private TaskSnapshotSurface mOuter;
public void setOuter(TaskSnapshotSurface outer) {
mOuter = outer;
}
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
Rect stableInsets, Rect outsets, boolean reportDraw,
MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar, int displayId) {
if (reportDraw) {
sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
}
}
}
/**
* Helper class to draw the background of the system bars in regions the task snapshot isn't
* filling the window.
*/
static class SystemBarBackgroundPainter {
private final Rect mContentInsets = new Rect();
private final Rect mStableInsets = new Rect();
private final Paint mStatusBarPaint = new Paint();
private final Paint mNavigationBarPaint = new Paint();
private final int mStatusBarColor;
private final int mNavigationBarColor;
private final int mWindowFlags;
private final int mWindowPrivateFlags;
private final int mSysUiVis;
SystemBarBackgroundPainter( int windowFlags, int windowPrivateFlags, int sysUiVis,
int statusBarColor, int navigationBarColor) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mSysUiVis = sysUiVis;
final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
mStatusBarColor = DecorView.calculateStatusBarColor(windowFlags,
context.getColor(R.color.system_bar_background_semi_transparent),
statusBarColor);
mNavigationBarColor = navigationBarColor;
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(navigationBarColor);
}
void setInsets(Rect contentInsets, Rect stableInsets) {
mContentInsets.set(contentInsets);
mStableInsets.set(stableInsets);
}
int getStatusBarColorViewHeight() {
final boolean forceStatusBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
mSysUiVis, mStatusBarColor, mWindowFlags, forceStatusBarBackground)) {
return getColorViewTopInset(mStableInsets.top, mContentInsets.top);
} else {
return 0;
}
}
private boolean isNavigationBarColorViewVisible() {
return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
mSysUiVis, mNavigationBarColor, mWindowFlags, false /* force */);
}
void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
drawNavigationBarBackground(c);
}
@VisibleForTesting
void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
int statusBarHeight) {
if (statusBarHeight > 0
&& (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
final int rightInset = DecorView.getColorViewRightInset(mStableInsets.right,
mContentInsets.right);
final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
}
}
@VisibleForTesting
void drawNavigationBarBackground(Canvas c) {
final Rect navigationBarRect = new Rect();
getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets,
navigationBarRect);
final boolean visible = isNavigationBarColorViewVisible();
if (visible && !navigationBarRect.isEmpty()) {
c.drawRect(navigationBarRect, mNavigationBarPaint);
}
}
}
}