diff --git a/app/src/main/java/cc/ioctl/hook/msg/SharePicExtHook.java b/app/src/main/java/cc/ioctl/hook/msg/SharePicExtHook.java index 95646e7d58..4d180a54ab 100644 --- a/app/src/main/java/cc/ioctl/hook/msg/SharePicExtHook.java +++ b/app/src/main/java/cc/ioctl/hook/msg/SharePicExtHook.java @@ -22,6 +22,7 @@ package cc.ioctl.hook.msg; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -31,6 +32,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; +import cc.hicore.QApp.QAppUtils; import cc.ioctl.util.HookUtils; import cc.ioctl.util.HostInfo; import cc.ioctl.util.Reflex; @@ -46,6 +48,7 @@ import io.github.qauxv.lifecycle.Parasitics; import io.github.qauxv.ui.ResUtils; import io.github.qauxv.util.Initiator; +import io.github.qauxv.util.IoUtils; import io.github.qauxv.util.Log; import io.github.qauxv.util.SyncUtils; import io.github.qauxv.util.Toasts; @@ -56,8 +59,12 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.net.URLConnection; +import java.util.ArrayList; import java.util.List; +import kotlin.collections.ArraysKt; +import kotlin.jvm.functions.Function3; @FunctionHookEntry @UiItemAgentEntry @@ -140,6 +147,90 @@ protected boolean initOnce() throws Exception { Class kAIOPictureData = Initiator.loadClass("com.tencent.mobileqq.richmediabrowser.model.AIOPictureData"); Constructor ctorAIOPictureModel = kAIOPictureModel.getConstructor(); Method getPictureFile = Reflex.findMethodByTypes_1(kAIOPictureModel, File.class, kAIOPictureData, int.class); + Function3 fnInjectItemToShareSheet = (shareSheet, ctx, file) -> { + try { + Object actionSheet = shareSheet; + if (actionSheet != null) { + if (kShareActionSheetProxy != null && kShareActionSheetProxy.isInstance(actionSheet)) { + actionSheet = fProxyImpl.get(actionSheet); + } + assert actionSheet != null; + if (kShareActionSheetImplV2.isInstance(actionSheet)) { + actionSheet = fImplV2Impl.get(actionSheet); + } + assert actionSheet != null; + // just make sure impl... + kShareActionSheetV2.cast(actionSheet); + if (!mAioPictureViewV2ListenerHooked) { + Object listener = fieldV2Listener.get(actionSheet); + assert listener != null; + Class clazz = listener.getClass(); + Method m = clazz.getDeclaredMethod(miv2OnItemClick.getName(), miv2OnItemClick.getParameterTypes()); + XposedBridge.hookMethod(m, mItemClickHandler); + mAioPictureViewV2ListenerHooked = true; + } + List[] itemListArray = (List[]) fV2ItemListArray.get(actionSheet); + assert itemListArray != null; + List row2 = itemListArray[1]; + assert row2 != null; + Object item = ctorActionSheetItem.newInstance(); + Parasitics.injectModuleResources(ctx.getResources()); + int drawableId = ResUtils.isInNightMode() ? R.drawable.ic_launch_28dp_night : R.drawable.ic_launch_28dp_light; + Reflex.setInstanceObject(item, "id", int.class, R.id.ShareActionSheet_sharePictureWithExtApp); + Reflex.setInstanceObject(item, "icon", int.class, drawableId); + Reflex.setInstanceObject(item, "label", String.class, "其他应用"); + Reflex.setInstanceObject(item, "argus", String.class, file.getAbsolutePath()); + row2.add(item); + } + return null; + } catch (ReflectiveOperationException e) { + IoUtils.unsafeThrow(e); + return null; + } + }; + if (QAppUtils.isQQnt()) { + Class kNTShareActionManager = Initiator.loadClass("com.tencent.qqnt.aio.gallery.share.NTShareActionManager"); + Field itemsField = Reflex.findSingleField(kNTShareActionManager, ArrayList.class, false); + itemsField.setAccessible(true); + Method maybeShow = ArraysKt.single(kNTShareActionManager.getDeclaredMethods(), m -> { + if (m.getReturnType() != void.class) { + return false; + } + Class[] argt = m.getParameterTypes(); + if (argt.length != 1) { + return false; + } + Class maybeNTShareContext = argt[0]; + return !maybeNTShareContext.isInterface() && Modifier.isFinal(maybeNTShareContext.getModifiers()); + }); + Class kNTShareContext = maybeShow.getParameterTypes()[0]; + Class kRFWLayerItemMediaInfo = Initiator.loadClass("com.tencent.richframework.gallery.bean.RFWLayerItemMediaInfo"); + Field layerItemInfoField = Reflex.findSingleField(kNTShareContext, kRFWLayerItemMediaInfo, false); + layerItemInfoField.setAccessible(true); + Field activityField = Reflex.findSingleField(kNTShareContext, Activity.class, false); + activityField.setAccessible(true); + Field actionSheetField = Reflex.findSingleField(kNTShareActionManager, kiShareActionSheet, false); + actionSheetField.setAccessible(true); + Method getExistSaveOrEditPath = kRFWLayerItemMediaInfo.getDeclaredMethod("getExistSaveOrEditPath"); + HookUtils.hookAfterIfEnabled(this, maybeShow, param -> { + List secondLine = ((ArrayList) itemsField.get(param.thisObject)); + Object shareContext = param.args[0]; + Object layerItemInfo = layerItemInfoField.get(shareContext); + Activity ctx = (Activity) activityField.get(shareContext); + String path = (String) getExistSaveOrEditPath.invoke(layerItemInfo); + if (TextUtils.isEmpty(path)) { + Toasts.error(ctx, "getExistSaveOrEditPath is empty"); + return; + } + File file = new File(path); + if (!file.exists()) { + Toasts.error(ctx, "file not exists"); + return; + } + Object actionSheet = actionSheetField.get(param.thisObject); + fnInjectItemToShareSheet.invoke(actionSheet, ctx, file); + }); + } HookUtils.hookAfterIfEnabled(this, showActionSheetForPic, param -> { Object actionSheet = fieldActionSheet.get(param.thisObject); Context ctx = (Context) contextOfAIOBrowserBaseView.get(param.thisObject); @@ -154,38 +245,7 @@ protected boolean initOnce() throws Exception { return; } assert ctx != null; - if (actionSheet != null) { - if (kShareActionSheetProxy != null && kShareActionSheetProxy.isInstance(actionSheet)) { - actionSheet = fProxyImpl.get(actionSheet); - } - assert actionSheet != null; - if (kShareActionSheetImplV2.isInstance(actionSheet)) { - actionSheet = fImplV2Impl.get(actionSheet); - } - assert actionSheet != null; - // just make sure impl... - kShareActionSheetV2.cast(actionSheet); - if (!mAioPictureViewV2ListenerHooked) { - Object listener = fieldV2Listener.get(actionSheet); - assert listener != null; - Class clazz = listener.getClass(); - Method m = clazz.getDeclaredMethod(miv2OnItemClick.getName(), miv2OnItemClick.getParameterTypes()); - XposedBridge.hookMethod(m, mItemClickHandler); - mAioPictureViewV2ListenerHooked = true; - } - List[] itemListArray = (List[]) fV2ItemListArray.get(actionSheet); - assert itemListArray != null; - List row2 = itemListArray[1]; - assert row2 != null; - Object item = ctorActionSheetItem.newInstance(); - Parasitics.injectModuleResources(ctx.getResources()); - int drawableId = ResUtils.isInNightMode() ? R.drawable.ic_launch_28dp_night : R.drawable.ic_launch_28dp_light; - Reflex.setInstanceObject(item, "id", int.class, R.id.ShareActionSheet_sharePictureWithExtApp); - Reflex.setInstanceObject(item, "icon", int.class, drawableId); - Reflex.setInstanceObject(item, "label", String.class, "其他应用"); - Reflex.setInstanceObject(item, "argus", String.class, picFile.getAbsolutePath()); - row2.add(item); - } + fnInjectItemToShareSheet.invoke(actionSheet, ctx, picFile); }); return true; } @@ -194,14 +254,28 @@ protected boolean initOnce() throws Exception { Object item = param.args[0]; int id = (int) Reflex.getInstanceObject(item, "id", int.class); if (id == R.id.ShareActionSheet_sharePictureWithExtApp) { - Class kAIOPictureView = Initiator.loadClass("com.tencent.mobileqq.richmediabrowser.view.AIOPictureView"); - Class kAIOBrowserBaseView = Initiator.load("com.tencent.mobileqq.richmediabrowser.view.AIOBrowserBaseView"); - if (kAIOBrowserBaseView == null) { - kAIOBrowserBaseView = kAIOPictureView.getSuperclass(); - assert kAIOBrowserBaseView != null; + Context ctx = null; + Class kNTAIOLayerMorePart = Initiator.load("com.tencent.qqnt.aio.gallery.part.NTAIOLayerMorePart"); + if (kNTAIOLayerMorePart != null && kNTAIOLayerMorePart.isInstance(param.thisObject)) { + // NT + Field activityField = Reflex.findSingleField(kNTAIOLayerMorePart, Activity.class, true); + activityField.setAccessible(true); + ctx = (Context) activityField.get(param.thisObject); + } else { + // older + Class kAIOPictureView = Initiator.loadClass("com.tencent.mobileqq.richmediabrowser.view.AIOPictureView"); + Class kAIOBrowserBaseView = Initiator.load("com.tencent.mobileqq.richmediabrowser.view.AIOBrowserBaseView"); + if (kAIOBrowserBaseView == null) { + kAIOBrowserBaseView = kAIOPictureView.getSuperclass(); + assert kAIOBrowserBaseView != null; + } + Field contextOfAIOBrowserBaseView = Reflex.findFirstDeclaredInstanceFieldByType(kAIOBrowserBaseView, Context.class); + ctx = (Context) contextOfAIOBrowserBaseView.get(Reflex.getFirstByType(param.thisObject, kAIOPictureView)); + } + if (ctx == null) { + Toasts.error(null, "unable to get activity"); + return; } - Field contextOfAIOBrowserBaseView = Reflex.findFirstDeclaredInstanceFieldByType(kAIOBrowserBaseView, Context.class); - Context ctx = (Context) contextOfAIOBrowserBaseView.get(Reflex.getFirstByType(param.thisObject, kAIOPictureView)); assert ctx != null; String picPath = (String) Reflex.getInstanceObject(item, "argus", String.class); if (TextUtils.isEmpty(picPath)) { diff --git a/app/src/main/java/cc/ioctl/util/Reflex.java b/app/src/main/java/cc/ioctl/util/Reflex.java index a3458992e8..9984251507 100644 --- a/app/src/main/java/cc/ioctl/util/Reflex.java +++ b/app/src/main/java/cc/ioctl/util/Reflex.java @@ -1131,6 +1131,29 @@ public static Method findSingleMethod(@NonNull Class clazz, @Nullable Class clazz, @NonNull Class type, boolean withSuper) throws NoSuchFieldException { + Objects.requireNonNull(clazz, "clazz == null"); + Objects.requireNonNull(type, "type == null"); + Class clz = clazz; + Field candidateField = null; + do { + for (Field field : clz.getDeclaredFields()) { + if (field.getType() == type) { + if (candidateField != null) { + throw new NoSuchFieldException("Multiple fields of type " + type.getName() + " found in " + clazz.getName()); + } else { + candidateField = field; + } + } + } + } while ((candidateField == null && withSuper) && (clz = clz.getSuperclass()) != null); + if (candidateField == null) { + throw new NoSuchFieldException("No field of type " + type.getName() + " found in " + clazz.getName()); + } + return candidateField; + } + /** * Finds a method with the given return type and parameter types. *