blob: b5d964e5276d12f096caa9d420bfc7eea1e1eb4b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.weex.util;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static android.graphics.Bitmap.Config.ARGB_8888;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
/**
* Utility class to take screenshots of activity screen
* With reference to open-source project Falcon
*/
public final class Falcon {
//region Constants
private static final String TAG = "Falcon";
//endregion
//region Public API
/**
* Takes screenshot of provided activity and saves it to provided file.
* File content will be overwritten if there is already some content.
*
* @param activity Activity of which the screenshot will be taken.
* @param toFile File where the screenshot will be saved.
* If there is some content it will be overwritten
* @throws UnableToTakeScreenshotException When there is unexpected error during taking screenshot
*/
public static void takeScreenshot(Activity activity, File toFile) {
if (activity == null) {
throw new IllegalArgumentException("Parameter activity cannot be null.");
}
if (toFile == null) {
throw new IllegalArgumentException("Parameter toFile cannot be null.");
}
Bitmap bitmap = null;
try {
if (!toFile.exists()) {
toFile.createNewFile();
}
bitmap = takeBitmapUnchecked(activity);
writeBitmap(bitmap, toFile);
}
catch (Exception e) {
String message = "Unable to take screenshot to file " + toFile.getAbsolutePath()
+ " of activity " + activity.getClass().getName();
Log.e(TAG, message, e);
throw new UnableToTakeScreenshotException(message, e);
}
finally {
if (bitmap != null) {
bitmap.recycle();
}
}
Log.d(TAG, "Screenshot captured to " + toFile.getAbsolutePath());
}
/**
* Takes screenshot of provided activity and puts it into bitmap.
*
* @param activity Activity of which the screenshot will be taken.
* @return Bitmap of what is displayed in activity.
* @throws UnableToTakeScreenshotException When there is unexpected error during taking screenshot
*/
public static Bitmap takeScreenshotBitmap(Activity activity) {
if (activity == null) {
throw new IllegalArgumentException("Parameter activity cannot be null.");
}
try {
return takeBitmapUnchecked(activity);
}
catch (Exception e) {
String message = "Unable to take screenshot to bitmap of activity "
+ activity.getClass().getName();
Log.e(TAG, message, e);
throw new UnableToTakeScreenshotException(message, e);
}
}
//endregion
//region Methods
private static Bitmap takeBitmapUnchecked(Activity activity) throws InterruptedException {
final List<ViewRootData> viewRoots = getRootViews(activity);
int statusBarHeight = ScreenShot.getStatusBarHeight1(activity);
int actionBarHeight = ScreenShot.getActionBarHeight(activity);
View main = activity.getWindow().getDecorView();
int mainWidth = main.getWidth();
int mainHeight = main.getHeight();
int baseWidth = 750;
float widthScale = ((float) baseWidth) / mainWidth;
// 新建立矩阵 按照宽度缩放因子自适应缩放
Matrix matrix = new Matrix();
matrix.postScale(widthScale, widthScale);
Bitmap bitmap1 = Bitmap.createBitmap(main.getWidth(), main.getHeight(), ARGB_8888);
final Bitmap bitmap = Bitmap.createBitmap(bitmap1, 0, statusBarHeight + actionBarHeight,
mainWidth, mainHeight - statusBarHeight - actionBarHeight, matrix, true);
// We need to do it in main thread
if (Looper.myLooper() == Looper.getMainLooper()) {
drawRootsToBitmap(viewRoots, bitmap);
} else {
final CountDownLatch latch = new CountDownLatch(1);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
drawRootsToBitmap(viewRoots, bitmap);
}
finally {
latch.countDown();
}
}
});
latch.await();
}
return bitmap;
}
private static void drawRootsToBitmap(List<ViewRootData> viewRoots, Bitmap bitmap) {
for (ViewRootData rootData : viewRoots) {
drawRootToBitmap(rootData, bitmap);
}
}
private static void drawRootToBitmap(ViewRootData config, Bitmap bitmap) {
// now only dim supported
if ((config._layoutParams.flags & FLAG_DIM_BEHIND) == FLAG_DIM_BEHIND) {
Canvas dimCanvas = new Canvas(bitmap);
int alpha = (int) (255 * config._layoutParams.dimAmount);
dimCanvas.drawARGB(alpha, 0, 0, 0);
}
Canvas canvas = new Canvas(bitmap);
canvas.translate(config._winFrame.left, config._winFrame.top);
config._view.draw(canvas);
}
private static void writeBitmap(Bitmap bitmap, File toFile) throws IOException {
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(toFile));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
}
finally {
if (outputStream != null) {
outputStream.close();
}
}
}
@SuppressWarnings("unchecked") // no way to check
private static List<ViewRootData> getRootViews(Activity activity) {
List<ViewRootData> rootViews = new ArrayList<>();
Object globalWindowManager;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
globalWindowManager = getFieldValue("mWindowManager", activity.getWindowManager());
} else {
globalWindowManager = getFieldValue("mGlobal", activity.getWindowManager());
}
Object rootObjects = getFieldValue("mRoots", globalWindowManager);
Object paramsObject = getFieldValue("mParams", globalWindowManager);
Object[] roots;
LayoutParams[] params;
// There was a change to ArrayList implementation in 4.4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
roots = ((List) rootObjects).toArray();
List<LayoutParams> paramsList = (List<LayoutParams>) paramsObject;
params = paramsList.toArray(new LayoutParams[paramsList.size()]);
} else {
roots = (Object[]) rootObjects;
params = (LayoutParams[]) paramsObject;
}
for (int i = 0; i < roots.length; i++) {
Object root = roots[i];
Object attachInfo = getFieldValue("mAttachInfo", root);
int top = (int) getFieldValue("mWindowTop", attachInfo);
int left = (int) getFieldValue("mWindowLeft", attachInfo);
Rect winFrame = (Rect) getFieldValue("mWinFrame", root);
Rect area = new Rect(left, top, left + winFrame.width(), top + winFrame.height());
View view = (View) getFieldValue("mView", root);
rootViews.add(new ViewRootData(view, area, params[i]));
}
return rootViews;
}
private static Object getFieldValue(String fieldName, Object target) {
try {
return getFieldValueUnchecked(fieldName, target);
}
catch (Exception e) {
throw new UnableToTakeScreenshotException(e);
}
}
private static Object getFieldValueUnchecked(String fieldName, Object target)
throws NoSuchFieldException, IllegalAccessException {
Field field = findField(fieldName, target.getClass());
field.setAccessible(true);
return field.get(target);
}
private static Field findField(String name, Class clazz) throws NoSuchFieldException {
Class currentClass = clazz;
while (currentClass != Object.class) {
for (Field field : currentClass.getDeclaredFields()) {
if (name.equals(field.getName())) {
return field;
}
}
currentClass = currentClass.getSuperclass();
}
throw new NoSuchFieldException("Field " + name + " not found for class " + clazz);
}
//endregion
//region Constructors
// No instances
private Falcon() {
}
//endregion
//region Nested classes
/**
* Custom exception thrown if there is some exception thrown during
* screenshot capturing to enable better client code exception handling.
*/
public static class UnableToTakeScreenshotException extends RuntimeException {
private UnableToTakeScreenshotException(String detailMessage, Exception exception) {
super(detailMessage, extractException(exception));
}
private UnableToTakeScreenshotException(Exception ex) {
super(extractException(ex));
}
/**
* Method to avoid multiple wrapping. If there is already our exception,
* just wrap the cause again
*/
private static Throwable extractException(Exception ex) {
if (ex instanceof UnableToTakeScreenshotException) {
return ex.getCause();
}
return ex;
}
}
private static class ViewRootData {
private final View _view;
private final Rect _winFrame;
private final LayoutParams _layoutParams;
public ViewRootData(View view, Rect winFrame, LayoutParams layoutParams) {
_view = view;
_winFrame = winFrame;
_layoutParams = layoutParams;
}
}
//endregion
}