在任务管理器中,有截取当前activity的图片作为单个任务的背景
现在就看看任务管理器图片背景
@Override public void toggleRecents(Display display, int layoutDirection, View statusBarView) { if (mUseAlternateRecents) { // Launch the alternate recents if required sAlternateRecents.onToggleRecents(); return; } if (DEBUG) Log.d(TAG, "toggle recents panel"); try { TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); intent.setClassName("com.android.systemui", "com.android.systemui.recent.RecentsActivity"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); if (firstTask == null) { if (RecentsActivity.forceOpaqueBackground(mContext)) { ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, R.anim.recents_launch_from_launcher_enter, R.anim.recents_launch_from_launcher_exit); mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( UserHandle.USER_CURRENT)); } else { // The correct window animation will be applied via the activity's style mContext.startActivityAsUser(intent, new UserHandle( UserHandle.USER_CURRENT)); } } else { Bitmap first = null; if (firstTask.getThumbnail() instanceof BitmapDrawable) { first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap(); } else { first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail(); d.draw(new Canvas(first)); } final Resources res = mContext.getResources(); float thumbWidth = res .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); float thumbHeight = res .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); if (first == null) { throw new RuntimeException("Recents thumbnail is null"); } if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, true); if (first == null) { throw new RuntimeException("Recents thumbnail is null"); } } DisplayMetrics dm = new DisplayMetrics(); display.getMetrics(dm); // calculate it here, but consider moving it elsewhere // first, determine which orientation you're in. final Configuration config = res.getConfiguration(); int x, y; if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { float appLabelLeftMargin = res.getDimensionPixelSize( R.dimen.status_bar_recents_app_label_left_margin); float appLabelWidth = res.getDimensionPixelSize( R.dimen.status_bar_recents_app_label_width); float thumbLeftMargin = res.getDimensionPixelSize( R.dimen.status_bar_recents_thumbnail_left_margin); float thumbBgPadding = res.getDimensionPixelSize( R.dimen.status_bar_recents_thumbnail_bg_padding); float width = appLabelLeftMargin + +appLabelWidth + thumbLeftMargin + thumbWidth + 2 * thumbBgPadding; x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth + thumbBgPadding + thumbLeftMargin); y = (int) (dm.heightPixels - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding); if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { x = dm.widthPixels - x - res.getDimensionPixelSize( R.dimen.status_bar_recents_thumbnail_width); } } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { float thumbTopMargin = res.getDimensionPixelSize( R.dimen.status_bar_recents_thumbnail_top_margin); float thumbBgPadding = res.getDimensionPixelSize( R.dimen.status_bar_recents_thumbnail_bg_padding); float textPadding = res.getDimensionPixelSize( R.dimen.status_bar_recents_text_description_padding); float labelTextSize = res.getDimensionPixelSize( R.dimen.status_bar_recents_app_label_text_size); Paint p = new Paint(); p.setTextSize(labelTextSize); float labelTextHeight = p.getFontMetricsInt().bottom - p.getFontMetricsInt().top; float descriptionTextSize = res.getDimensionPixelSize( R.dimen.status_bar_recents_app_description_text_size); p.setTextSize(descriptionTextSize); float descriptionTextHeight = p.getFontMetricsInt().bottom - p.getFontMetricsInt().top; float statusBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); float recentsItemTopPadding = statusBarHeight; float height = thumbTopMargin + thumbHeight + 2 * thumbBgPadding + textPadding + labelTextHeight + recentsItemTopPadding + textPadding + descriptionTextHeight; float recentsItemRightPadding = res .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); float recentsScrollViewRightPadding = res .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); x = (int) (dm.widthPixels - res .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) - thumbBgPadding - recentsItemRightPadding - recentsScrollViewRightPadding); y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin + recentsItemTopPadding + thumbBgPadding + statusBarHeight); } ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( statusBarView, first, x, y, new ActivityOptions.OnAnimationStartedListener() { public void onAnimationStarted() { Intent intent = new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT); intent.setPackage("com.android.systemui"); sendBroadcastSafely(intent); } }); intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true); startActivitySafely(intent, opts.toBundle()); } } catch (ActivityNotFoundException e) { Log.e(TAG, "Failed to launch RecentAppsIntent", e); } }点击任务管理器启动之后会调用这段代码,TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); 这个是获取任务管理器的图标和背景图片
public TaskDescription getFirstTask() { while(true) { synchronized(mFirstTaskLock) { if (mFirstTaskLoaded) { return mFirstTask; } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) { mFirstTask = loadFirstTask(); mFirstTaskLoaded = true; return mFirstTask; } } try { Thread.sleep(3); } catch (InterruptedException e) { } } }第一次点击没有加载过图片和背景,所以会直接到loadFirstTask()
public TaskDescription loadFirstTask() { final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1, ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES, UserHandle.CURRENT.getIdentifier()); TaskDescription item = null; if (recentTasks.size() > 0) { ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); Intent intent = new Intent(recentInfo.baseIntent); if (recentInfo.origActivity != null) { intent.setComponent(recentInfo.origActivity); } // Don't load the current home activity. if (isCurrentHomeActivity(intent.getComponent(), null)) { return null; } // Don't load ourselves if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) { return null; } item = createTaskDescription(recentInfo.id, recentInfo.persistentId, recentInfo.baseIntent, recentInfo.origActivity, recentInfo.description, recentInfo.userId); if (item != null) { loadThumbnailAndIcon(item); } return item; } return null; }这里会获取最近的任务列表,继而获取包名类名信息,有了这些就可以继续去寻找图标和背景了loadThumbnailAndIcon(item);
void loadThumbnailAndIcon(TaskDescription td) { final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); final PackageManager pm = mContext.getPackageManager(); final Bitmap thumbnail = SystemServicesProxy.getThumbnail(am, td.persistentTaskId); Log.e("loadThumbnailAndIcon", "thumbnail ==persistentTaskId=" + thumbnail); Drawable icon = getFullResIcon(td.resolveInfo, pm); if (td.userId != UserHandle.myUserId()) { // Need to badge the icon icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(td.userId)); } if (DEBUG) Log.v(TAG, "Loaded bitmap for task " + td + ": " + thumbnail); synchronized (td) { if (thumbnail != null) { td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail)); } else { td.setThumbnail(mDefaultThumbnailBackground); } if (icon != null) { td.setIcon(icon); } td.setLoaded(true); } }Drawable icon = getFullResIcon(td.resolveInfo, pm);通过pm拿到图标 SystemServicesProxy.getThumbnail(am, td.persistentTaskId); 通过am去拿缩略图,继续跟进
public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) { ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId); if (taskThumbnail == null) { if (DEBUG) { Xlog.d(TAG, "getThumbnail: getTaskThumbnail is null: " + taskId); } return null; } Bitmap thumbnail = taskThumbnail.mainThumbnail; ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; if (thumbnail == null && descriptor != null) { thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(), null, sBitmapOptions); } if (descriptor != null) { try { descriptor.close(); } catch (IOException e) { } } if (DEBUG && thumbnail == null) { Xlog.d(TAG, "getThumbnail: thumbnail is null"); } return thumbnail; }这里只是做一些逻辑判断,其实还是去am里面去拿缩略图
/** @hide */ public TaskThumbnail getTaskThumbnail(int id) throws SecurityException { try { return ActivityManagerNative.getDefault().getTaskThumbnail(id); } catch (RemoteException e) { // System dead, we will be dead too soon! return null; } }这里是通过ActivityManagerNative.getDefault()获取缩略图
继续
public IBinder asBinder() { return this; } private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b); if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } }; } /** * Retrieve the system's default/global activity manager. */ static public IActivityManager getDefault() { return gDefault.get(); }本质是通过IBinder ipc调用到ActivityManagerService 的
@Override public ActivityManager.TaskThumbnail getTaskThumbnail(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "getTaskThumbnail()"); TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id); if (tr != null) { return tr.getTaskThumbnailLocked(); } } return null; }最终会调用到
public TaskThumbnail getTaskThumbnailLocked() { if (stack != null) { final ActivityRecord resumedActivity = stack.mResumedActivity; //mLastNoHistoryActivity mLastPausedActivity mPausingActivity mLastStartedActivity // if("com.mxtech.videoplayer.pro".equals(realActivity.getPackageName())){ // resumedActivity = stack.topActivity(); // } Slog.e("wanzhichengtask","resumedActivity === " + resumedActivity ); if (resumedActivity != null && resumedActivity.task == this) { final Bitmap thumbnail = stack.screenshotActivities(resumedActivity); setLastThumbnail(thumbnail); } } final TaskThumbnail taskThumbnail = new TaskThumbnail(); getLastThumbnail(taskThumbnail); return taskThumbnail; }TaskThumbnail 对象包含一个mainThumbnail就是缩略图 final Bitmap thumbnail = stack.screenshotActivities(resumedActivity); 这个ActivityRecord resumedActivity是activity在resume时的状态记录,可以通过这个对象获取缩略图
public final Bitmap screenshotActivities(ActivityRecord who) { if (DEBUG_SCREENSHOTS) Slog.d(TAG, "screenshotActivities: " + who); if (who.noDisplay) { if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tNo display"); return null; } if (isHomeStack()) { // This is an optimization -- since we never show Home or Recents within Recents itself, // we can just go ahead and skip taking the screenshot if this is the home stack. if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tHome stack"); return null; } int w = mService.mThumbnailWidth; int h = mService.mThumbnailHeight; if (w > 0) { if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tTaking screenshot"); return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565); } Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h); return null; }最后是 通过mWindowManager.screenshotApplications来获取图片
@Override public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height, boolean force565) { if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER, "screenshotApplications()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent == null) { if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + ": returning null. No Display for displayId=" + displayId); return null; } final DisplayInfo displayInfo = displayContent.getDisplayInfo(); int dw = displayInfo.logicalWidth; int dh = displayInfo.logicalHeight; if (dw == 0 || dh == 0) { if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + ": returning null. logical widthxheight=" + dw + "x" + dh); return null; } Bitmap bm = null; int maxLayer = 0; final Rect frame = new Rect(); final Rect stackBounds = new Rect(); float scale = 0; int rot = Surface.ROTATION_0; boolean screenshotReady; int minLayer; if (appToken == null) { screenshotReady = true; minLayer = 0; } else { screenshotReady = false; minLayer = Integer.MAX_VALUE; } int retryCount = 0; WindowState appWin = null; final boolean appIsImTarget = mInputMethodTarget != null && mInputMethodTarget.mAppToken != null && mInputMethodTarget.mAppToken.appToken != null && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken; final int aboveAppLayer = (mPolicy.windowTypeToLayerLw(TYPE_APPLICATION) + 1) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; while (true) { if (retryCount++ > 0) { // Reset max/min layers on retries so we don't accidentally take a screenshot of a // layer based on the previous try. maxLayer = 0; minLayer = Integer.MAX_VALUE; try { Thread.sleep(100); } catch (InterruptedException e) { } } synchronized(mWindowMap) { // Figure out the part of the screen that is actually the app. appWin = null; final WindowList windows = displayContent.getWindowList(); for (int i = windows.size() - 1; i >= 0; i--) { WindowState ws = windows.get(i); if (!ws.mHasSurface) { continue; } if (ws.mLayer >= aboveAppLayer) { continue; } if (ws.mIsImWindow) { if (!appIsImTarget) { continue; } } else if (ws.mIsWallpaper) { if (appWin == null) { // We have not ran across the target window yet, so it is probably // behind the wallpaper. This can happen when the keyguard is up and // all windows are moved behind the wallpaper. We don't want to // include the wallpaper layer in the screenshot as it will coverup // the layer of the target window. continue; } // Fall through. The target window is in front of the wallpaper. For this // case we want to include the wallpaper layer in the screenshot because // the target window might have some transparent areas. } else if (appToken != null) { if (ws.mAppToken == null || ws.mAppToken.token != appToken) { // This app window is of no interest if it is not associated with the // screenshot app. continue; } appWin = ws; } // Include this window. final WindowStateAnimator winAnim = ws.mWinAnimator; if (maxLayer < winAnim.mSurfaceLayer) { maxLayer = winAnim.mSurfaceLayer; } if (minLayer > winAnim.mSurfaceLayer) { minLayer = winAnim.mSurfaceLayer; } // Don't include wallpaper in bounds calculation if (!ws.mIsWallpaper) { final Rect wf = ws.mFrame; final Rect cr = ws.mContentInsets; int left = wf.left + cr.left; int top = wf.top + cr.top; int right = wf.right - cr.right; int bottom = wf.bottom - cr.bottom; frame.union(left, top, right, bottom); ws.getStackBounds(stackBounds); frame.intersect(stackBounds); } if (ws.mAppToken != null && ws.mAppToken.token == appToken && ws.isDisplayedLw()) { screenshotReady = true; } } if (appToken != null && appWin == null) { // Can't find a window to snapshot. if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: Couldn't find a surface matching " + appToken); return null; } if (!screenshotReady) { if (retryCount > MAX_SCREENSHOT_RETRIES) { Slog.i(TAG, "Screenshot max retries " + retryCount + " of " + appToken + " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" + appWin.mWinAnimator.mDrawState))); return null; } // Delay and hope that window gets drawn. if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState); continue; } // Screenshot is ready to be taken. Everything from here below will continue // through the bottom of the loop and return a value. We only stay in the loop // because we don't want to release the mWindowMap lock until the screenshot is // taken. if (maxLayer == 0) { if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + ": returning null maxLayer=" + maxLayer); return null; } // Constrain frame to the screen size. frame.intersect(0, 0, dw, dh); // Tell surface flinger what part of the image to crop. Take the top // right part of the application, and crop the larger dimension to fit. Rect crop = new Rect(frame); if (width / (float) frame.width() < height / (float) frame.height()) { int cropWidth = (int)((float)width / (float)height * frame.height()); crop.right = crop.left + cropWidth; } else { int cropHeight = (int)((float)height / (float)width * frame.width()); crop.bottom = crop.top + cropHeight; } // The screenshot API does not apply the current screen rotation. rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; } // Surfaceflinger is not aware of orientation, so convert our logical // crop to surfaceflinger's portrait orientation. convertCropForSurfaceFlinger(crop, rot, dw, dh); if (DEBUG_SCREENSHOT) { Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to " + maxLayer + " appToken=" + appToken); for (int i = 0; i < windows.size(); i++) { WindowState win = windows.get(i); Slog.i(TAG, win + ": " + win.mLayer + " animLayer=" + win.mWinAnimator.mAnimLayer + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer); } } ScreenRotationAnimation screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); final boolean inRotation = screenRotationAnimation != null && screenRotationAnimation.isAnimating(); if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG, "Taking screenshot while rotating"); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmScreenshot"); bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer, inRotation, rot); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); if (bm == null) { Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh + ") to layer " + maxLayer); return null; } } break; } if (DEBUG_SCREENSHOT) { // TEST IF IT's ALL BLACK int[] buffer = new int[bm.getWidth() * bm.getHeight()]; bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); boolean allBlack = true; final int firstColor = buffer[0]; for (int i = 0; i < buffer.length; i++) { if (buffer[i] != firstColor) { allBlack = false; break; } } if (allBlack) { Slog.i(TAG, "Screenshot " + appWin + " was monochrome(" + Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + (appWin != null ? appWin.mWinAnimator.mSurfaceLayer : "null") + " minLayer=" + minLayer + " maxLayer=" + maxLayer); } } // Copy the screenshot bitmap to another buffer so that the gralloc backed // bitmap will not have a long lifetime. Gralloc memory can be pinned or // duplicated and might have a higher cost than a skia backed buffer. Bitmap ret = bm.copy(bm.getConfig(),true); bm.recycle(); return ret; }代码长,最主要是就是这句bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer,inRotation, rot);最终是通过截屏获取当前任务管理器的图片
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform, int rotation) { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); return nativeScreenshot(displayToken, sourceCrop, width, height, minLayer, maxLayer, false, useIdentityTransform, rotation); }最后通过c来获取图片。