Hooking into main application's onCreate method from Adobe AIR extension - android

I am creating an ANE for Urban Airship, a service for sending push notifications (among other things). So far the integration has worked great but only when the app is open. When the app is exited, receiving a new push notification results in the app crashing with:
11-29 01:19:32.448 22340-22340/air.com.example.app E/Urban Airship Autopilot: Unable to takeOff automatically
11-29 01:19:32.496 22340-22440/air.com.example.app E/AndroidRuntime: FATAL EXCEPTION: IntentService[PushService]
Process: air.com.example.app, PID: 22340
java.lang.IllegalStateException: Take off must be called before shared()
at com.urbanairship.UAirship.shared(UAirship.java:147)
at com.urbanairship.BaseIntentService.onHandleIntent(BaseIntentService.java:94)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java)
at android.os.Handler.dispatchMessage(Handler.java)
at android.os.Looper.loop(Looper.java)
at android.os.HandlerThread.run(HandlerThread.java)
Through a lot of digging I believe the issue to be that I am calling UAirship.takeOff() from within TakeoffFunction (an FREFunction within my ANE) instead of calling it from the Application's main onCreate method (as is the issue seen in: UrbanAirship NPE)
Here is my TakeoffFunction:
public class TakeoffFunction implements FREFunction
{
#Override
public FREObject call(FREContext context, FREObject[] freObjects)
{
Log.d("TakeoffFunction", "Attempting Urban Airship TAKEOFF");
Application app = context.getActivity().getApplication();
Log.d("TakeoffFunction", "app found: " + app);
AirshipConfigOptions options = new AirshipConfigOptions();
options.developmentAppKey = "xxx";
options.developmentAppSecret = "xxx";
options.inProduction = false;
options.gcmSender = "000";
Log.d("TakeoffFunction", "Prepare to Launch...");
UAirship.takeOff(app, options, new UAirship.OnReadyCallback()
{
#Override
public void onAirshipReady(UAirship uAirship)
{
Log.d("TakeoffFunction", "Urban Airship is ready after takeoff");
uAirship.getPushManager().setUserNotificationsEnabled(true);
Log.d("TakeoffFunction", "User notifications have been enabled");
}
});
return null;
}
}
Thus, it would seem I need to somehow call UAirship.takeOff() from the main application's onCreate method. However, this is proving to be a challenge as I know that for Adobe AIR, there is an AppEntry class which functions as the main Application class, but this class is, as far as I know, barred from modification for developers. I found this tutorial: http://blogs.adobe.com/digitalmedia/2011/05/extending-air-for-android/ from back in 2011 before native extensions were officially supported. In there I see that they're able to override and extend the onCreate() method, but I don't know how I would go about doing the same thing with this native extension.
I would like to know if it is possible to extend the AppEntry's onCreate method or point AIR to a different AppEntry class altogether, overwriting the original.

Its common to see libs wanting to have the app initialize its module in the application's onCreate as its called before all services, receivers, or activities are created. Basically the only real entry point for all apps.
For frameworks like Adobe where its hard to call takeoff in the applicaiton file, we have added another way of calling takeoff using Autopilot.
Example:
public class AirAutopilot extends Autopilot {
#Override
public AirshipConfigOptions createAirshipConfigOptions(Context context) {
AirshipConfigOptions options = new AirshipConfigOptions();
options.developmentAppKey = "xxx";
options.developmentAppSecret = "xxx";
options.inProduction = false;
options.gcmSender = "000";
return options;
}
#Override
public void onAirshipReady(UAirship airship) {
Log.d("TakeoffFunction", "Urban Airship is ready after takeoff");
airship.getPushManager().setUserNotificationsEnabled(true);
Log.d("TakeoffFunction", "User notifications have been enabled");
}
}
Then add the autopilot class to the manifest in the application block (not sure about the Adobe Air way of doing this):
<meta-data android:name="com.urbanairship.autopilot" android:value="com.example.AirAutopilot" />
Then in your plugin, make sure a call to autopilot is made before accessing the UAirship instance:
Autopilot.automaticTakeOff(context.getActivity().getApplication());

