How to start a non existent Activity mentioned in Manifest? - android

I am attempting to develop a "Dynamic" Android application.
Dynamic in the sense that I have an activity listed in the manifest that is "built" at runtime.
I can build the required activity fine, however, when I attempt to start it my application fails with...
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.research.ps/com.research.Dynamic}: java.lang.ClassNotFoundException:
Didn't find class "com.research.Dynamic" on path: DexPathList[[zip file "/data/app/com.research.ps-1/base.apk"],nativeLibraryDirectories=[/data/app/com.research.ps-1/lib/arm,
/data/app/com.research.ps-1/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]
Is there an approach I can take to successfully instantiate an Android Activity at runtime?
Is there a way I can add a "temporary" or "shell" activity onto my application path? and then replace the "temporary" activity with my dynamic instance?
UPDATE
My Manifest XML contains this entry
<activity
android:name=".Dynamic"
android:label="#string/title_activity_dynamic"
android:theme="#style/AppTheme.NoActionBar" />
However, there is no Activity called "Dynamic" contained within my application.
I am using ByteBuddy to build my dynamic activity:-
final Class<? extends android.support.v7.app.AppCompatActivity> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
.subclass(android.support.v7.app.AppCompatActivity.class, IMITATE_SUPER_CLASS)
.name("com.research.Dynamic")
.make()
.load(getClass().getClassLoader(), new AndroidClassLoadingStrategy.Wrapping(this.getDir("dexgen", Context.MODE_PRIVATE)))
.getLoaded();
final Intent intent = new Intent(this, dynamicType);
startActivity(intent);

