/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.inspector.screencast;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.view.View;
import com.facebook.stetho.common.LogUtil;
import com.facebook.stetho.inspector.elements.android.ActivityTracker;
import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;
import com.facebook.stetho.inspector.protocol.module.Page;
import java.io.ByteArrayOutputStream;
public final class ScreencastDispatcher {
private static final long FRAME_DELAY = 200l;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final BitmapFetchRunnable mBitmapFetchRunnable = new BitmapFetchRunnable();
private final ActivityTracker mActivityTracker = ActivityTracker.get();
private final EventDispatchRunnable mEventDispatchRunnable = new EventDispatchRunnable();
private final RectF mTempSrc = new RectF();
private final RectF mTempDst = new RectF();
private boolean mIsRunning;
private Handler mBackgroundHandler;
private JsonRpcPeer mPeer;
private HandlerThread mHandlerThread;
private Bitmap mBitmap;
private Canvas mCanvas;
private Page.StartScreencastRequest mRequest;
private ByteArrayOutputStream mStream;
private Page.ScreencastFrameEvent mEvent = new Page.ScreencastFrameEvent();
private Page.ScreencastFrameEventMetadata mMetadata = new Page.ScreencastFrameEventMetadata();
public ScreencastDispatcher() {
}
public void startScreencast(JsonRpcPeer peer, Page.StartScreencastRequest request) {
LogUtil.d("Starting screencast");
mRequest = request;
mHandlerThread = new HandlerThread("Screencast Thread");
mHandlerThread.start();
mPeer = peer;
mIsRunning = true;
mStream = new ByteArrayOutputStream();
mBackgroundHandler = new Handler(mHandlerThread.getLooper());
mMainHandler.postDelayed(mBitmapFetchRunnable, FRAME_DELAY);
}
public void stopScreencast() {
LogUtil.d("Stopping screencast");
mBackgroundHandler.post(new CancellationRunnable());
}
private class BitmapFetchRunnable implements Runnable {
@Override
public void run() {
updateScreenBitmap();
mBackgroundHandler.post(mEventDispatchRunnable.withEndAction(this));
}
private void updateScreenBitmap() {
if (!mIsRunning) {
return;
}
Activity activity = mActivityTracker.tryGetTopActivity();
if (activity == null) {
return;
}
// This stuff needs to happen in the UI thread
View rootView = activity.getWindow().getDecorView();
try {
if (mBitmap == null) {
int viewWidth = rootView.getWidth();
int viewHeight = rootView.getHeight();
float scale = Math.min((float) mRequest.maxWidth / (float) viewWidth,
(float) mRequest.maxHeight / (float) viewHeight);
int destWidth = (int) (viewWidth * scale);
int destHeight = (int) (viewHeight * scale);
mBitmap = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.RGB_565);
mCanvas = new Canvas(mBitmap);
Matrix matrix = new Matrix();
mTempSrc.set(0, 0, viewWidth, viewHeight);
mTempDst.set(0, 0, destWidth, destHeight);
matrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);
mCanvas.setMatrix(matrix);
}
rootView.draw(mCanvas);
} catch (OutOfMemoryError e) {
LogUtil.w("Out of memory trying to allocate screencast Bitmap.");
}
}
}
private class EventDispatchRunnable implements Runnable {
private Runnable mEndAction;
private EventDispatchRunnable withEndAction(Runnable endAction) {
mEndAction = endAction;
return this;
}
@Override
public void run() {
if (!mIsRunning || mBitmap == null) {
return;
}
int width = mBitmap.getWidth();
int height = mBitmap.getHeight();
mStream.reset();
Base64OutputStream base64Stream = new Base64OutputStream(mStream, Base64.DEFAULT);
// request format is either "jpeg" or "png"
Bitmap.CompressFormat format = Bitmap.CompressFormat.valueOf(mRequest.format.toUpperCase());
mBitmap.compress(format, mRequest.quality, base64Stream);
mEvent.data = mStream.toString();
mMetadata.pageScaleFactor = 1;
mMetadata.deviceWidth = width;
mMetadata.deviceHeight = height;
mEvent.metadata = mMetadata;
mPeer.invokeMethod("Page.screencastFrame", mEvent, null);
mMainHandler.postDelayed(mEndAction, FRAME_DELAY);
}
}
private class CancellationRunnable implements Runnable {
@Override
public void run() {
mHandlerThread.interrupt();
mMainHandler.removeCallbacks(mBitmapFetchRunnable);
mBackgroundHandler.removeCallbacks(mEventDispatchRunnable);
mIsRunning = false;
mHandlerThread = null;
mBitmap = null;
mCanvas = null;
mStream = null;
}
}
}