Related

Android WebView causing RuntimeException at WebViewDelegate.getPackageId

I have a WebView in the layout xml of my MainActivity, to which I setWebViewClient(new WebViewClient()), followed by loadUrl(...) in onCreate.
Most of the time the app runs fine and the Web content is displayed correctly.
But in some cases, opening the app causes a crash. I've noticed that it happens when the app scheduled a PendingIntent broadcast with AlarmManager, which triggers a Notification whose contentIntent is a PendingIntent.getActivity set to launch MainActivity.
But it happens only in the case when the user has removed the app from the stack of active apps in the meantime (Notification is visible, not yet clicked, and stack if apps cleared. So, app process probably stopped?).
Seemingly no other system modifications in between (in particular no app/system update, no playing around with user profiles or Chrome app.)
Stack trace:
java.lang.RuntimeException:
at android.webkit.WebViewDelegate.getPackageId (WebViewDelegate.java:164)
at yj.a (PG:16)
at xH.run (PG:14)
at java.lang.Thread.run (Thread.java:764)
Occurs with Android 7.0 thru 9. Also, seems to have started to occur when I upgraded target SDK to 28.
I don't use explicitly a WebViewDelegate. It must be internal system code (hence the obfuscation).
By reading the source code of AOSP, it seems that the WebView fails to retrieve the package to which it belongs -- but why sometimes only!?
Any help appreciated! Thanks.
It has taken weeks of investigation on and off, but I've finally found why I'm seeing this issue. For me, it was just because I'd overridden the getResources() method in my application scope to use the current activity. Something like this:
public class MyApplication extends MultiDexApplication {
private static MyApplication sInstance = null;
private WeakReference<Activity> mCurrentActivity;
public static MyApplication getInstance() {
return sInstance;
}
public void setCurrentActivity(Activity activity) {
mCurrentActivity = new WeakReference<>(activity);
}
public Activity getCurrentActivity() {
return mCurrentActivity == null ? null : mCurrentActivity.get();
}
#Override
public Resources getResources() {
// This is a very BAD thing to do
Activity activity = getCurrentActivity();
if (activity != null) {
return activity.getResources();
}
return super.getResources();
}
}
This was done as a shortcut as I often wanted to get strings that were activity-specific, so I was calling MyApplication.getInstance().getResources().getString(). I now know this was a bad thing to do - removing my override of this method instantly fixed it.
So the key takeaway from this for me is that when the WebView is initialising, it MUST be able to get hold of the application context, so that the resources passed into WebViewDelegate.getPackageId() are at the application level - the activity context isn't enough, and causes this error.
As a side note - I wasn't even trying to add a WebView to my application. I was only actually using the following:
String userAgent = WebSettings.getDefaultUserAgent(this);
I was then passing this value into a custom media player that I'm using. Passing "this" as either application or activity scope always failed, due to my override.
Looking through documentation,you can see that error is thrown when package can't be found.Check your syntax ,package name and try again.
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewDelegate.java (Line 164)
/**
* Returns the package id of the given {#code packageName}.
*/
public int getPackageId(Resources resources, String packageName) {
SparseArray<String> packageIdentifiers =
resources.getAssets().getAssignedPackageIdentifiers();
for (int i = 0; i < packageIdentifiers.size(); i++) {
final String name = packageIdentifiers.valueAt(i);
if (packageName.equals(name)) {
return packageIdentifiers.keyAt(i);
}
}
throw new RuntimeException("Package not found: " + packageName);
}

Is it possible to get a FlutterView from BroadcastReceiver/Alarm Manager?