Yes you CAN start such an Activity (assuming you have a dummy manifest Activity entry). If you don't like this technique, use Fragments (they don't need entries in the manifest). Alternatively use WebView and JavaScript like Apache Cordova et-al (cross platform too !).
ByteBuddy (kudos too #Rafael Winterhalter author of Byte Buddy) looks cool, maybe a learning curve involved. Why not download the linked project and try both techniques.
Here's how to include ByteBuddy in your Android Studio Gradle project (build.gradle):
android {
compileSdkVersion 25
buildToolsVersion '25'
dependencies {
compile 'com.android.support:appcompat-v7:25'
compile 'net.bytebuddy:byte-buddy:1.7.9'
compile 'net.bytebuddy:byte-buddy-android:1.7.9'
}
}
how I can "find" my dynamically instantiate class at runtime?
External loading of DEX files (class byte code)
See my answer here and follow the links for source code and tutorial (Apache Ant {Eclipse compatible, build.xml} and Android Studio Gradle examples build.gradle of the same code, you need some custom build steps which these project's provide).
Code snippet:
// Internal storage where the DexClassLoader writes the optimized dex file to.
final File optimizedDexOutputPath = getDir(SECONDARY_DEX_INTERNAL_DIR, Context.MODE_PRIVATE);
// Initialize the class loader with the secondary dex file.
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
getClassLoader());
Class libProviderClazz = null;//variable libProviderClazz of type Class
try {
// Load the library class from the class loader.
libProviderClazz = cl.loadClass(PROVIDER_CLASS);
// Cast the return object to the library interface so that the
// caller can directly invoke methods in the interface.
// Alternatively, the caller can invoke methods through reflection,
// which is more verbose and slow.
LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
}
catch (Exception exception)
{
// Handle exception gracefully here.
exception.printStackTrace();
}
Q: How do I add an Activity, I cannot add it to the manifest ? A: Use
Fragments, they don't need entries in the manifest.

I have managed to call a dynamically instantiated Activity and set the required layout content using ByteBuddy.
Heres how
final DynamicType.Unloaded<? extends AppCompatActivity> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
.subclass(AppCompatActivity.class)
.name(CLASS_NAME)
.method(named("onCreate").and(takesArguments(1)))
.intercept(MethodDelegation.to(TargetActivity.class).andThen(SuperMethodCall.INSTANCE))
.make();
final Class<? extends AppCompatActivity> dynamicTypeClass = dynamicType.load(getClassLoader(), new AndroidClassLoadingStrategy.Injecting(this.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();
final Intent intent = new Intent(this, dynamicTypeClass);
startActivity(intent);
The method delegation class
public class TargetActivity {
public static void intercept(Bundle savedInstanceState, #This AppCompatActivity thiz) {
thiz.setContentView(R.layout.activity_fourth);
}
}
Even though this gives the desired result it still has issues as the super.onCreate(savedInstanceState) call is made after I have called setContent (I think).
Using the excellent ByteBuddy library is a much better approach IMHO than working with DEX manipulation.

Related

Trouble instantiating gdx-pay purchaseManager in my game

I am trying to set up gdx-pay in my game. I have followed the official readme (https://github.com/libgdx/gdx-pay/), but can't seem to instantiate the PurchaseManager in my AndroidLauncher.
I have set up a class that handles PurchaseManager installation, but every time I try to instantiate it in the AndroidLauncher I get this error:
"java.lang.RuntimeException: Unable to start activity ComponentInfo{appPackage.AndroidLauncher}: java.lang.IllegalArgumentException: Support for pending purchases must be enabled. Enable this by calling 'enablePendingPurchases()' on BillingClientBuilder."
The readme says to add this to the AndroidLauncher onCreate
game.purchaseManager = new PurchaseManagerGoogleBilling(this);
The problem is that I have to add this
ProjectName.purchaseManager = new PurchaseManagerGoogleBilling(this);
This give me an error saying that I have to make purchaseManager static in my ProjectName class
When I do this, I get the RuntimeException mentioned above.
The sample gdx-pay project instantiates PurchaseManager with this code
public class AndroidLauncher extends GenericAndroidLauncher {
#Override
protected void initFlavor(GdxPayApp game) {
super.initFlavor(game);
game.purchaseManager = new PurchaseManagerGoogleBilling(this);
}
}
When I try this, #Override is invalid and initFlavor does not exist. I tried looking for initFlavor in the gdx-pay files, but had no luck finding anything...
Thanks for taking the time to help
Well it turns out that the following line in my android gradle was the issue
implementation 'com.android.billingclient:billing:2.0.1'
Gdx-pay takes care of this automatically and uses client 1.1
Setting the billingclient to 2.0.1 was the problem. I was able to fix it by deleting that line. Big thanks to the libGdx discord guys!

Error searching for native library (libjnidispatch.so) using JNA to access .so file

Please note that this question is a follow-up question to another question I asked here some time ago.
What would I like to achieve?
I would like to use JNA (version 4.5.2 with Android Studio) to achieve the following:
First I would like to get a list of all callable functions from a given .so file.
Once I have this list, I would like to call a specific function
What did I do so far?
So far, the code of the file "MainActivita.java" of my test app looks as follows:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String MyLibraryString = MyLibrary.Instance.toString();
}
public interface MyLibrary extends Library
{
MyLibrary Instance = (MyLibrary ) Native.loadLibrary("nameOfMyLibraryWithoutSoExtension", MyLibrary.class);
}
}
What issues am I facing?
I used to get an error message that my library is not found on my Android device but after putting it in the "jniLibs" subfolder my app seems to be able to detect it. Unfortunately, I now get the error message
java.lang.UnsatisfiedLinkError: Native library
(com/sun/jna/android-aarch64/libjnidispatch.so) not found in resource
path (.)
whenever I try to start the app on my device. I already tried to extract the file "libjnidispatch.so" from the JNA.jar file and put it in its respective subfolders (as described here) but that doesn't work.
The folder structure of the subfolder "app\src\main\jniLibs" of my app looks as follows:
app\src\main\jniLibs\arm64-v8a\myLibrary.so
...
app\src\main\jniLibs\arm64-v8a\libjnidispatch.so
app\src\main\jniLibs\armeabi\myLibrary.so
...
app\src\main\jniLibs\armeabi\libjnidispatch.so
app\src\main\jniLibs\armeabi-v7a\myLibrary.so
...
app\src\main\jniLibs\armeabi-v7a\libjnidispatch.so
app\src\main\jniLibs\mips\myLibrary.so
...
app\src\main\jniLibs\mips\libjnidispatch.so
app\src\main\jniLibs\mips64\myLibrary.so
...
app\src\main\jniLibs\mips64\libjnidispatch.so
app\src\main\jniLibs\x86\myLibrary.so
...
app\src\main\jniLibs\x86\libjnidispatch.so
app\src\main\jniLibs\x86_64\myLibrary.so
...
app\src\main\jniLibs\x86_64\libjnidispatch.so
Any help would be highly appreciated.

ClassLoader loading android class which extends android class from android support library

I am creating an extendable android application and I need to be able to load class which extends Fragment from different application (apk).
String packageName = "com.something.project";
String className = "com.something.project.TestFragment";
String apkName = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
PathClassLoader classLoader = new PathClassLoader(apkName,ClassLoader.getSystemClassLoader());
Class<?> clazz = classLoader.loadClass(className);
Fragment f = (Fragment)obj;
This was working fine, but when I tried to use Fragment from Android support library(android.support.v4.app.Fragment) to make my application compatible with older Android devices the problem occurred. To be specific I have been getting exception when I try to cast to Fragment (ClassCastException). I know that this is happenning because both Fragment classes are loaded with different ClassLoader, but I was not able to make it work.
So I have finally figured it out. I needed to remove android support library from my plugin application so with help from this article http://android-developers.blogspot.cz/2011/07/custom-class-loading-in-dalvik.html I was able to use ant to compile my plugin application without android support library classes. Then it worked like a charm.

UNITY JNI android plugin GetApplicationContext()

I'm developing a plugin for Unity Android using JNI. I create the .jar file with my Java code, and then .so file with the c++ bridge. Everything works fine when I call my library from Unity side. So, my problem is that in several parts of my Java code, I need to access to the context of the main activity (UnityPlayer3d). I need a pointer to this activity to call the function GetApplicationContext().
How can I obtain this pointer? I think about passing the pointer from Unity to my Java class as a parameter, but first I have to get it in Unity side.
Here's some code to get the activity JNI style:
var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var unityActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
I think your logic is wrong, you don't need to pass Context pointer from Unity(C#) to Java, you can get the Context in Android Pluglin MainActivity(this is the main activity (UnityPlayer3d) you mentioned in your post) directly as follow:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this.getApplicationContext();
}
And then, you can operate the context to do stuff in JAVA intead of writing it in Unity(C#)
Hope it be helpful. :)

Use external application fragment/activity inside application

Is it possible to use a fragment/activity from an external application and use as it is embedded?
For example: embed a PDF reader fragment from a PDF reader application.
May be a little bit late, but still feel that it can be added and might help others.
For activity, there's really no point to have it embedded, there's convenient way to use other apps activities - start it with intent. For fragments at might make sense in case of implementation some kind of 'plug-in' functionality inside the app.
There's an official way to use code from other applications (or load code from network) in Android Blog 'Custom Class Loading in Dalvik'. Please note, the android is not much different from other platforms/environments, so both parts (your app and fragment You want load into your app) should support some kind of contract. That means You cannot load any component from any application, which is quite common and there are number of reasons for it to be that way.
So, here's some small example of the implementation. It consists of 3 parts:
Interfaces project - this project contains definitions of interfaces which should be loaded by main app in order to use external classes:
package com.example.test_interfaces;
import android.app.Fragment;
/**
* Interface of Fragment holder to be obtained from external application
*/
public interface FragmentHolder {
Fragment getFragment();
}
For this example we need only single interface just to demonstrate how to load the fragment.
Plug-in application, which contains the code You need to load - in our case it's a fragment. Please note, that this project in your IDE should depend on Interface one using 'provided' type and without exporting, because it will be imported by main application.
Fragment, we're going to load PlugInFragment:
package com.sandrstar.plugin;
import com.example.test_interfaces.FragmentHolder;
public class PlugInFragment extends Fragment implements FragmentHolder {
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
// Note that loading of resources is not the same as usual, because it loaded actually from another apk
final XmlResourceParser parser = container.getContext().getPackageManager().getXml("com.sandrstar.plugin", R.layout.fragment_layout, null);
return inflater.inflate(parser, container, false);
}
#Override
public Fragment getFragment() {
return this;
}
}
And it's layout fragment_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/black">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is from fragment"
android:textColor="#android:color/white"/>
</LinearLayout>
Main application which wants to load the fragment from another application. It should have Interface project imported:
Activity itself MyActivity:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
try {
Class<?> requiredClass = null;
final String apkPath = getPackageManager().getApplicationInfo("com.sandrstar.plugin",0).sourceDir;
final File dexTemp = getDir("temp_folder", 0);
final String fullName = "com.sandrstar.plugin.PlugInFragment";
boolean isLoaded = true;
// Check if class loaded
try {
requiredClass = Class.forName(fullName);
} catch(ClassNotFoundException e) {
isLoaded = false;
}
if (!isLoaded) {
final DexClassLoader classLoader = new DexClassLoader(apkPath,
dexTemp.getAbsolutePath(),
null,
getApplicationContext().getClassLoader());
requiredClass = classLoader.loadClass(fullName);
}
if (null != requiredClass) {
// Try to cast to required interface to ensure that it's can be cast
final FragmentHolder holder = FragmentHolder.class.cast(requiredClass.newInstance());
if (null != holder) {
final Fragment fragment = holder.getFragment();
if (null != fragment) {
final FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.add(R.id.fragmentPlace, fragment, "MyFragment").commit();
}
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
And it's layout main.xml:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clipChildren="false"
android:id="#+id/root">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/down_image" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/fragmentPlace"
android:layout_centerInParent="true" />
</RelativeLayout>
And the finally we able to observe the following on the real device:
Possible issues handling (thanks to #MikeMiller for the update):
If you get the following error in the call to classLoader.loadClass:
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
Make sure the fragment modules are included in the main app (as 'compiled')
If you get a NameNotFoundException in the call to context.getPackageManager().getApplicationInfo(packageName,0).sourceDir, then make sure the fragment is in an installed APPLICATION (not just a library dependency). Follow the steps below to make sure that's the case:
1) In the main application's build.gradle, change apply plugin: 'android-library' to apply plugin: 'android' and make sure there's a dummy activity java file. In the main application, remove the dependency on the fragment module (It's not specified in step 3, but I had to add a dependency on the fragment module to the main application. But the fragment module is now an activity application, and you can't have dependencies on those) or you'll get this: Error:Dependency unspecified on project resolves to an APK archive which is not supported as a compilation dependency.
2) Run the fragment module (which you can do now, because it's an activity application). That installs it in a way that the getApplicationInfo call can find it Revert build.gradle and add the dependency back in the main app (as a 'compile' dependency) Everything should work now. When you make updates to the fragment code, you won't need to go through this process again. You will, though, if you want to run on a new device or if you add a new fragment module. I hope this is able to save someone the time I spent trying to resolve the above errors.
Android L
Seems, based on normal multidex support with Android L, above steps are not needed, because class loading is different. Approach, described in multidex support can be used instead of Android Blog 'Custom Class Loading in Dalvik', because it clearly states that:
Note: The guidance provided in this document supersedes the guidance
given in the Android Developers blog post Custom Class Loading in
Dalvik.
Probably, changes in android.support.multidex might be needed to reuse that approach.
No, you can not "reuse" code from other applications. The only official way is to use Intent to invoke the whole Activity.
I use a quite similar approach to sandrstart. Maybe it's less secure.
I use a normal Classloader derived from a packagecontext created with the plugin package name. The package names of all plugins are loaded and saved along with other configurations from a config website.
Context ctx = createPackageContext(packetName, Context.CONTEXT_INCLUDE_CODE |
Context.CONTEXT_IGNORE_SECURITY);
ClassLoader cl = ctx.getClassLoader();
Class<?> c = cl.loadClass(className);
Fragment fragObj = (Fragment)c.newInstance();
But I wanted to stress that my approach and I think sandrstar's approach only work with the android internal android.app.Fragment class.
I was trying (again) as part for adopting to android 9 to switch from android.app.Fragment (depricated) to android.support.v4.Fragment but could'nt get it to work.
The reason is that:
apk1:framework.class.A == apk2.framework.class.A but
apk1:someJarOrAar.class.B != aps2.someJarOrAar.class.B
even if the same exact jar/aar is used in both projects.
So I will always get a ClassCastException on (Fragment)c.newInstance();.
I have tried to cast via the classloader from the plugin and cast via classloader from main package but to no avail. I think there is no way around it as it can't be guaranteed that jars/aars are really the same even if they have same names and same classnames in them so it's a security issue to treat them as different even if they are the same.
I surely hope for some workaround (other than keep on using the android.app.Fragment even under android 9 as I will do for now) but I will also be grateful for comments for why it is definetly not possible with shared classes (such as support.v4.Fragment).

Categories

Resources