package tk.wasdennnoch.androidn_ify.systemui.screenshot;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import tk.wasdennnoch.androidn_ify.XposedHook;
import tk.wasdennnoch.androidn_ify.android.AndroidHooks;
import tk.wasdennnoch.androidn_ify.extracted.systemui.ScreenshotSelectorView;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@SuppressLint("StaticFieldLeak")
public class ScreenshotHooks {
public static final String TAG = "ScreenshotHooks";
private static final String KEY_PARTIAL_SCREENSHOT = "nify_partial_screenshot";
private static int x, y, width, height;
private static Service mService;
public static void hook(final ClassLoader classLoader) {
try {
XposedHelpers.findAndHookConstructor(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.GlobalScreenshot", classLoader, Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
FrameLayout mScreenshotLayout = (FrameLayout) XposedHelpers.getObjectField(param.thisObject, "mScreenshotLayout");
ScreenshotSelectorView mScreenshotSelectorView = new ScreenshotSelectorView(mScreenshotLayout.getContext());
mScreenshotSelectorView.setVisibility(View.GONE);
mScreenshotLayout.addView(mScreenshotSelectorView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mScreenshotSelectorView.setFocusable(true);
mScreenshotSelectorView.setFocusableInTouchMode(true);
XposedHelpers.setAdditionalInstanceField(param.thisObject, "mScreenshotSelectorView", mScreenshotSelectorView);
}
});
XposedHelpers.findAndHookMethod(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.GlobalScreenshot", classLoader, "startAnimation", Runnable.class, int.class, int.class, boolean.class, boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
DisplayMetrics mDisplayMetrics = (DisplayMetrics) XposedHelpers.getObjectField(param.thisObject, "mDisplayMetrics");
Bitmap mScreenBitmap = (Bitmap) XposedHelpers.getObjectField(param.thisObject, "mScreenBitmap");
if (width != 0 && height != 0 && (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels)) {
// Crop the screenshot to selected region
Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);
mScreenBitmap.recycle();
mScreenBitmap = cropped;
}
// reset
x = 0;
y = 0;
width = 0;
height = 0;
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
XposedHelpers.setObjectField(param.thisObject, "mScreenBitmap", mScreenBitmap);
}
});
XposedHelpers.findAndHookMethod(Service.class, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Service service = (Service) param.thisObject;
if (!service.getClass().getName().contains("TakeScreenshotService")) return;
mService = service;
}
});
Class handlerClass;
try {
handlerClass = XposedHelpers.findClass(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.TakeScreenshotService$1", classLoader);
} catch (XposedHelpers.ClassNotFoundError e) { // Some ROMs
handlerClass = XposedHelpers.findClass(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.TakeScreenshotService$ScreenshotHandler", classLoader);
}
XposedHelpers.findAndHookMethod(handlerClass, "handleMessage", Message.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (!shouldTakePartial(mService)) return;
Settings.Global.putInt(mService.getContentResolver(), KEY_PARTIAL_SCREENSHOT, 0);
Message msg = (Message) param.args[0];
final Messenger callback = msg.replyTo;
Runnable finisher = new Runnable() {
@Override
public void run() {
Message reply = Message.obtain(null, 1);
try {
callback.send(reply);
} catch (RemoteException e) {
// ignored
}
}
};
Context service;
try {
service = (Context) XposedHelpers.getObjectField(param.thisObject, "this$0");
} catch (NoSuchFieldError e) { // Some ROMs
service = (Context) XposedHelpers.getObjectField(param.thisObject, "takeScreenshotService");
}
Object mScreenshot = XposedHelpers.getObjectField(service, "mScreenshot");
if (mScreenshot == null) {
mScreenshot = XposedHelpers.newInstance(XposedHelpers.findClass(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.GlobalScreenshot", classLoader), service);
XposedHelpers.setObjectField(service, "mScreenshot", mScreenshot);
}
takeScreenshotPartial(mScreenshot, finisher, msg.arg1 > 0, msg.arg2 > 0);
param.setResult(null);
}
});
/*XposedHelpers.findAndHookMethod(XposedHook.PACKAGE_SYSTEMUI + ".screenshot.TakeScreenshotService", classLoader, "onUnbind", Intent.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object globalScreenshot = XposedHelpers.getObjectField(param.thisObject, "mScreenshot");
if (globalScreenshot != null) {
final WindowManager mWindowManager = (WindowManager) XposedHelpers.getObjectField(globalScreenshot, "mWindowManager");
final FrameLayout mScreenshotLayout = (FrameLayout) XposedHelpers.getObjectField(globalScreenshot, "mScreenshotLayout");
final ScreenshotSelectorView mScreenshotSelectorView = (ScreenshotSelectorView) XposedHelpers.getAdditionalInstanceField(globalScreenshot, "mScreenshotSelectorView");
if (mScreenshotSelectorView.getSelectionRect() != null) {
mWindowManager.removeView(mScreenshotLayout);
mScreenshotSelectorView.stopSelection();
}
}
param.setResult(true);
}
});*/
} catch (Throwable t) {
XposedHook.logE(TAG, "Crash in screenshot hooks", t);
}
}
public static void takePartialScreenshot(Context context) {
Settings.Global.putInt(context.getContentResolver(), KEY_PARTIAL_SCREENSHOT, 1);
AndroidHooks.sendTakeScreenshot(context);
}
private static boolean shouldTakePartial(Context context) {
return Settings.Global.getInt(context.getContentResolver(), KEY_PARTIAL_SCREENSHOT, 0) != 0;
}
private static void takeScreenshotPartial(final Object globalScreenshot, final Runnable finisher, final boolean statusBarVisible, final boolean navBarVisible) {
XposedHook.logI(TAG, "Adding view");
final WindowManager mWindowManager = (WindowManager) XposedHelpers.getObjectField(globalScreenshot, "mWindowManager");
final WindowManager.LayoutParams mWindowLayoutParams = (WindowManager.LayoutParams) XposedHelpers.getObjectField(globalScreenshot, "mWindowLayoutParams");
//noinspection WrongConstant
mWindowLayoutParams.type = TYPE_SYSTEM_ERROR;
final FrameLayout mScreenshotLayout = (FrameLayout) XposedHelpers.getObjectField(globalScreenshot, "mScreenshotLayout");
final ScreenshotSelectorView mScreenshotSelectorView = (ScreenshotSelectorView) XposedHelpers.getAdditionalInstanceField(globalScreenshot, "mScreenshotSelectorView");
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ScreenshotSelectorView view = (ScreenshotSelectorView) v;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
view.startSelection((int) event.getX(), (int) event.getY());
return true;
case MotionEvent.ACTION_MOVE:
view.updateSelection((int) event.getX(), (int) event.getY());
return true;
case MotionEvent.ACTION_UP:
view.setVisibility(View.GONE);
mWindowManager.removeView(mScreenshotLayout);
final Rect rect = view.getSelectionRect();
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
mScreenshotLayout.post(new Runnable() {
public void run() {
x = rect.left;
y = rect.top;
width = rect.width();
height = rect.height();
XposedHelpers.callMethod(globalScreenshot, "takeScreenshot", finisher, statusBarVisible, navBarVisible);
}
});
}
}
view.stopSelection();
return true;
}
return false;
}
});
mScreenshotLayout.post(new Runnable() {
@Override
public void run() {
mScreenshotSelectorView.setVisibility(View.VISIBLE);
mScreenshotSelectorView.requestFocus();
}
});
}
private static boolean windowTypeFieldMatches(int value, String type) {
return value == XposedHelpers.getStaticIntField(WindowManager.LayoutParams.class, type);
}
}