I am trying to build a Flutter app that does the following:
1) Run an alarm manager every minute (even when app is in background/closed).
2) When the alarm manager's onReceive method is called, get the users location.
3) Store this location in a SQL/SQF database.
Basically, I have all the code working. However, I'd prefer to do step 2 and 3 with two different plugins, as to create more modularity. But to do so, I need to have an instance of a FlutterView, such that I can do a "MethodChannel(flutterView, CHANNEL).invokeMethod(methods, args);" from Android.
(In android/Java, onReceive method) I have tried to get the FlutterView from the context, like so:
private FlutterView viewFromAppContext(Context context) {
Application app = (Application) context.getApplicationContext();
if (!(app instanceof FlutterApplication)) {
Log.i(TAG, "viewFromAppContext app not a FlutterApplication");
return null;
}
FlutterApplication flutterApp = (FlutterApplication) app;
Activity activity = flutterApp.getCurrentActivity();
if (activity == null) {
Log.i(TAG, "viewFromAppContext activity is null");
return null;
}
if (!(activity instanceof FlutterActivity)) {
Log.i(TAG, "viewFromAppContext activity is not a FlutterActivity");
return null;
}
FlutterActivity flutterActivity = (FlutterActivity) activity;
return flutterActivity.getFlutterView();
}
However, when I try to do this when the app is in the background, the activity is null.
Is it possible to create a new activity and/or flutterview in this scenario (which can direct to my 'setMethodCallHandler' method in dart)?
I think what you are looking for is a FlutterNativeView called with the second param set to true; it is not easy to follow, slightly divergent from the linked git repo and is engineered well in excess of your needs, but Ben Konyi has a working example of this. I think you might enjoy checking out some of the ways he kept the other plugins working and the service working, if not the methodchannel impl (with three callback domains).

Why does Carto Package Manager can't connect to it's database after resuming?

We are using Carto Xamarin Mobile SDK to develop app with offline maps support. It works fine until user does not try to resume application after it has been disposed. Activity creation (activity uses carto package manager) crashes with exception:
Carto.PackageManager.CartoPackageManager.CartoPackageManager(string source, string dataFolder)<01980a2dae7148abb92e6b982667f448>:0
App.Droid.OfflineMaps.AndroidMapPackageManager.AndroidMapPackageManager()<76ae23b9271c407d865ebb6162639870>:0
App.Droid.Plugins.MapDownloader.Plugin.<>c.<Load>b__0_0()<76ae23b9271c407d865ebb6162639870>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.<>c__DisplayClass33_0<TInterface>.<RegisterSingleton>b__0()<4ddde23419c5494288c799fcdbb0f189>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.ConstructingSingletonResolver.Resolve()<4ddde23419c5494288c799fcdbb0f189>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.InternalTryResolve(Type type, MvxSimpleIoCContainer.IResolver resolver, ref object resolved
App.Core.ViewModels.Blocks.Maps.Offline.MapDownloaderOwnerViewModel.EnableMapDownloader(City city)<d6cca792b401420d922bc024
Here on line 105 you can see where exception is originally thrown.
In the regular entering page it works fine.
PackageManager request is happening in the ctor of the page ViewModel via:
Mvx.Resolve<MapDownloadsManager>(); // It receivs as injection platform-dependent packageManager
It seems that the problem is in the database path.
We are generating the folder name that then passed to the CartoPackageManager constructor in the following way:
private static string CreateFolder(string folderPath)
{
var folder = GetDocumentDirectory(folderPath);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return folder;
}
private static string GetDocumentDirectory(string withFolder = null)
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return withFolder == null ? documents : Path.Combine(documents, withFolder + "/");
}
Do you have any ideas where the source of the problem could be?
P.S.: Carto SDK v 4.0.2, MvvmCross 4.4.0, unfortunately can't update to the newer now.
After some research I have spotted that task queue is reset, now supposing that app actually crashes here in source code:
std::string taskDbFileName = "tasks_v1.sqlite";
try {
_taskQueue = std::make_shared<PersistentTaskQueue>(createLocalFilePath(taskDbFileName));
}
catch (const std::exception& ex) {
//This looks like: Error while constructing PackageManager: Package encryption keys do not match, trying to remove
Log::Errorf("PackageManager: Error while constructing PackageManager::PersistentTaskQueue: %s, trying to remove...", ex.what());
_taskQueue.reset();
utf8_filesystem::unlink(taskDbFileName.c_str());
try {
_taskQueue = std::make_shared<PersistentTaskQueue>(createLocalFilePath(taskDbFileName));
}
catch (const std::exception& ex) {
**throw FileException("Failed to create/open package manager task queue database", taskDbFileName); //App gets here.**
}
}
Initialization of package manager:
public class AndroidMapPackageManager : PackageManagerListener, IPackageManger
{
private readonly CartoPackageManager _packageManager;
public AndroidMapPackageManager()
{
var folder = CreateFolder(OfflineMapStrings.MapsFolderName); // /data/user/0/com.app.cityguide/files/mapmap2/
_packageManager =
new CartoPackageManager(OfflineMapsStrings.PackageManagerSource, folder)
{
PackageManagerListener = this
};
}
}
I am starting it just after the constructor worked after subscription to the listener events by other class. And, frankly saying, I never stop it. Do I need it?
With FIllRam Repoductivity is 100%. I used debug mode with no background allowed and now I tryed Ram Filler and result is same.
License issues:
05-25 12:41:41.091 26101 26101 E carto-mobile-sdk: CartoPackageManager: RegisterLicense not called, using random key for package encryption!
05-25 12:41:41.094 26101 26101 E carto-mobile-sdk: PackageManager: Error while constructing PackageManager: Package encryption keys do not match, trying to remove
The problem was that I was registering license key in SplashActivity. But, after resuming splash activity is not shown and, obviously, Registering of the license key was not performed. I have moved it to the custom application class and it worked:
public class CustomApplication : Application
{
public override void OnCreate()
{
base.OnCreate();
MapView.RegisterLicense(CartoApiKey, this);
}
}

