diff --git a/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java new file mode 100644 index 000000000..bc4e35245 --- /dev/null +++ b/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java @@ -0,0 +1,62 @@ +package com.tencent.shadow.core.runtime; + +import android.annotation.SuppressLint; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.tencent.shadow.core.runtime.container.PluginContainerActivity; + +@SuppressLint("NewApi") +public class ShadowFragmentSupport { + + public static ShadowActivity fragmentGetActivity(Fragment fragment) { + PluginContainerActivity pluginContainerActivity + = (PluginContainerActivity) fragment.getActivity(); + return (ShadowActivity) PluginActivity.get(pluginContainerActivity); + } + + public static Context fragmentGetContext(Fragment fragment) { + Context context = fragment.getContext(); + if (context instanceof PluginContainerActivity) { + return PluginActivity.get((PluginContainerActivity) context); + } else { + return context; + } + } + + public static Object fragmentGetHost(Fragment fragment) { + Object host = fragment.getHost(); + if (host instanceof PluginContainerActivity) { + return PluginActivity.get((PluginContainerActivity) host); + } else { + return host; + } + } + + public static void fragmentStartActivity(Fragment fragment, Intent intent) { + fragmentStartActivity(fragment, intent, null); + } + + @SuppressLint("NewApi") + public static void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) { + ShadowContext shadowContext = fragmentGetActivity(fragment); + Intent containerActivityIntent + = shadowContext.mPluginComponentLauncher.convertPluginActivityIntent(intent); + if (options == null) { + fragment.startActivity(containerActivityIntent); + } else { + fragment.startActivity(containerActivityIntent, options); + } + } + + public static Context toPluginContext(Context pluginContainerActivity) { + return PluginActivity.get((PluginContainerActivity) pluginContainerActivity); + } + + public static Context toOriginalContext(Context pluginActivity) { + PluginActivity activity = (PluginActivity) pluginActivity; + return activity.hostActivityDelegator.getHostActivity().getImplementActivity(); + } +} diff --git a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt index 5b2368f88..8d89d8f2a 100644 --- a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt +++ b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt @@ -35,6 +35,7 @@ class TransformManager(ctClassInputMap: Map, ActivityTransform(), ServiceTransform(), InstrumentationTransform(), + FragmentSupportTransform(), DialogTransform(), WebViewTransform(), ContentProviderTransform(), diff --git a/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransform.kt b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransform.kt new file mode 100644 index 000000000..9efa31681 --- /dev/null +++ b/projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransform.kt @@ -0,0 +1,463 @@ +/* + * Tencent is pleased to support the open source community by making Tencent Shadow available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.shadow.core.transform.specific + +import com.tencent.shadow.core.transform_kit.CodeConverterExtension +import com.tencent.shadow.core.transform_kit.SpecificTransform +import com.tencent.shadow.core.transform_kit.TransformStep +import javassist.* +import javassist.bytecode.Descriptor + +class FragmentSupportTransform : SpecificTransform() { + companion object { + const val ObjectClassname = "java.lang.Object" + const val ShadowActivityClassname = "com.tencent.shadow.core.runtime.ShadowActivity" + const val AndroidActivityClassname = "android.app.Activity" + const val AndroidFragmentClassname = "android.app.Fragment" + const val AndroidIntentClassname = "android.content.Intent" + const val AndroidBundleClassname = "android.os.Bundle" + const val AndroidContextClassname = "android.content.Context" + const val AndroidAttributeSetClassname = "android.util.AttributeSet" + const val ShadowFragmentSupportClassname = "com.tencent.shadow.core.runtime.ShadowFragmentSupport" + } + + private fun CtClass.isFragment(): Boolean = isClassOf(AndroidFragmentClassname) + + override fun setup(allInputClass: Set) { + val javaObject = mClassPool[ObjectClassname] + val androidActivity = mClassPool[AndroidActivityClassname] + val androidFragment = mClassPool[AndroidFragmentClassname] + val androidIntent = mClassPool[AndroidIntentClassname] + val androidBundle = mClassPool[AndroidBundleClassname] + val androidContext = mClassPool[AndroidContextClassname] + val androidAttributeSet = mClassPool[AndroidAttributeSetClassname] + val shadowActivity = mClassPool[ShadowActivityClassname] + val shadowFragmentSupport = mClassPool[ShadowFragmentSupportClassname] + val getActivityMethod = androidFragment.getDeclaredMethod("getActivity") + val getContextMethod = androidFragment.getDeclaredMethod("getContext") + val getHostMethod = androidFragment.getDeclaredMethod("getHost") + val fragmentGetActivityMethod = shadowFragmentSupport.getMethod("fragmentGetActivity", + Descriptor.ofMethod(shadowActivity, + arrayOf(androidFragment))) + val fragmentGetContextMethod = shadowFragmentSupport.getMethod("fragmentGetContext", + Descriptor.ofMethod(androidContext, + arrayOf(androidFragment))) + val fragmentGetHostMethod = shadowFragmentSupport.getMethod("fragmentGetHost", + Descriptor.ofMethod(javaObject, + arrayOf(androidFragment))) + + mClassPool.importPackage("android.app") + mClassPool.importPackage("android.content") + mClassPool.importPackage("android.util") + mClassPool.importPackage("android.os") + mClassPool.importPackage("com.tencent.shadow.core.runtime") + + //appClass中的Activity都已经被改名为ShadowActivity了.所以要把方法签名也先改一下. + getActivityMethod.methodInfo.descriptor = "()Lcom/tencent/shadow/core/runtime/ShadowActivity;" + + val startActivityMethod1 = androidFragment.getMethod("startActivity", + Descriptor.ofMethod(CtClass.voidType, + arrayOf(androidIntent))) + val fragmentStartActivityMethod1 = shadowFragmentSupport.getMethod("fragmentStartActivity", + Descriptor.ofMethod(CtClass.voidType, + arrayOf(androidFragment, androidIntent))) + val startActivityMethod2 = androidFragment.getMethod("startActivity", + Descriptor.ofMethod(CtClass.voidType, + arrayOf(androidIntent, androidBundle))) + val fragmentStartActivityMethod2 = shadowFragmentSupport.getMethod("fragmentStartActivity", + Descriptor.ofMethod(CtClass.voidType, + arrayOf(androidFragment, androidIntent, androidBundle))) + + /** + * 调用插件Fragment的Activity相关方法时将ContainerActivity转换成ShadowActivity + */ + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = allInputClass + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + val codeConverter = CodeConverterExtension() + codeConverter.redirectMethodCallToStaticMethodCall(getActivityMethod, fragmentGetActivityMethod) + codeConverter.redirectMethodCallToStaticMethodCall(getContextMethod, fragmentGetContextMethod) + codeConverter.redirectMethodCallToStaticMethodCall(getHostMethod, fragmentGetHostMethod) + codeConverter.redirectMethodCallToStaticMethodCall(startActivityMethod1, fragmentStartActivityMethod1) + codeConverter.redirectMethodCallToStaticMethodCall(startActivityMethod2, fragmentStartActivityMethod2) + try { + ctClass.instrument(codeConverter) + } catch (e: Exception) { + System.err.println("处理" + ctClass.name + "时出错:" + e) + throw e + } + } + }) + + fun onAttachSupport() { + //收集哪些Fragment覆盖了onAttach方法 + val overrideOnAttachContextFragments = mutableSetOf() + val overrideOnAttachActivityFragments = mutableSetOf() + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = allInputClass + .filter { it.isFragment() } + .toSet() + + override fun transform(ctClass: CtClass) { + val onAttachContext: CtMethod? = + try { + ctClass.getDeclaredMethod("onAttach", arrayOf(androidContext)) + } catch (e: NotFoundException) { + null + } + val onAttachActivity: CtMethod? = + try { + ctClass.getDeclaredMethod("onAttach", arrayOf(shadowActivity)) + } catch (e: NotFoundException) { + null + } + if (onAttachContext != null) { + overrideOnAttachContextFragments.add(ctClass) + } + if (onAttachActivity != null) { + overrideOnAttachActivityFragments.add(ctClass) + } + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnAttachContextFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + val onAttachContext: CtMethod = ctClass.getDeclaredMethod("onAttach", arrayOf(androidContext)) + //原来的onAttach方法改名为onAttachShadowContext + onAttachContext.name = "onAttachShadowContext" + onAttachContext.modifiers = Modifier.setPrivate(onAttachContext.modifiers) + + //重新定义onAttach方法 + val newOnAttachContext = CtMethod.make(""" + public void onAttach(Context context) { + Context pluginActivity = ShadowFragmentSupport.toPluginContext(context); + onAttachShadowContext(pluginActivity); + } + """.trimIndent(), ctClass) + ctClass.addMethod(newOnAttachContext) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnAttachActivityFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + val onAttachActivity: CtMethod = ctClass.getDeclaredMethod("onAttach", arrayOf(shadowActivity)) + //原来的onAttach方法改名为onAttachShadowActivity + onAttachActivity.name = "onAttachShadowActivity" + onAttachActivity.modifiers = Modifier.setPrivate(onAttachActivity.modifiers) + + //重新定义onAttach方法 + val newOnAttachActivity = CtMethod.make(""" + public void onAttach(Activity activity) { + ShadowActivity shadowActivity = (ShadowActivity)ShadowFragmentSupport.toPluginContext(activity); + onAttachShadowActivity(shadowActivity); + } + """.trimIndent(), ctClass) + ctClass.addMethod(newOnAttachActivity) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnAttachContextFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + //定义将插件Activity还原为ContainerActivity再调用super.onAttach方法的转调方法 + val superOnAttach = CtMethod.make(""" + private void superOnAttach(Context context) { + Context pluginContainerActivity = ShadowFragmentSupport.toOriginalContext(context); + super.onAttach(pluginContainerActivity); + } + """.trimIndent(), ctClass) + + //将插件Fragment中对super.onAttach的调用改调到superOnAttach上 + val codeConverter = CodeConverterExtension() + val superOnAttachContext: CtMethod = ctClass.superclass.getMethod("onAttach", "(Landroid/content/Context;)V") + codeConverter.redirectMethodCall(superOnAttachContext, superOnAttach) + try { + ctClass.instrument(codeConverter) + } catch (e: Exception) { + System.err.println("处理" + ctClass.name + "时出错:" + e) + throw e + } + + /** + * 一定要在super.onAttach转调到superOnAttach之后 + * 再把superOnAttach添加到ctClass上,避免superOnAttach中的 + * super.onAttach也被改成superOnAttach + */ + ctClass.addMethod(superOnAttach) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnAttachActivityFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + //定义将插件Activity还原为ContainerActivity再调用super.onAttach方法的转调方法 + + val superOnAttach = + fixSuperOnAttachCall(ctClass) { + CtMethod.make(""" + private void superOnAttach(ShadowActivity shadowActivity) { + Activity pluginContainerActivity = (Activity)ShadowFragmentSupport.toOriginalContext(shadowActivity); + super.onAttach(pluginContainerActivity); + } + """.trimIndent(), ctClass) + } + + //将插件Fragment中对super.onAttach的调用改调到superOnAttach上 + val codeConverter = CodeConverterExtension() + var superOnAttachActivity: CtMethod = androidFragment.getDeclaredMethod("onAttach", arrayOf(androidActivity)) + superOnAttachActivity = CtNewMethod.copy(superOnAttachActivity, androidFragment, null) + superOnAttachActivity.methodInfo.descriptor = "(Lcom/tencent/shadow/core/runtime/ShadowActivity;)V" + codeConverter.redirectMethodCall(superOnAttachActivity, superOnAttach) + try { + ctClass.instrument(codeConverter) + } catch (e: Exception) { + System.err.println("处理" + ctClass.name + "时出错:" + e) + throw e + } + + /** + * 一定要在super.onAttach转调到superOnAttach之后 + * 再把superOnAttach添加到ctClass上,避免superOnAttach中的 + * super.onAttach也被改成superOnAttach + */ + ctClass.addMethod(superOnAttach) + } + + /** + * Javassist疑似有bug,当super类存在满足签名的方法时,就不会去父类的父类中查找更加准确匹配的方法了。 + * 导致当父类只Override了onAttach(Context)方法时,我们定义的superOnAttach方法中的 + * super.onAttach(Activity)调用会编译成对onAttach(Context)的调用。这与正常的Javac编译结果不一致。 + * + * 因此,在这里如果父类没有定义onAttach(Activity),先为它添加上,make后再移除。 + */ + private fun fixSuperOnAttachCall(ctClass: CtClass, make: () -> CtMethod): CtMethod { + val superclass = ctClass.superclass + val needFix = try { + superclass.getDeclaredMethod("onAttach", arrayOf(androidActivity)) + false + } catch (e: NotFoundException) { + true + } + return if (needFix) { + superclass.defrost() + val newOnAttachActivity = CtMethod.make(""" + public void onAttach(Activity activity) {} + """.trimIndent(), superclass) + superclass.addMethod(newOnAttachActivity) + val result = make() + superclass.removeMethod(newOnAttachActivity) + result + } else { + make() + } + } + }) + } + onAttachSupport() + + fun onInflateSupport() { + //收集哪些Fragment覆盖了onInflate方法 + val overrideOnInflateContextFragments = mutableSetOf() + val overrideOnInflateActivityFragments = mutableSetOf() + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = allInputClass + .filter { it.isFragment() } + .toSet() + + override fun transform(ctClass: CtClass) { + val onInflateContext: CtMethod? = + try { + ctClass.getDeclaredMethod("onInflate", arrayOf(androidContext, androidAttributeSet, androidBundle)) + } catch (e: NotFoundException) { + null + } + val onInflateActivity: CtMethod? = + try { + ctClass.getDeclaredMethod("onInflate", arrayOf(shadowActivity, androidAttributeSet, androidBundle)) + } catch (e: NotFoundException) { + null + } + if (onInflateContext != null) { + overrideOnInflateContextFragments.add(ctClass) + } + if (onInflateActivity != null) { + overrideOnInflateActivityFragments.add(ctClass) + } + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnInflateContextFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + val onInflateContext: CtMethod = ctClass.getDeclaredMethod("onInflate", arrayOf(androidContext, androidAttributeSet, androidBundle)) + //原来的onInflate方法改名为onInflateShadowContext + onInflateContext.name = "onInflateShadowContext" + onInflateContext.modifiers = Modifier.setPrivate(onInflateContext.modifiers) + + //重新定义onInflate方法 + val newOnInflateContext = CtMethod.make(""" + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + Context pluginActivity = ShadowFragmentSupport.toPluginContext(context); + onInflateShadowContext(pluginActivity, attrs, savedInstanceState); + } + """.trimIndent(), ctClass) + ctClass.addMethod(newOnInflateContext) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnInflateActivityFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + val onInflateActivity: CtMethod = ctClass.getDeclaredMethod("onInflate", arrayOf(shadowActivity, androidAttributeSet, androidBundle)) + //原来的onInflate方法改名为onInflateShadowContext + onInflateActivity.name = "onInflateShadowContext" + onInflateActivity.modifiers = Modifier.setPrivate(onInflateActivity.modifiers) + + //重新定义onInflate方法 + val newOnInflateContext = CtMethod.make(""" + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + ShadowActivity shadowActivity = (ShadowActivity)ShadowFragmentSupport.toPluginContext(activity); + onInflateShadowContext(shadowActivity, attrs, savedInstanceState); + } + """.trimIndent(), ctClass) + ctClass.addMethod(newOnInflateContext) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnInflateContextFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + //定义将插件Activity还原为ContainerActivity再调用super.onInflate方法的转调方法 + val superOnInflate = CtMethod.make(""" + private void superOnInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + Context pluginContainerActivity = ShadowFragmentSupport.toOriginalContext(context); + super.onInflate(pluginContainerActivity, attrs, savedInstanceState); + } + """.trimIndent(), ctClass) + + //将插件Fragment中对super.onAttach的调用改调到superOnAttach上 + val codeConverter = CodeConverterExtension() + val superOnInflateContext: CtMethod = ctClass.superclass.getMethod("onInflate", "(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V") + codeConverter.redirectMethodCall(superOnInflateContext, superOnInflate) + try { + ctClass.instrument(codeConverter) + } catch (e: Exception) { + System.err.println("处理" + ctClass.name + "时出错:" + e) + throw e + } + + /** + * 一定要在super.onInflate转调到superOnInflate之后 + * 再把superOnInflate添加到ctClass上,避免superOnInflate中的 + * super.onInflate也被改成superOnInflate + */ + ctClass.addMethod(superOnInflate) + } + }) + + newStep(object : TransformStep { + override fun filter(allInputClass: Set) = overrideOnInflateActivityFragments + + override fun transform(ctClass: CtClass) { + ctClass.defrost() + //定义将插件Activity还原为ContainerActivity再调用super.onInflate方法的转调方法 + + val superOnInflate = + fixSuperOnInflateCall(ctClass) { + CtMethod.make(""" + private void superOnInflate(ShadowActivity shadowActivity, AttributeSet attrs, Bundle savedInstanceState) { + Activity pluginContainerActivity = (Activity)ShadowFragmentSupport.toOriginalContext(shadowActivity); + super.onInflate(pluginContainerActivity, attrs, savedInstanceState); + } + """.trimIndent(), ctClass) + } + + //将插件Fragment中对super.onAttach的调用改调到superOnAttach上 + val codeConverter = CodeConverterExtension() + var superOnInflateActivity: CtMethod = androidFragment.getDeclaredMethod("onInflate", arrayOf(androidActivity, androidAttributeSet, androidBundle)) + superOnInflateActivity = CtNewMethod.copy(superOnInflateActivity, androidFragment, null) + superOnInflateActivity.methodInfo.descriptor = "(Lcom/tencent/shadow/core/runtime/ShadowActivity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V" + codeConverter.redirectMethodCall(superOnInflateActivity, superOnInflate) + try { + ctClass.instrument(codeConverter) + } catch (e: Exception) { + System.err.println("处理" + ctClass.name + "时出错:" + e) + throw e + } + + /** + * 一定要在super.onInflate转调到superOnInflate之后 + * 再把superOnInflate添加到ctClass上,避免superOnInflate中的 + * super.onInflate也被改成superOnInflate + */ + ctClass.addMethod(superOnInflate) + } + + /** + * Javassist疑似有bug,当super类存在满足签名的方法时,就不会去父类的父类中查找更加准确匹配的方法了。 + * 导致当父类只Override了onAttach(Context)方法时,我们定义的superOnAttach方法中的 + * super.onAttach(Activity)调用会编译成对onAttach(Context)的调用。这与正常的Javac编译结果不一致。 + * + * 因此,在这里如果父类没有定义onAttach(Activity),先为它添加上,make后再移除。 + */ + private fun fixSuperOnInflateCall(ctClass: CtClass, make: () -> CtMethod): CtMethod { + val superclass = ctClass.superclass + val needFix = try { + superclass.getDeclaredMethod("onInflate", arrayOf(androidActivity, androidAttributeSet, androidBundle)) + false + } catch (e: NotFoundException) { + true + } + return if (needFix) { + superclass.defrost() + val newOnInflateActivity = CtMethod.make(""" + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {} + """.trimIndent(), superclass) + superclass.addMethod(newOnInflateActivity) + val result = make() + superclass.removeMethod(newOnInflateActivity) + result + } else { + make() + } + } + }) + } + onInflateSupport() + } + +} \ No newline at end of file diff --git a/projects/sdk/core/transform/src/test/java/android/app/Fragment.java b/projects/sdk/core/transform/src/test/java/android/app/Fragment.java new file mode 100644 index 000000000..48e473c9c --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/android/app/Fragment.java @@ -0,0 +1,30 @@ +package android.app; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.tencent.shadow.core.runtime.ShadowActivity; + +/** + * 因为在TransformManager中ActivityTransform是在FragmentSupportTransform之前进行的, + * 所以FragmentSupportTransform运行时,Activity已经都变成ShadowActivity了, + * 所以这个Mock类定义的方法中Activity已经是ShadowActivity了。 + */ +public class Fragment { + final public ShadowActivity getActivity() { + return null; + } + + public void startActivity(Intent intent) { + + } + + public void startActivity(Intent intent, Bundle options) { + + } + + public void onAttach(Context context) { + + } +} diff --git a/projects/sdk/core/transform/src/test/java/android/content/Intent.java b/projects/sdk/core/transform/src/test/java/android/content/Intent.java new file mode 100644 index 000000000..459e57566 --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/android/content/Intent.java @@ -0,0 +1,4 @@ +package android.content; + +public class Intent { +} diff --git a/projects/sdk/core/transform/src/test/java/android/os/Bundle.java b/projects/sdk/core/transform/src/test/java/android/os/Bundle.java new file mode 100644 index 000000000..49ff9ee20 --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/android/os/Bundle.java @@ -0,0 +1,4 @@ +package android.os; + +public class Bundle { +} diff --git a/projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java b/projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java new file mode 100644 index 000000000..df4686ddb --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java @@ -0,0 +1,27 @@ +package com.tencent.shadow.core.runtime; + +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +public class ShadowFragmentSupport { + + public static ShadowActivity fragmentGetActivity(Fragment fragment) { + return null; + } + + public void fragmentStartActivity(Fragment fragment, Intent intent) { + } + + public void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) { + } + + public static Context toPluginContext(Context pluginContainerActivity) { + return null; + } + + public static Context toOriginalContext(Context pluginActivity) { + return null; + } +} diff --git a/projects/sdk/core/transform/src/test/java/test/fragment/TestFragment.java b/projects/sdk/core/transform/src/test/java/test/fragment/TestFragment.java new file mode 100644 index 000000000..7a2901dc7 --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/test/fragment/TestFragment.java @@ -0,0 +1,15 @@ +package test.fragment; + +import android.app.Fragment; +import android.content.Context; + +public class TestFragment extends Fragment { + + @Override + public void onAttach(Context context) { + System.out.println("before super.onAttach" + context); + super.onAttach(context); + System.out.println("after super.onAttach" + context); + } + +} diff --git a/projects/sdk/core/transform/src/test/java/test/fragment/UseFragment.java b/projects/sdk/core/transform/src/test/java/test/fragment/UseFragment.java new file mode 100644 index 000000000..4e61e9fce --- /dev/null +++ b/projects/sdk/core/transform/src/test/java/test/fragment/UseFragment.java @@ -0,0 +1,15 @@ +package test.fragment; + +import android.content.Intent; +import android.os.Bundle; + +import com.tencent.shadow.core.runtime.ShadowActivity; + +public class UseFragment { + + ShadowActivity test(TestFragment fragment) { + fragment.startActivity(new Intent()); + fragment.startActivity(new Intent(), new Bundle()); + return fragment.getActivity(); + } +} diff --git a/projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransformTest.kt b/projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransformTest.kt new file mode 100644 index 000000000..4d7c517ab --- /dev/null +++ b/projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransformTest.kt @@ -0,0 +1,139 @@ +/* + * Tencent is pleased to support the open source community by making Tencent Shadow available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.shadow.core.transform.specific + +import com.tencent.shadow.core.transform_kit.AbstractTransformTest +import javassist.CtClass +import javassist.NotFoundException +import org.junit.Assert +import org.junit.Test + +/** + * ./gradlew -p projects/sdk/core :transform:test --tests com.tencent.shadow.core.transform.specific.FragmentSupportTransformTest + */ +class FragmentSupportTransformTest : AbstractTransformTest() { + + companion object { + const val ShadowFragmentSupportClassName = "com.tencent.shadow.core.runtime.ShadowFragmentSupport" + const val ShadowActivitySig = "Lcom/tencent/shadow/core/runtime/ShadowActivity;" + const val TestFragmentSig = "Ltest/fragment/TestFragment;" + const val FragmentSig = "Landroid/app/Fragment;" + const val IntentSig = "Landroid/content/Intent;" + const val BundleSig = "Landroid/os/Bundle;" + } + + val shadowFragmentSupportClazz = sLoader[ShadowFragmentSupportClassName] + val fragmentGetActivity = shadowFragmentSupportClazz.getMethod("fragmentGetActivity", "($FragmentSig)$ShadowActivitySig") + val fragmentStartActivity1 = shadowFragmentSupportClazz.getMethod("fragmentStartActivity", "($FragmentSig$IntentSig)V") + val fragmentStartActivity2 = shadowFragmentSupportClazz.getMethod("fragmentStartActivity", "($FragmentSig$IntentSig$BundleSig)V") + + private fun transform(clazz: CtClass) { + val transform = FragmentSupportTransform() + transform.mClassPool = sLoader + + val allInputClass = setOf( + clazz, + sLoader["test.fragment.TestFragment"] + ) + transform.setup(allInputClass) + + transform.list.forEach { step -> + step.filter(allInputClass).forEach { + step.transform(it) + it.writeFile(WRITE_FILE_DIR) + } + } + } + + + @Test + fun fragmentGetActivity() { + val name = "test.fragment.UseFragment" + transform(sLoader[name]) + + val transformedClass = dLoader.get(name) + try { + transformedClass.getMethod("test", "($TestFragmentSig)$ShadowActivitySig") + } catch (e: Exception) { + Assert.fail("找不到正确的test方法") + } + + Assert.assertTrue("${fragmentGetActivity}调用应该可以找到", + matchMethodCallInClass(fragmentGetActivity, transformedClass) + ) + } + + @Test + fun fragmentStartActivity() { + val name = "test.fragment.UseFragment" + transform(sLoader[name]) + + val transformedClass = dLoader.get(name) + try { + transformedClass.getMethod("test", "($TestFragmentSig)$ShadowActivitySig") + } catch (e: Exception) { + Assert.fail("找不到正确的test方法") + } + + Assert.assertTrue("${fragmentStartActivity1}调用应该可以找到", + matchMethodCallInClass(fragmentStartActivity1, transformedClass) + ) + Assert.assertTrue("${fragmentStartActivity2}调用应该可以找到", + matchMethodCallInClass(fragmentStartActivity2, transformedClass) + ) + } + + @Test + fun attachContext() { + val name = "test.fragment.TestFragment" + val transform = FragmentSupportTransform() + transform.mClassPool = sLoader + + val allInputClass = setOf( + sLoader[name] + ) + transform.setup(allInputClass) + + transform.list.forEach { step -> + step.filter(allInputClass).forEach { + step.transform(it) + it.writeFile(WRITE_FILE_DIR) + } + } + + val transformedClass = dLoader.get(name) + try { + transformedClass.getDeclaredMethod("onAttach") + } catch (e: NotFoundException) { + Assert.fail("找不到onAttach") + } + + try { + transformedClass.getDeclaredMethod("onAttachShadowContext") + } catch (e: NotFoundException) { + Assert.fail("找不到onAttachShadowContext") + } + + try { + transformedClass.getDeclaredMethod("superOnAttach") + } catch (e: NotFoundException) { + Assert.fail("找不到superOnAttach") + } + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/CommonFragmentSupportTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/CommonFragmentSupportTest.java new file mode 100644 index 000000000..71baafa25 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/CommonFragmentSupportTest.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Tencent Shadow available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.shadow.test.cases.plugin_main.fragment_support; + +import android.content.Intent; +import android.os.Build; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.matcher.ViewMatchers; + +import com.tencent.shadow.test.cases.plugin_main.PluginMainAppTest; + +import org.hamcrest.Matchers; +import org.junit.Assume; +import org.junit.Test; + +abstract class CommonFragmentSupportTest extends PluginMainAppTest { + + abstract protected String getActivityName(); + + abstract protected String expectMsg(); + + abstract protected String fragmentType(); + + @Override + protected Intent getLaunchIntent() { + Intent pluginIntent = new Intent(); + String packageName = ApplicationProvider.getApplicationContext().getPackageName(); + pluginIntent.setClassName( + packageName, + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName() + ); + pluginIntent.putExtra("FragmentType", fragmentType()); + return pluginIntent; + } + + @Test + public void setArguments() { + matchTextWithViewTag("TestFragmentTextView", expectMsg()); + } + + @Test + public void fragmentStartActivity() { + Espresso.onView(ViewMatchers.withTagValue(Matchers.is("fragmentStartActivity"))).perform(ViewActions.click()); + matchTextWithViewTag("finish_button", "finish"); + } + + @Test + public void fragmentStartActivityWithOptions() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + Espresso.onView(ViewMatchers.withTagValue(Matchers.is("fragmentStartActivityWithOptions"))).perform(ViewActions.click()); + matchTextWithViewTag("finish_button", "finish"); + } + + @Test + public void attachContext() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + matchTextWithViewTag("AttachContextView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } + + @Test + public void attachActivity() { + matchTextWithViewTag("AttachActivityView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } + + @Test + public void getActivity() { + matchTextWithViewTag("GetActivityView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } + + @Test + public void getContext() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + matchTextWithViewTag("GetContextView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } + + @Test + public void getHost() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + matchTextWithViewTag("GetHostView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/FragmentSupportTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/FragmentSupportTest.java new file mode 100644 index 000000000..bc1a231e5 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/FragmentSupportTest.java @@ -0,0 +1,19 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ProgrammaticallyAddNormalFragmentTest.class, + ProgrammaticallyAddSubFragmentTest.class, + ProgrammaticallyAddBaseFragmentTest.class, + ProgrammaticallyAddDialogFragmentTest.class, + ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.class, + ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.class, + XmlAddNormalFragmentTest.class, + XmlAddSubFragmentTest.class, + XmlAddBaseFragmentTest.class +}) +public class FragmentSupportTest { +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddBaseFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddBaseFragmentTest.java new file mode 100644 index 000000000..82a76713c --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddBaseFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddBaseFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "TestBaseFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddDialogFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddDialogFragmentTest.java new file mode 100644 index 000000000..3118a0c57 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddDialogFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddDialogFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "TestDialogFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddFragmentTest.java new file mode 100644 index 000000000..e32d0d7b4 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddFragmentTest.java @@ -0,0 +1,13 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +abstract class ProgrammaticallyAddFragmentTest extends CommonFragmentSupportTest { + @Override + protected String getActivityName() { + return "ProgrammaticallyAddFragmentActivity"; + } + + @Override + protected String expectMsg() { + return "addFragmentProgrammatically"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddNormalFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddNormalFragmentTest.java new file mode 100644 index 000000000..184a4cd14 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddNormalFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddNormalFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "TestNormalFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.java new file mode 100644 index 000000000..b4f96809a --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "OnlyOverrideActivityMethodBaseFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.java new file mode 100644 index 000000000..982f2d5f8 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "OnlyOverrideContextMethodBaseFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddSubFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddSubFragmentTest.java new file mode 100644 index 000000000..8d986cff9 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddSubFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class ProgrammaticallyAddSubFragmentTest extends ProgrammaticallyAddFragmentTest { + @Override + protected String fragmentType() { + return "TestSubFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddBaseFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddBaseFragmentTest.java new file mode 100644 index 000000000..5ce64c501 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddBaseFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class XmlAddBaseFragmentTest extends XmlAddFragmentTest { + @Override + protected String fragmentType() { + return "TestBaseFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddFragmentTest.java new file mode 100644 index 000000000..1644f2e52 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddFragmentTest.java @@ -0,0 +1,31 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +import android.os.Build; + +import org.junit.Assume; +import org.junit.Test; + +abstract class XmlAddFragmentTest extends CommonFragmentSupportTest { + @Override + protected String getActivityName() { + return "XmlAddFragmentActivity"; + } + + @Override + protected String expectMsg() { + return "addFragmentWithXml"; + } + + @Test + public void inflateContext() { + Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + matchTextWithViewTag("InflateContextView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } + + @Test + public void inflateActivity() { + matchTextWithViewTag("InflateActivityView", + "com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment." + getActivityName()); + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddNormalFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddNormalFragmentTest.java new file mode 100644 index 000000000..ae56b40ba --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddNormalFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class XmlAddNormalFragmentTest extends XmlAddFragmentTest { + @Override + protected String fragmentType() { + return "TestNormalFragment"; + } +} diff --git a/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddSubFragmentTest.java b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddSubFragmentTest.java new file mode 100644 index 000000000..394a958d9 --- /dev/null +++ b/projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddSubFragmentTest.java @@ -0,0 +1,8 @@ +package com.tencent.shadow.test.cases.plugin_main.fragment_support; + +public class XmlAddSubFragmentTest extends XmlAddFragmentTest { + @Override + protected String fragmentType() { + return "TestSubFragment"; + } +} diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/AndroidManifest.xml b/projects/test/plugin/general-cases/general-cases-lib/src/main/AndroidManifest.xml index 48f320e50..f32cd4059 100644 --- a/projects/test/plugin/general-cases/general-cases-lib/src/main/AndroidManifest.xml +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/AndroidManifest.xml @@ -39,9 +39,9 @@ - + - + @@ -75,6 +75,7 @@ + = Build.VERSION_CODES.M) { + addGetContextView(rootView); + addGetHostView(rootView); + } + + return rootView; + } + + private void addTestArgumentsView(LinearLayout rootView) { + TextView textView = rootView.findViewById(R.id.tv_msg); + textView.setTag("TestFragmentTextView"); + Bundle bundle = fragment.getArguments(); + if (bundle != null) { + String msg = bundle.getString("msg"); + if (!TextUtils.isEmpty(msg)) { + textView.setText(msg); + } + } + } + + private void addFragmentStartActivityView(LinearLayout rootView) { + Button fragmentStartActivity = new Button(rootView.getContext()); + fragmentStartActivity.setText("fragmentStartActivity"); + fragmentStartActivity.setTag("fragmentStartActivity"); + fragmentStartActivity.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + fragmentStartActivity(); + } + }); + rootView.addView(fragmentStartActivity); + } + + private void fragmentStartActivity() { + Intent intent = new Intent(fragment.getActivity(), FragmentStartedActivity.class); + fragment.startActivity(intent); + FragmentStartedActivity.sIdlingResource.setIdleState(false); + } + + private void addFragmentStartActivityWithOptionsView(LinearLayout rootView) { + Button fragmentStartActivityWithOptions = new Button(rootView.getContext()); + fragmentStartActivityWithOptions.setText("fragmentStartActivityWithOptions"); + fragmentStartActivityWithOptions.setTag("fragmentStartActivityWithOptions"); + fragmentStartActivityWithOptions.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + fragmentStartActivityWithOptions(); + } + } + }); + rootView.addView(fragmentStartActivityWithOptions); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void fragmentStartActivityWithOptions() { + Intent intent = new Intent(fragment.getActivity(), FragmentStartedActivity.class); + ActivityOptions activityOptions = ActivityOptions.makeBasic(); + fragment.startActivity(intent, activityOptions.toBundle()); + FragmentStartedActivity.sIdlingResource.setIdleState(false); + } + + private void addAttachContextView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("AttachContextView"); + if (attachContext == null) { + textView.setText("attachContext == null"); + } else { + textView.setText(attachContext.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + private void addAttachActivityView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("AttachActivityView"); + if (attachActivity == null) { + textView.setText("attachActivity == null"); + } else { + textView.setText(attachActivity.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + private Context attachContext; + private Activity attachActivity; + + void onAttach(Context context) { + attachContext = context; + } + + void onAttach(Activity activity) { + attachActivity = activity; + } + + void onDetach() { + attachContext = null; + attachActivity = null; + inflateContext = null; + inflateActivity = null; + } + + private void addInflateContextView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("InflateContextView"); + if (inflateContext == null) { + textView.setText("inflateContext == null"); + } else { + textView.setText(inflateContext.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + private void addInflateActivityView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("InflateActivityView"); + if (inflateActivity == null) { + textView.setText("inflateActivity == null"); + } else { + textView.setText(inflateActivity.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + private Context inflateContext; + private Activity inflateActivity; + + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + inflateContext = context; + } + + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + inflateActivity = activity; + } + + private void addGetActivityView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("GetActivityView"); + Activity activity = fragment.getActivity(); + if (activity == null) { + textView.setText("activity == null"); + } else { + textView.setText(activity.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void addGetContextView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("GetContextView"); + Context context = fragment.getContext(); + if (context == null) { + textView.setText("context == null"); + } else { + textView.setText(context.getClass().getCanonicalName()); + } + rootView.addView(textView); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void addGetHostView(LinearLayout rootView) { + TextView textView = new TextView(rootView.getContext()); + textView.setTag("GetHostView"); + Object host = fragment.getHost(); + if (host == null) { + textView.setText("host == null"); + } else { + textView.setText(host.getClass().getCanonicalName()); + } + rootView.addView(textView); + } +} diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestNormalFragment.java b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestNormalFragment.java new file mode 100644 index 000000000..617d8764e --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestNormalFragment.java @@ -0,0 +1,94 @@ +/* + * Tencent is pleased to support the open source community by making Tencent Shadow available. + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.espresso.IdlingRegistry; + +@SuppressWarnings("deprecation") +public class TestNormalFragment extends Fragment implements TestFragment { + + public TestNormalFragment() { + setArguments(new Bundle());//低版本系统上不允许attach后再setArguments + } + + final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this); + + public void setTestArguments(String msg) { + commonLogic.setTestArguments(msg); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return commonLogic.onCreateView(inflater, container, savedInstanceState); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + commonLogic.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + commonLogic.onAttach(activity); + } + + @Override + public void onDetach() { + commonLogic.onDetach(); + super.onDetach(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource); + } + + @Override + public void onDestroy() { + IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource); + super.onDestroy(); + } + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + commonLogic.onInflate(context, attrs, savedInstanceState); + } + + @Override + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(activity, attrs, savedInstanceState); + commonLogic.onInflate(activity, attrs, savedInstanceState); + } +} diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubFragment.java b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubFragment.java new file mode 100644 index 000000000..a3b01c99f --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubFragment.java @@ -0,0 +1,73 @@ +package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.espresso.IdlingRegistry; + +public class TestSubFragment extends BaseFragment implements TestFragment { + public TestSubFragment() { + setArguments(new Bundle());//低版本系统上不允许attach后再setArguments + } + + final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this); + + public void setTestArguments(String msg) { + commonLogic.setTestArguments(msg); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return commonLogic.onCreateView(inflater, container, savedInstanceState); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + commonLogic.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + commonLogic.onAttach(activity); + } + + @Override + public void onDetach() { + commonLogic.onDetach(); + super.onDetach(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource); + } + + @Override + public void onDestroy() { + IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource); + super.onDestroy(); + } + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + commonLogic.onInflate(context, attrs, savedInstanceState); + } + + @Override + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(activity, attrs, savedInstanceState); + commonLogic.onInflate(activity, attrs, savedInstanceState); + } +} \ No newline at end of file diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachActivityFragment.java b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachActivityFragment.java new file mode 100644 index 000000000..235394b3f --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachActivityFragment.java @@ -0,0 +1,73 @@ +package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.espresso.IdlingRegistry; + +public class TestSubOnlyOverrideOnAttachActivityFragment extends OnlyOverrideActivityMethodBaseFragment implements TestFragment { + public TestSubOnlyOverrideOnAttachActivityFragment() { + setArguments(new Bundle());//低版本系统上不允许attach后再setArguments + } + + final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this); + + public void setTestArguments(String msg) { + commonLogic.setTestArguments(msg); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return commonLogic.onCreateView(inflater, container, savedInstanceState); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + commonLogic.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + commonLogic.onAttach(activity); + } + + @Override + public void onDetach() { + commonLogic.onDetach(); + super.onDetach(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource); + } + + @Override + public void onDestroy() { + IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource); + super.onDestroy(); + } + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + commonLogic.onInflate(context, attrs, savedInstanceState); + } + + @Override + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(activity, attrs, savedInstanceState); + commonLogic.onInflate(activity, attrs, savedInstanceState); + } +} \ No newline at end of file diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachContextFragment.java b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachContextFragment.java new file mode 100644 index 000000000..525747aab --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachContextFragment.java @@ -0,0 +1,73 @@ +package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.espresso.IdlingRegistry; + +public class TestSubOnlyOverrideOnAttachContextFragment extends OnlyOverrideContextMethodBaseFragment implements TestFragment { + public TestSubOnlyOverrideOnAttachContextFragment() { + setArguments(new Bundle());//低版本系统上不允许attach后再setArguments + } + + final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this); + + public void setTestArguments(String msg) { + commonLogic.setTestArguments(msg); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return commonLogic.onCreateView(inflater, container, savedInstanceState); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + commonLogic.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + commonLogic.onAttach(activity); + } + + @Override + public void onDetach() { + commonLogic.onDetach(); + super.onDetach(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource); + } + + @Override + public void onDestroy() { + IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource); + super.onDestroy(); + } + + @Override + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(context, attrs, savedInstanceState); + commonLogic.onInflate(context, attrs, savedInstanceState); + } + + @Override + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + super.onInflate(activity, attrs, savedInstanceState); + commonLogic.onInflate(activity, attrs, savedInstanceState); + } +} \ No newline at end of file diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestXmlFragmentActivity.java b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/XmlAddFragmentActivity.java similarity index 50% rename from projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestXmlFragmentActivity.java rename to projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/XmlAddFragmentActivity.java index 3db11a8e6..b46300fad 100644 --- a/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestXmlFragmentActivity.java +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/XmlAddFragmentActivity.java @@ -19,17 +19,43 @@ package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment; import android.app.Activity; +import android.app.Fragment; import android.os.Bundle; import android.support.annotation.Nullable; import com.tencent.shadow.test.plugin.general_cases.lib.R; -public class TestXmlFragmentActivity extends Activity { +public class XmlAddFragmentActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.layout_fragment_xml_activity); + String fragmentType = getIntent().getStringExtra("FragmentType"); + int xmlId; + switch (fragmentType) { + case "TestNormalFragment": + xmlId = R.layout.layout_xml_add_normal_fragment_activity; + break; + case "TestSubFragment": + xmlId = R.layout.layout_xml_add_sub_fragment_activity; + break; + case "TestBaseFragment": + xmlId = R.layout.layout_xml_add_base_fragment_activity; + break; + default: + throw new IllegalArgumentException("fragmentType不识别:" + fragmentType); + } + + setContentView(xmlId); + } + + @Override + public void onAttachFragment(Fragment fragment) { + super.onAttachFragment(fragment); + if ("TestFragmentTag".equals(fragment.getTag())) { + TestFragment testFragment = (TestFragment) fragment; + testFragment.setTestArguments("addFragmentWithXml"); + } } } \ No newline at end of file diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_test.xml b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_test.xml index a4c139653..979d2c259 100644 --- a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_test.xml +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_test.xml @@ -17,25 +17,15 @@ ~ --> - - - - - - - - + android:layout_height="wrap_content" /> + diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_base_fragment_activity.xml b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_base_fragment_activity.xml new file mode 100644 index 000000000..53ce1c684 --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_base_fragment_activity.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_xml_activity.xml b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_normal_fragment_activity.xml similarity index 93% rename from projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_xml_activity.xml rename to projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_normal_fragment_activity.xml index ba63e47dd..87c41afb8 100644 --- a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_fragment_xml_activity.xml +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_normal_fragment_activity.xml @@ -26,7 +26,8 @@ diff --git a/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_sub_fragment_activity.xml b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_sub_fragment_activity.xml new file mode 100644 index 000000000..11dbe4653 --- /dev/null +++ b/projects/test/plugin/general-cases/general-cases-lib/src/main/res/layout/layout_xml_add_sub_fragment_activity.xml @@ -0,0 +1,33 @@ + + + + + + + +