在任务管理器中,有截取当前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来获取图片。