Dynamically load classes from other APK's - android

I am creating a framework to be used by many departments in my work environment. I need a way to dynamically load classes into the framework from individual department apk's. For instance a way to dynamically load department A's content provider class into the framework.
I have had little luck trying to figure this out, any help would be much appreciated. Thanks.

If you want to load an already known class from another .apk currently installed on your device you can take the following approach (assuming your class has a default constructor). Also please remember that you must know the package name of the other .apk file and the other .apk file's package name must be different from your applications package name.
private Object loadClass(String packageName, String className){
Object plugin = null;
try {
PackageManager packageManager = getPackageManager();
ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
DexFile df = new DexFile(appInfo.sourceDir);
ClassLoader cl = getClassLoader();
Class classToInvestigate = df.loadClass(className, cl);
plugin = classToInvestigate.newInstance();
} catch (Exception e) {
System.out.println("EXCEPTION");
}
finally{
return plugin;
}
}

I expect it would be a security hole if this was allowed and your design is risky as you are tightly coupling your program to someone else's.
A better solution is to be able to just use the content provider from your activity, much as you can access the contacts in Android.

Related

How can I make my Android app read and write a .txt file?

I'm using Xamarin, C# and Monogame and I'm taking a fully-working Desktop game and porting it over to Android.
My problem is that I have this "Content folder" that you would always use in the Desktop version of the app. But I cannot access it or any other folder through the code directly using Android.
basicShader = new Effect(game1.GraphicsDevice,System.IO.File.ReadAllBytes("Content/TextureShader.mgfxo"));
This works just fine in the Desktop app but throws System.IO.DirectoryNotFoundException:'Could not find a part of the path "/Content/TextureShader.mgfxo".' on Android.
I'd like to mention that I already had the code and the project working perfectly when it was a desktop program. I also have a private class-level variable string[] list_of_files and in the constructor, I had the line list_of_files = Directory.GetFiles("./Content","*.txt");
This is for saving and loading player data. It may have been rudimentary but I had a fully functioning program that saved and loaded data on my computer. I am transitioning this program to be an Android app and this is the only part of the project that isn't working. When I run the code as it was originally written, I get "System.IO.DirectoryNotFoundException: 'Could not find a part of the path '/Content'.' ".
I've tried playing around with trying to read the contents of different folders.
I've messed around with different paths, including the Resources folder instead.
I added <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> to my manifest.
I know that I'm trying to access internal storage, not external, so I also tried <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> just to see if that might work.
Nothing works.
In another stack overflow post, a guy commented:
For the people who are facing NullPointerException - you are trying to access the files in the app's internal storage which are private sorry you can't do that. –
coderpc
Jun 23, 2017 at 16:00
I cannot imagine why this would be true. Why would a programmer not be able to write a program that can access it's own internal storage? That makes no sense to me. Obviously my app needs to be able to read and write it's own internal storage! And if this is true, then how else can I save persistent data on my phone? I don't want a database or a shared thingamabobber that uses key-value pairs, I have a self-made system that works as a text file and I want to continue to use it. I refuse to believe that an Android app can't keep track of a simple .txt file in one of it's own folders, that's just too hard for me to imagine. It can't be true.
I wanted to ask the commenter about his comment but Stack Overflow wouldn't let me because I don't have over 50xp.
Just like CommonsWare sayed, you can use the Intent.ActionOpenDocument to get the uri of the file. Such as
static readonly int READ_REQUEST_CODE = 1337;
Intent intent = new Intent(Intent.ActionOpenDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("*/*");
StartActivityForResult(intent, READ_REQUEST_CODE);
And override the OnActivityResult method:
if (requestCode == READ_REQUEST_CODE && resultCode == Result.Ok)
{
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (data != null)
{
Android.Net.Uri uri = data.Data;
DocumentFile documentFile = DocumentFile.FromSingleUri(this.ApplicationContext,uri);
// Then you can operate the file with input and output stream
}
}
More information please check the simple on the github:
https://github.com/xamarin/monodroid-samples/blob/main/StorageClient/StorageClientFragment.cs
In addition, if you can ensure the file's path. You can use the StreamWriter and the StreamReader to write and read the file. Such as:
using (StreamWriter sw = new StreamWriter(path))
{
sw.WriteLine(content);
}
Furthermore, you can try to create the content folder and the txt file in the Android with the following code.
var filename1 = Android.App.Application.Context.GetExternalFilesDir(System.DateTime.Now.ToString("Content")).AbsolutePath;
var filename = System.IO.Path.Combine(filename1, "xxx.txt");
using (System.IO.FileStream os = new System.IO.FileStream(filename, System.IO.FileMode.Create))
{
}
The folder and the files created by this way belongs to the app and you can access it easily.
You can read the official document about the storage in the Android.
Link : https://developer.android.com/training/data-storage/shared/documents-files

Creating SQLite database on Internal storage ANDROID

I am trying to create a SQLIte database on Internal Storage in my Xamarin application. I am creating a system for an offline environment where at least 3 applications are inter-connected with shared data. So if one application creates some data the other application should be able to read that.
The Database project is a Portable Class Library which I plan to include in all three applications.
My DbHelper is as follows:
public Database()
{
string folder = "/data/data/Anchor.Main";
_dbPath = System.IO.Path.Combine(folder, "Anchor.db");
try
{
if (!System.IO.Directory.Exists(folder))
{
System.IO.Directory.CreateDirectory(folder); //Getting error here
}
_connection = new SQLiteConnection(_dbPath);
_connection.CreateTable<Orders>();
_connection.CreateTable<Items>();
}
catch(Exception ex)
{ }
}
I get error which says
Access to the path "/data/data/Anchor.Main" is denied.
Following is the stack trace
at System.IO.Directory.CreateDirectoriesInternal (System.String path)
[0x0004b] in <3fd174ff54b146228c505f23cf75ce71>:0 at
System.IO.Directory.CreateDirectory (System.String path) [0x00075] in
<3fd174ff54b146228c505f23cf75ce71>:0 at
anchorapp.db.Helper.Database..ctor () [0x0002e]
I understand this is because of the permissions, but what permissions do I need to set in order to write to the Internal storage from a PCL?
You are accessing Android system folder of "/data/data/Anchor.Main".
Your app internal storage will be in
/data/data/#PACKAGE_NAME#/files
You can use the following code to get the folder to store the database:
// Equal to /data/data/#PACKAGE_NAME#/files
var homePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
_dbPath = System.IO.Path.Combine(homePath, "Anchor.db");
_connection = new SQLiteConnection(_dbPath);
Android and iOS does not work that way. Each platform keeps apps in a sandboxed environment. If you want to create and store data in your app you need to create the path like the following sample:
var docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
_dbPath = System.IO.Path.Combine(docFolder, "Anchor.db");
Also set the permissions ReadExternalStorage and WriteExternalStorage in your Android project
Instead of writing to a private folder, you could create the database in a public one. To do so the docFolder would change to:
var docFolder = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "YourDirectoryName");
Please be advised, that if you go that way everyone can see and open the database.
I did this by creating a sharedId within my projects and using the SharedContext to access the databases created by other apps

