Android最近任务显示的图片
- 1、TaskSnapshot截图
- 1.1 snapshotTask
- 1.2 drawAppThemeSnapshot
- 2、导航栏显示问题
- 3、Recentan按键进入最近任务
1、TaskSnapshot截图
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java
frameworks/base/core/java/android/view/SurfaceControl.java
frameworks/base/core/jni/android_view_SurfaceControl.cpp
frameworks/native/libs/gui/SurfaceComposerClient.cpp
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
截图保存路径:data/system_ce/0/snapshots
TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) {
final TaskSnapshot snapshot;
if (snapshotHome) {
snapshot = snapshotTask(task);
} else {
switch (getSnapshotMode(task)) {
case SNAPSHOT_MODE_NONE:
return null;
case SNAPSHOT_MODE_APP_THEME:
snapshot = drawAppThemeSnapshot(task);
break;
case SNAPSHOT_MODE_REAL:
snapshot = snapshotTask(task);
break;
default:
snapshot = null;
break;
}
}
return snapshot;
}
1.1 snapshotTask
SNAPSHOT_MODE_REAL
:截图一张真实的屏幕截图作为快照。
实际截图缓存SurfaceControl.captureLayersExcluding -> ScreenshotClient::captureLayers -> SurfaceFlinger::captureLayers
TaskSnapshot snapshotTask(Task task, int pixelFormat) {
TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
if (!prepareTaskSnapshot(task, pixelFormat, builder)) {
// Failed some pre-req. Has been logged.
return null;
}
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
createTaskSnapshot(task, builder);
if (screenshotBuffer == null) {
// Failed to acquire image. Has been logged.
return null;
}
builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
builder.setColorSpace(screenshotBuffer.getColorSpace());
return builder.build();
}
SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
TaskSnapshot.Builder builder) {
Point taskSize = new Point();
final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
builder.setTaskSize(taskSize);
return taskSnapshot;
}
SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
if (task.getSurfaceControl() == null) {
if (DEBUG_SCREENSHOT) {
Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
}
return null;
}
task.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
SurfaceControl[] excludeLayers;
final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
// Exclude IME window snapshot when IME isn't proper to attach to app.
final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
&& !task.getDisplayContent().shouldImeAttachedToApp();
final WindowState navWindow =
task.getDisplayContent().getDisplayPolicy().getNavigationBar();
// If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
// the swiped app when entering recent app, therefore the task will contain the navigation
// bar and we should exclude it from snapshot.
final boolean excludeNavBar = navWindow != null;
if (excludeIme && excludeNavBar) {
excludeLayers = new SurfaceControl[2];
excludeLayers[0] = imeWindow.getSurfaceControl();
excludeLayers[1] = navWindow.getSurfaceControl();
} else if (excludeIme || excludeNavBar) {
excludeLayers = new SurfaceControl[1];
excludeLayers[0] =
excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
} else {
excludeLayers = new SurfaceControl[0];
}
builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureLayersExcluding(
task.getSurfaceControl(), mTmpRect, scaleFraction,
pixelFormat, excludeLayers);
if (outTaskSize != null) {
outTaskSize.x = mTmpRect.width();
outTaskSize.y = mTmpRect.height();
}
final HardwareBuffer buffer = screenshotBuffer == null ? null
: screenshotBuffer.getHardwareBuffer();
if (isInvalidHardwareBuffer(buffer)) {
return null;
}
return screenshotBuffer;
}
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
status_t validate = validateScreenshotPermissions(args);
if (validate != OK) {
return validate;
}
ui::Size reqSize;
sp<Layer> parent;
Rect crop(args.sourceCrop);
std::unordered_set<sp<Layer>, SpHash<Layer>> excludeLayers;
ui::Dataspace dataspace;
// Call this before holding mStateLock to avoid any deadlocking.
bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
{
Mutex::Autolock lock(mStateLock);
parent = fromHandle(args.layerHandle).promote();
if (parent == nullptr) {
ALOGE("captureLayers called with an invalid or removed parent");
return NAME_NOT_FOUND;
}
if (!canCaptureBlackoutContent &&
parent->getDrawingState().flags & layer_state_t::eLayerSecure) {
ALOGW("Attempting to capture secure layer: PERMISSION_DENIED");
return PERMISSION_DENIED;
}
Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getDrawingState());
if (args.sourceCrop.width() <= 0) {
crop.left = 0;
crop.right = parentSourceBounds.getWidth();
}
if (args.sourceCrop.height() <= 0) {
crop.top = 0;
crop.bottom = parentSourceBounds.getHeight();
}
if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
// Error out if the layer has no source bounds (i.e. they are boundless) and a source
// crop was not specified, or an invalid frame scale was provided.
return BAD_VALUE;
}
reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
for (const auto& handle : args.excludeHandles) {
sp<Layer> excludeLayer = fromHandle(handle).promote();
if (excludeLayer != nullptr) {
excludeLayers.emplace(excludeLayer);
} else {
ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
return NAME_NOT_FOUND;
}
}
// The dataspace is depended on the color mode of display, that could use non-native mode
// (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes,
// and failed if display is not in native mode. This provide a way to force using native
// colors when capture.
dataspace = args.dataspace;
} // mStateLock
// really small crop or frameScale
if (reqSize.width <= 0 || reqSize.height <= 0) {
ALOGW("Failed to captureLayes: crop or scale too small");
return BAD_VALUE;
}
Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height);
bool childrenOnly = args.childrenOnly;
RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
childrenOnly, layerStackSpaceRect,
args.captureSecureLayers);
});
auto traverseLayers = [parent, args, excludeLayers](const LayerVector::Visitor& visitor) {
parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
if (!layer->isVisible()) {
return;
} else if (args.childrenOnly && layer == parent.get()) {
return;
} else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
return;
}
sp<Layer> p = layer;
while (p != nullptr) {
if (excludeLayers.count(p) != 0) {
return;
}
p = p->getParent();
}
visitor(layer);
});
};
if (captureListener == nullptr) {
ALOGE("capture screen must provide a capture listener callback");
return BAD_VALUE;
}
auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
args.pixelFormat, args.allowProtected, args.grayscale,
captureListener);
return fenceStatus(future.get());
}
1.2 drawAppThemeSnapshot
SNAPSHOT_MODE_APP_THEME
:不允许截图真实的屏幕截图,但我们应该尝试使用应用程序主题来创建应用程序的虚假表示。
/**
* If we are not allowed to take a real screenshot, this attempts to represent the app as best
* as possible by using the theme's window background.
*/
private TaskSnapshot drawAppThemeSnapshot(Task task) {
final ActivityRecord topChild = task.getTopMostActivity();
if (topChild == null) {
return null;
}
final WindowState mainWindow = topChild.findMainWindow();
if (mainWindow == null) {
return null;
}
final int color = ColorUtils.setAlphaComponent(
task.getTaskDescription().getBackgroundColor(), 255);
final LayoutParams attrs = mainWindow.getAttrs();
final Rect taskBounds = task.getBounds();
final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
mHighResTaskSnapshotScale, insetsState);
final int taskWidth = taskBounds.width();
final int taskHeight = taskBounds.height();
final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
final RenderNode node = RenderNode.create("TaskSnapshotController", null);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
final RecordingCanvas c = node.start(width, height);
c.drawColor(color);
decorPainter.setInsets(systemBarInsets);
decorPainter.drawDecors(c /* statusBarExcludeFrame */);
node.end(c);
final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
if (hwBitmap == null) {
return null;
}
final Rect contentInsets = new Rect(systemBarInsets);
final Rect letterboxInsets = topChild.getLetterboxInsets();
InsetUtils.addInsets(contentInsets, letterboxInsets);
// Note, the app theme snapshot is never translucent because we enforce a non-translucent
// color above
return new TaskSnapshot(
System.currentTimeMillis() /* id */,
topChild.mActivityComponent, hwBitmap.getHardwareBuffer(),
hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
contentInsets, letterboxInsets, false /* isLowResolution */,
false /* isRealSnapshot */, task.getWindowingMode(),
getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */);
}
2、导航栏显示问题
应用设置导航栏可避免图片底部黑条
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
3、Recentan按键进入最近任务
<!-- Component name for the activity that will be presenting the Recents UI, which will receive
special permissions for API related to fetching and presenting recent tasks. The default
configuration uses Launcehr3QuickStep as default launcher and points to the corresponding
recents component. When using a different default launcher, change this appropriately or
use the default systemui implementation: com.android.systemui/.recents.RecentsActivity -->
<string name="config_recentsComponentName" translatable="false"
>com.android.launcher3/com.android.quickstep.RecentsActivity</string>