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 frameScaleif (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 abovereturn 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 receivespecial permissions for API related to fetching and presenting recent tasks. The defaultconfiguration uses Launcehr3QuickStep as default launcher and points to the correspondingrecents component. When using a different default launcher, change this appropriately oruse the default systemui implementation: com.android.systemui/.recents.RecentsActivity -->
<string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>