Distribute a library separate from apk

I want to use a library in my android app but want this library to be optional to the users.
To make it optional, I want to distribute the library separate from the apk. So that:
Users who intend to use this option can
download the lib from within the app
And those who don't wish to use this get a smaller apk file.
I want to ask, Is it possible to distribute a library separately from the apk?.
I've seen some games downloading files after their installation. So, I feel this might be achievable but don't know how to achieve this.
Note: Please keep in mind the following:
The classes or methods would only be used in case the library has been downloaded by the user and would not be used in the other case.
Do not confuse distribution of library with distribution of resources. So please keep in mind that I want to separately distribute a library and not resources.
I don't think you can use a library as expansion file because if you use any class, methods of library in your project you have to import them, and you can not import them without using library in project, But I am not sure.
Please see below link for more detail.
https://developer.android.com/google/play/expansion-files.html
You can distribute your base functionality with your APK
If the user chooses to, your application can download additional files, including libraries. If they are not expansion files (OBB), then you should take care hosting them.
These additional libraries will have to be loaded in a similar manner:
for (File pluginFile : pluginFiles)
{
try
{
JarFile jarFile = new JarFile(pluginFile);
Enumeration<? extends JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class"))
{
URLClassLoader loader = URLClassLoader.newInstance(new URL[]{pluginFile.toURI().toURL()});
System.out.println(entry.getName());
Class<?> entryClass = loader.loadClass(entry.getName().replace(".class", "").replace("/", "."));
if (isPlugin(entryClass))
{
IPlugin plugin = (IPlugin)entryClass.newInstance();
plugins.add(plugin);
System.out.println("Added plugin \'" + plugin.getPluginId() + "\' from \'" + pluginFile.getName() + "\'");
}
}
}
}
catch (InstantiationException | ClassNotFoundException | IOException | IllegalAccessException e)
{
e.printStackTrace();
}
}
This assumes you want to load JAR files.

