I have service.apk that is using resources to show a notification in the status bar.
I am dynamically loading the class that is using these resources but then the resources for the notifications disappear somehow, and I'm getting this error:
W/ResourceType( 1142): No known package when getting value for resource number 0x7f020001
W/StatusBarIconView( 1142): Icon not found in 2130837505: 7f020001
W/StatusBarIconView( 1142): No icon for slot android/0x20
W/ActivityManager( 941): crashApplication: trying to crash self!
D/NotificationService( 941): onNotification error pkg=android tag=null id=32; will crashApplication(uid=1000, pid=941)
W/StatusBar( 1142): removeNotification for unknown key: android.os.BinderProxy#41b166b8
The code that dynamically loads:
PathClassLoader classLoader =
new PathClassLoader("system/app/ServiceImpl.apk",
ClassLoader.getSystemClassLoader());
Class someClass =
classLoader.loadClass("com.bla.ServiceImpl");
Constructor<Class> ctor = someClass.getConstructor(Context.class);
Object someObject = ctor.newInstance(context);
I'm thinking maybe the way I'm loading the apk is wrong? Maybe I should add a path for the resources?
Any Help would be very much appreciated!
you can refer to the following open source project:
https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md
following is the key thinking:
first, apk can be loaded by host android application, ignored that it is on sdcard or on system directory. but how can we do this? two problems need to be resolved: resource and activity's lifecircle. the above project resolves this two problems, now, Dynamically load apk that uses resources is possible, use DexClassLoader and provide new AssetManager can satisfy your demand.
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mDexPath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
then, resources can be visited by R identifer, use getResource.
To obtain resources from other apk, You need to use getResourcesForApplication(). It's obvious that main apk doesn't have that icon in it's generated R class, but Service one has.
So, in the Service, whenever You're trying to access resources, it should look like:
Resources res = packageManager.getResourcesForApplication("com.your_service");
Drawable icon = res.getDrawable(iconId);
Unfortunately, I think it'll not be possible to just pass iconId in notification and You'll need to load resource yourself before using in notification (e.g. setup RemoteViews and use Notification.Builder.setContent()).
Related
We have a shared library that contains version info and is referenced by all our projects in our Visual Studio Solution.
For the most part, we can reference the version string from every project and the dll reflect the info accordingly.
My issue here is, with our Android application (xamarin based). It has a manifest file which contains the versionName and versionCode.
How can we make those values in our android manifest file read from our shared project?
My understanding is that, it is not possible. Because
The manifest file presents essential information about your app to the Android system, information the system must have before it can run any of the app's code.
From Google's documentation
So this is a file that is required before the App builds.
C# Code in Shared Project (SAP/PCL) is ready to be used only after successful Compilation. So logically setting the Version Code and Version Name in Android Manifest File from Shared logic is not possible.
Another standard approach would be to set it from String Resource (XML) file in Android. You may have to copy and paste the value from Shared Project to strings.xml file and refer it in manifest, like
#string/versionCode
Note: I do not know anything about xamarin.
In java you can get the versioninfo from the manifest like this
public static String getAppVersionName(final Context context) {
try {
final String versionName = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0).versionName;
return versionName;
} catch (final NameNotFoundException e) {
}
return null;
}
I assume that xamarin has some mechanism to call PackageManager to get Packageinfo, too
You could do this by using a Dependency Service. Here's a great article on them: https://developer.xamarin.com/guides/xamarin-forms/dependency-service/
The idea would be your Dependency Service would expose the Android specific information to the shared code library.
For instance you might have an interface in your common code declared such as:
public interface IPlatformVersionInfo
{
string GetOSVersion ();
}
Now, in your Android library you would implement it:
public class PlatformVersionInfo : IPlatformVersionInfo
{
public string GetOSVersion () {
return Android.OS.Build.VERSION.SdkInt.ToString ();
}
}
Finally, in your common code you would use your dependency service of choice to invoke an instance of it:
var osVersion = DependencyService.Get<IPlatformVersionInfo>().GetOSVersion ();
Of course this is somewhat pseudo-code and depending what dependency service you choose the code may look a bit different.
In my application I make use of two build flavours / build variants. After switching to two build flavours, a bug was introduced in my application. I have now discovered the reason for this bug, but I am unable to find a solution.
The situation:
In my MainActivity class, I have a function that checks if a file exists - it is very straightforward;
public boolean fileExists(String filename) {
File file = null;
file = this.getApplicationContext().getFileStreamPath(filename);
return file.exists();
}
Using the debugger, location of the file is reported as: /data/data/foo.bar.appname.buildflavour/files/filename
In another class, I try to write to this same location;
outputStream = getActivity().getApplicationContext().openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write("test");
outputStream.close();
However, when I print the following line in front of the outputStream getActivity().getApplicationContext() - context is reported as; com.foo.bar.appname#14fcdd18. Therefore, I believe that these two classes are trying to save / retrieve a file in different locations. Any ideas on how I can make sure that the application is writing the file in the correct build flavour location? Thank you in advance!
You need to use different application id.
Form official site Configuring Gradle Builds
When using build variants, the build system enables you to uniquely identify different packages for each product flavors and build types.
productFlavors {
pro {
applicationId = "com.example.my.pkg.pro"
}
free {
applicationId = "com.example.my.pkg.free"
}
}
Eventually I was able to find the answer to my question. Initially I was calling a function in MainActivity onCreate, but this function was no longer being called because the buildFlavors redirected directly to one of my application fragments.
To anyone coming here with a similar problem: verify that your Flavors call the necessary functions and classes.
I'm using the book "Embedded Android".
I'm making a new System Service using AOSP(4.0.3_r1).
I want my system service to be registered in frameworks/base/core/java/android/content/app/ContextImpl.java so that I can use it through getSystemService() method.
The problem is, I can't find the app folder under content:androidroot/frameworks/base/core/java/android/content/app/ContextImpl.java
But, I found it in:androidroot/frameworks/base/core/java/android/app/ContextImpl.java
Are these 2 files the same? or is it just missing(the content/app folder)?
Any idea on what to do?
Karim wrote his book mostly orienting on Android 2.3.4 version. Something can be changed from this time. This is an example what has been changed.
Are these 2 files the same? or is it just missing(the content/app folder)?
These are the same files.
Any idea on what to do?
As I said the implementation has been changed. I looked into the code and here what you can change to make your code working (I can only suppose because I did not actually build my code). In the static block of ContextImpl class you need to add the following code:
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(OPERSYS_SERVICE);
IOpersysService service = IOpersysService.Stub.asInterface(b);
return new OpersysManager(service);
}});
You need to use SystemServer which holds all system services' names.
You should check this link out:
http://processors.wiki.ti.com/index.php/Android-Adding_SystemService
I downloaded the source code from the below link and added to my project.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/com/android/internal/os/PowerProfile.java
I am getting and it can not find R file shown below.
int id = com.android.internal.R.xml.power_profile;
Also can not import
import com.android.internal.util.XmlUtils;
I basically want to measure the power consumption of Android devices.
Personally using patached android.jar just causes headaches, using reflection is a 'simple' way of accessing PowerProfile.java. But as #FoamyGuy and countless others have noted this is hidden api so wrap it in a big try catch as it could break on later version of Android.
Class<?> powerProfileClazz = Class.forName("com.android.internal.os.PowerProfile");
//get constructor that takes a context object
Class[] argTypes = { Context.class };
Constructor constructor = powerProfileClazz
.getDeclaredConstructor(argTypes);
Object[] arguments = { context };
//Instantiate
Object powerProInstance = constructor.newInstance(arguments);
//define method
Method batteryCap = powerProfileClazz.getMethod("getBatteryCapacity", null);
//call method
Log.d(TAG, batteryCap.invoke(powerProInstance, null).toString());
Yes you can access the internal API that is com.android.internal.os.PowerProfile
just take a look at this link, and follow the step by step process.
You could use
int id = Resources.getSystem().getIdentifier("power_profile", "xml", "android");
But be aware of what FoamyGuy commented.
Easiest way to do this is to download the framework.jar of android and include that as external library in your project. After including the framework.jar in your android project you can find that resource file.
Is there any way to make an Android application to download and use a Java library at runtime?
Here is an example:
Imagine that the application needs to make some calculations depending on the input values. The application asks for these input values and then checks if the required Classes or Methods are available.
If not, it connects to a server, downloads the needed library, and loads it at runtime to calls the required methods using reflection techniques. The implementation could change depending on various criteria such as the user who is downloading the library.
Sorry, I'm late and the question has already an accepted answer, but yes, you can download and execute external libraries. Here is the way I did:
I was wondering whether this was feasible so I wrote the following class:
package org.shlublu.android.sandbox;
import android.util.Log;
public class MyClass {
public MyClass() {
Log.d(MyClass.class.getName(), "MyClass: constructor called.");
}
public void doSomething() {
Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
}
}
And I packaged it in a DEX file that I saved on my device's SD card as /sdcard/shlublu.jar.
Then I wrote the "stupid program" below, after having removed MyClass from my Eclipse project and cleaned it:
public class Main extends Activity {
#SuppressWarnings("unchecked")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
try {
final String libPath = Environment.getExternalStorageDirectory() + "/shlublu.jar";
final File tmpDir = getDir("dex", 0);
final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());
final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shlublu.android.sandbox.MyClass");
final Object myInstance = classToLoad.newInstance();
final Method doSomething = classToLoad.getMethod("doSomething");
doSomething.invoke(myInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
It basically loads the class MyClass that way:
create a DexClassLoader
use it to extract the class MyClass from "/sdcard/shlublu.jar"
and store this class to the application's "dex" private directory (internal storage of the phone).
Then, it creates an instance of MyClass and invokes doSomething() on the created instance.
And it works... I see the traces defined in MyClass in my LogCat:
I've tried on both an emulator 2.1 and on my physical HTC cellphone (which is running Android 2.2 and which is NOT rooted).
This means you can create external DEX files for the application to download and execute them. Here it was made the hard way (ugly Object casts, Method.invoke() ugly calls...), but it must be possible to play with Interfaces to make something cleaner.
Wow. I'm the first surprised. I was expecting a SecurityException.
Some facts to help investigating more:
My DEX shlublu.jar was signed, but not my app
My app was executed from Eclipse / USB connection. So this is an unsigned APK compiled in DEBUG mode
Shlublu's anwser is really nice. Some small things though that would help a beginner:
for library file "MyClass" make a separate Android Application project which has the MyClass file as only file in the src folder (other stuff, like project.properties, manifest, res, etc. should also be there)
in library project manifest make sure you have:
<application android:icon="#drawable/icon"
android:label="#string/app_name">
<activity android:name=".NotExecutable"
android:label="#string/app_name">
</activity>
</application>
(".NotExecutable" is not a reserved word. It is just that I had to put something here)
For making the .dex file, just run the library project as android application (for the compiling) and locate .apk file from the bin folder of the project.
Copy the .apk file to your phone and rename it as shlublu.jar file (an APK is actually a specialization of a jar, though)
Other steps are the same as described by Shlublu.
Big thanks to Shlublu for cooperation.
Technically should work but what about Google rules?
From: play.google.com/intl/en-GB/about/developer-content-policy-print
An app distributed via Google Play may not modify, replace or update
itself using any method other than Google Play’s update mechanism.
Likewise, an app may not download executable code (e.g. dex, JAR, .so
files) from a source other than Google Play. This restriction does not
apply to code that runs in a virtual machine and has limited access to
Android APIs (such as JavaScript in a WebView or browser).
I am not sure if you can achieve this by dynamically loading java code. May be you can try embedding a script engine your code like rhino which can execute java scripts which can be dynamically downloaded and updated.
sure, it is possible. apk which is not installed can be invoked by host android application.generally,resolve resource and activity's lifecircle,then,can load jar or apk dynamically.
detail,please refer to my open source research on github: https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md
also,DexClassLoader and reflection is needed, now look at some key code:
/**
* Load a apk. Before start a plugin Activity, we should do this first.<br/>
* NOTE : will only be called by host apk.
* #param dexPath
*/
public DLPluginPackage loadApk(String dexPath) {
// when loadApk is called by host apk, we assume that plugin is invoked by host.
mFrom = DLConstants.FROM_EXTERNAL;
PackageInfo packageInfo = mContext.getPackageManager().
getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
if (packageInfo == null)
return null;
final String packageName = packageInfo.packageName;
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
if (pluginPackage == null) {
DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
AssetManager assetManager = createAssetManager(dexPath);
Resources resources = createResources(assetManager);
pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
resources, packageInfo);
mPackagesHolder.put(packageName, pluginPackage);
}
return pluginPackage;
}
your demands is only partly of function in the open source project mentioned at the begining.
If you're keeping your .DEX files in external memory on the phone, such as the SD card (not recommended! Any app with the same permissions can easily overwrite your class and perform a code injection attack) make sure you've given the app permission to read external memory. The exception that gets thrown if this is the case is 'ClassNotFound' which is quite misleading, put something like the following in your manifest (consult Google for most up to date version).
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
I think #Shlublu answer is correct but i just want to highlight some key points.
We can load any classes from external jar and apk file.
In Any way, we can load Activity from external jar but we can not start it because of the context concept.
To load the UI from external jar we can use fragment. Create the instance of the fragment and embedded it in the Activity. But make sure fragment creates the UI dynamically
as given below.
public class MyFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState)
{
super.onCreateView(inflater, container, savedInstanceState);
LinearLayout layout = new LinearLayout(getActivity());
layout.setLayoutParams(new
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
Button button = new Button(getActivity());
button.setText("Invoke host method");
layout.addView(button, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
return layout;
}
}