Registering a GCM Service on Android in Application Class

Would it be a good idea to register GCM in an Application class like so:
public class MyApplication extends Application {
private GCMRegistrationUtil mGCMRegistrationUtil = GCMRegistrationUtil.getInstance(this);
#Override
public void onCreate() {
String registrationId = mGCMRegistrationUtil.registerDevice();
Log.d(TAG, "Device Registered Properly: " + registrationId);
}
It's working fine - I just am wondering if this could create any issues since it's inside an application class. I implemented it this was so it always checks to see if an id is stored in SharedPreferences immediately upon application startup, and if not go ahead and register.
By issues I am guessing maybe Performance issues?? Any potential for that?

How do you add a new Android Application to existing project?

I have been developing a number of Android activities in a project in Eclipse.
I am now following a tutorial to achieve push notifications and I need to create a new Android Application (extends Application in the code).
However, when trying to create this application it does not run properly. Does the class name need to be the same as the project title?
When trying to run this on my phone it crashes.
My code is below: (ApplicationLoader is not the name of the App/Project)
public class ApplicationLoader extends Application {
#Override
public void onCreate() {
super.onCreate();
//Configure your application
//
// This can be done in code as illustrated here,
// or you can add these settings to a properties file
// called airshipconfig.properties
// and place it in your "assets" folder
AirshipConfigOptions options = AirshipConfigOptions.loadDefaultOptions(this);
options.developmentAppKey = --my development app key
options.productionAppKey = --my production app key
options.inProduction = false; //determines which app key to use
Logger.logLevel = Log.VERBOSE;
// Take off initializes the services
UAirship.takeOff(this, options);
PushManager.enablePush();
PushPreferences prefs = PushManager.shared().getPreferences();
Logger.info("My Application onCreate - App APID: " + prefs.getPushId());
}
}
If your application name is ApplicationLoader (defined under application tag in your AndroidManifest) then Application Class name should also be ApplicationLoader
<application android:name="ApplicationLoader ">
public class ApplicationLoader extends Application

Categories

Resources