Delete data of one application from other in android

Can we clear or delete data of one application from another application in android. If it is not possible simply could you please suggest any tricky way to do this like to go to the folder in internal memory and delete that folder programatically.
Thanks.
Simple answer,
Android is designed that this should not be possible.
But when using root access you can actually delete folders from other applications.
I believe if two applications having a different package, but with the same signature, actually can have access to each others private folders. Or i'm not sure, i believe you could add some kind of declaration to you manifest file allowing other (friend) apps to have access to your private folder. But i'm not sure i should search for it.
Edit after search:
Apps having the same android:sharedUserId and android:sharedUserLabel and signature have access to each others private files.
http://developer.android.com/guide/topics/manifest/manifest-element.html#uid
Two Android applications with the same user ID
Edit 2:
There are some private methods in the android API, witch can be used to clear app data i think. I'm not sure but if you reflect those methods with the right permissions in you manifest file it could be possible to clear app data, but i'm not 100% sure.
Some small example code:
Method clearApplicationUserData = getReflectedMethod("clearApplicationUserData", String.class, IPackageDataObserver.class);
And the method i use the get it reflected...
private Method getReflectedMethod(String methodname, Class<?>... args) {
Method temp = null;
try {
temp = pm.getClass().getMethod(methodname, args);
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
return temp;
}
The IPackageDataObserver class should be copied from the original android source, and added as new class in the source folder of your project under the package android.content.pm.
When you want to clear user data i think you should invoke the method like this:
public void clearApplicationUserData(String packageName) {
if (clearApplicationUserData != null) {
try {
clearApplicationUserData.invoke(pm, packageName, data_helper);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
}
The data_helper is any class extending the IPackageDataObserver.Stub class.
You can find a lot of questions about reflecting methods and stuff here on stackoverflow.
I have no idea if this works but this is the only way i can think of.
Rolf
If its stored in the database you can delete the data using content provider by following method
mRowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI, // the user dictionary content URI
mSelectionClause // the column to select on
mSelectionArgs // the value to compare to
);
follow the methods Here
The question is not very clear.
Do you mean your app will delete data from someone elses app? The answer there is "it depends". First, if the data is on the SD card, you could access it and delete it. If the data is in the apps private data area, then you cannot do that unless your phone is rooted.
If the apps in question are both made by you, the answer is yes, it is possible. You would have to use the android:sharedUserId property in the manifest file of each app, make them the same and sign both apps with the same key. This would give the apps access to each others data.
If the data you are speaking of is on the devices SD card, yes you can. If it is in the internal storage, then no (unless you created both apps and have used the

How to get Android application id?

In Android, how do I get the application's id programatically (or by some other method), and how can I communicate with other applications using that id?
If your are looking for the value defined by applicationId in gradle, you can simply use
BuildConfig.APPLICATION_ID
EDIT 2022:
As indicated in the comments, BuildConfig.APPLICATION_ID is deprecated, but for librairies only. In this case, BuildConfig.LIBRARY_PACKAGE_NAME should be used instead, as explained here.
Also, now, Context::getPackageName returns the value defined by applicationId in gradle, even if you have several flavors with different applicationId (with a unique namespace), see here.
If by application id, you're referring to package name, you can use the method Context::getPackageName (http://http://developer.android.com/reference/android/content/Context.html#getPackageName%28%29).
In case you wish to communicate with other application, there are multiple ways:
Start an activity of another application and send data in the "Extras" of the "Intent"
Send a broadcast with specific action/category and send data in the extras
If you just need to share structured data, use content provider
If the other application needs to continuously run in the background, use Server and "bind" yourself to the service.
If you can elaborate your exact requirement, the community will be able to help you better.
i'm not sure what "application id" you are referring to, but for a unique identifier of your application you can use:
getApplication().getPackageName() method from your current activity
For getting AppId (or package name, how some says), just call this:
But be sure that you importing BuildConfig with your app id packages path
BuildConfig.APPLICATION_ID
Package name is your android app id .
String appId = BuildConfig.APPLICATION_ID
Or
https://play.google.com/store/apps/details?id=com.whatsapp
App Id = com.whatsapp
Else you can get id of process your application runs in:
final static int android.os.Process.myPid()
Returns the identifier of this process, which can be used with killProcess(int) and sendSignal(int, int).
I am not sure what you need the app/installation ID for, but you can review the existing possibilities in a great article from Android developers:
http://android-developers.blogspot.com/2011/03/identifying-app-installations.html
To sum up:
UUID.randomUUID() for creating id on the first time an app runs after installation and simple retrieval afterwards
TelephonyManager.getDeviceId() for actual device identifier
Settings.Secure.ANDROID_ID on relatively modern devices
Step 1: Open the Google Play Store
Step 2: Open any App in App Store
Example: facebook
Step 3: Click on any App and Look at the Browser link and At the End id=com.facebook.katana&hl=en will be there and this is your Apps Unique Id.
The PackageInfo.sharedUserId field will show the user Id assigned in the manifest.
If you want two applications to have the same userId, so they can see each other's data and run in the same process, then assign them the same userId in the manifest:
android:sharedUserId="string"
The two packages with the same sharedUserId need to have the same signature too.
I would also recommend reading here for a nudge in the right direction.
If the whole purpose is to communicate data with some other application, use Intent's sendBroadcast methods.
If you are using the new** Gradle build system then getPackageName will oddly return application Id, not package name. So MasterGaurav's answer is correct but he doesn't need to start off with ++
If by application id, you're referring to package name...
See more about the differences here.
** not so new at this point
++ I realize that his answer made perfect sense in 2011
Android App ES File Explorer shows the Android package name in the User Apps section which is useful for Bitwarden. Bitwarden refers to this as "android application package ID (or package name)".
To track installations, you could for example use a UUID as an identifier, and simply create a new one the first time an app runs after installation. Here is a sketch of a class named “Installation” with one static method Installation.id(Context context). You could imagine writing more installation-specific data into the INSTALLATION file.
public class Installation {
private static String sID = null;
private static final String INSTALLATION = "INSTALLATION";
public synchronized static String id(Context context) {
if (sID == null) {
File installation = new File(context.getFilesDir(), INSTALLATION);
try {
if (!installation.exists())
writeInstallationFile(installation);
sID = readInstallationFile(installation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sID;
}
private static String readInstallationFile(File installation) throws IOException {
RandomAccessFile f = new RandomAccessFile(installation, "r");
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}
private static void writeInstallationFile(File installation) throws IOException {
FileOutputStream out = new FileOutputStream(installation);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
}
Yon can see more at https://github.com/MShoaibAkram/Android-Unique-Application-ID
This is now deprecated you should use BuildConfig.LIBRARY_PACKAGE_NAME as answered here

Categories

Resources