| /* |
| * 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 |
| } |