From 36da4016849269b03a372e27a39aee9eee650420 Mon Sep 17 00:00:00 2001 From: atanasovg Date: Fri, 3 Jun 2016 15:34:30 +0300 Subject: [PATCH 1/3] Remove the android.app.Application extend from the core modules. This will enable various scenarios where custom application implementation is needed. --- .../application/application.android.ts | 147 +++++++++--------- tns-core-modules/application/application.d.ts | 7 + tns-core-modules/utils/utils.android.ts | 38 ++++- 3 files changed, 112 insertions(+), 80 deletions(-) diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 96ec3800b..9456032a7 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -7,40 +7,9 @@ import * as typesModule from "utils/types"; global.moduleMerge(appModule, exports); var typedExports: typeof definition = exports; -@JavaProxy("com.tns.NativeScriptApplication") -class NativeScriptApplication extends android.app.Application { - - constructor() { - super(); - return global.__native(this); - } - - public onCreate(): void { - androidApp.init(this); - setupOrientationListener(androidApp); - } - - public onLowMemory(): void { - gc(); - java.lang.System.gc(); - super.onLowMemory(); - - typedExports.notify({ eventName: typedExports.lowMemoryEvent, object: this, android: this }); - } - - public onTrimMemory(level: number): void { - gc(); - java.lang.System.gc(); - super.onTrimMemory(level); - } -} - -// We are using the exports object for the common events since we merge the appModule with this module's exports, which is what users will receive when require("application") is called; -// TODO: This is kind of hacky and is "pure JS in TypeScript" - -function initEvents() { +function initLifecycleCallbacks() { // TODO: Verify whether the logic for triggerring application-wide events based on Activity callbacks is working properly - var lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({ + let lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({ onActivityCreated: function (activity: any, bundle: any) { if (!androidApp.startActivity) { androidApp.startActivity = activity; @@ -55,7 +24,6 @@ function initEvents() { }, onActivityDestroyed: function (activity: any) { - // Clear the current activity reference to prevent leak if (activity === androidApp.foregroundActivity) { androidApp.foregroundActivity = undefined; @@ -150,6 +118,55 @@ function initEvents() { return lifecycleCallbacks; } +let currentOrientation: number; +function initComponentCallbacks() { + // TODO: Verify whether the logic for triggerring application-wide events based on Activity callbacks is working properly + let componentCallbacks = new android.content.ComponentCallbacks2({ + onLowMemory: function() { + gc(); + java.lang.System.gc(); + typedExports.notify({ eventName: typedExports.lowMemoryEvent, object: this, android: this }); + }, + + onTrimMemory: function(level: number) { + // TODO: This is skipped for now, test carefully for OutOfMemory exceptions + }, + + onConfigurationChanged: function(newConfig: android.content.res.Configuration) { + let newOrientation = newConfig.orientation; + if(newOrientation === currentOrientation) { + return; + } + + currentOrientation = newOrientation; + + let enums = require("ui/enums"); + let newValue; + + switch (orientation) { + case android.content.res.Configuration.ORIENTATION_LANDSCAPE: + newValue = enums.DeviceOrientation.landscape; + break; + case android.content.res.Configuration.ORIENTATION_PORTRAIT: + newValue = enums.DeviceOrientation.portrait; + break; + default: + newValue = enums.DeviceOrientation.unknown; + break; + } + + typedExports.notify({ + eventName: typedExports.orientationChangedEvent, + android: androidApp.nativeApp, + newValue: newValue, + object: typedExports.android, + }); + } + }); + + return componentCallbacks; +} + export class AndroidApplication extends observable.Observable implements definition.AndroidApplication { public static activityCreatedEvent = "activityCreated"; public static activityDestroyedEvent = "activityDestroyed"; @@ -187,15 +204,20 @@ export class AndroidApplication extends observable.Observable implements definit public onActivityResult: (requestCode: number, resultCode: number, data: android.content.Intent) => void; - private _eventsToken: any; - public init(nativeApp: any) { + if(this.nativeApp) { + throw new Error("application.android already initialized.") + } + this.nativeApp = nativeApp; this.packageName = nativeApp.getPackageName(); this.context = nativeApp.getApplicationContext(); - this._eventsToken = initEvents(); - this.nativeApp.registerActivityLifecycleCallbacks(this._eventsToken); + let lifecycleCallbacks = initLifecycleCallbacks(); + let componentCallbacks = initComponentCallbacks(); + this.nativeApp.registerActivityLifecycleCallbacks(lifecycleCallbacks); + this.nativeApp.unregisterComponentCallbacks(componentCallbacks); + this._registerPendingReceivers(); } @@ -240,11 +262,11 @@ export class AndroidApplication extends observable.Observable implements definit } } -var androidApp = new AndroidApplication(); +let androidApp = new AndroidApplication(); // use the exports object instead of 'export var' due to global namespace collision typedExports.android = androidApp; -var BroadcastReceiverClass; +let BroadcastReceiverClass; function ensureBroadCastReceiverClass() { if (BroadcastReceiverClass) { return; @@ -269,11 +291,18 @@ function ensureBroadCastReceiverClass() { BroadcastReceiverClass = BroadcastReceiver; } -var started = false; +let started = false; export function start(entry?: frame.NavigationEntry) { if (started) { throw new Error("Application is already started."); } + + if(!androidApp.nativeApp) { + // we are still not initialized, this is possible if no 'androidApp.init' call has been made + let utils = require("utils/utils"); + let nativeApp = utils.ad.getApplication(); + androidApp.init(nativeApp); + } started = true; if (entry) { @@ -283,42 +312,6 @@ export function start(entry?: frame.NavigationEntry) { loadCss(); } -var currentOrientation: number; -function setupOrientationListener(androidApp: AndroidApplication) { - androidApp.registerBroadcastReceiver(android.content.Intent.ACTION_CONFIGURATION_CHANGED, onConfigurationChanged); - currentOrientation = androidApp.context.getResources().getConfiguration().orientation; -} - -function onConfigurationChanged(context: android.content.Context, intent: android.content.Intent) { - var orientation = context.getResources().getConfiguration().orientation; - - if (currentOrientation !== orientation) { - currentOrientation = orientation; - - var enums = require("ui/enums"); - - var newValue; - switch (orientation) { - case android.content.res.Configuration.ORIENTATION_LANDSCAPE: - newValue = enums.DeviceOrientation.landscape; - break; - case android.content.res.Configuration.ORIENTATION_PORTRAIT: - newValue = enums.DeviceOrientation.portrait; - break; - default: - newValue = enums.DeviceOrientation.unknown; - break; - } - - typedExports.notify({ - eventName: typedExports.orientationChangedEvent, - android: context, - newValue: newValue, - object: typedExports.android, - }); - } -} - function loadCss() { //HACK: identical to application.ios.ts typedExports.appSelectors = typedExports.loadCss(typedExports.cssFile) || []; diff --git a/tns-core-modules/application/application.d.ts b/tns-core-modules/application/application.d.ts index 1a7105d28..af7237bc4 100644 --- a/tns-core-modules/application/application.d.ts +++ b/tns-core-modules/application/application.d.ts @@ -376,6 +376,13 @@ declare module "application" { * True if the application is not running (suspended), false otherwise. */ paused: boolean; + + /** + * Initialized the android-specific application object with the native android.app.Application instance. + * This is useful when creating custom application types. + * @param nativeApp - the android.app.Application instance that started the app. + */ + init: (nativeApp) => void; /** * [Deprecated. Please use the respective event instead.] Direct handler of the [onActivityCreated method](http://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks.html). diff --git a/tns-core-modules/utils/utils.android.ts b/tns-core-modules/utils/utils.android.ts index 9fc92a3db..b0526a9d0 100644 --- a/tns-core-modules/utils/utils.android.ts +++ b/tns-core-modules/utils/utils.android.ts @@ -147,11 +147,43 @@ export module ad { view.setSingleLine(value === enums.WhiteSpace.nowrap); view.setEllipsize(value === enums.WhiteSpace.nowrap ? android.text.TextUtils.TruncateAt.END : null); } - + + let nativeApp: android.app.Application; + declare var com; export function getApplication() { - return (com.tns).NativeScriptApplication.getInstance(); + if(!nativeApp) { + // check whether the com.tns.NativeScriptApplication type exists + if(com.tns.NativeScriptApplication) { + nativeApp = com.tns.NativeScriptApplication.getInstance(); + } + else { + // check whether application.android.init has been explicitly called + let application = require("application"); + nativeApp = application.android.nativeApp; + + if(!nativeApp) { + // TODO: Should we handle the case when a custom application type is provided and the user has not explicitly initialized the application module? + let clazz = java.lang.Class.forName("android.app.ActivityThread"); + if(clazz) { + let method = clazz.getMethod("currentApplication", null); + if(method) { + nativeApp = method.invoke(null, null); + } + } + } + } + + if(!nativeApp) { + throw new Error("Failed to retrieve native Android Application object. If you have a custom android.app.Application type implemented make sure that you've called the '.android.init' method.") + } + } + + return nativeApp; + } + export function getApplicationContext() { + let app = getApplication(); + return app.getApplicationContext(); } - export function getApplicationContext() { return getApplication().getApplicationContext(); } var inputMethodManager: android.view.inputmethod.InputMethodManager; export function getInputMethodManager() { From 87b0f53c3dce6f3804b34297d6aeaf2918d912eb Mon Sep 17 00:00:00 2001 From: atanasovg Date: Fri, 3 Jun 2016 16:27:17 +0300 Subject: [PATCH 2/3] Remove redundant comment. Fix native method call. --- tns-core-modules/application/application.android.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 9456032a7..9bfeccfb2 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -120,7 +120,6 @@ function initLifecycleCallbacks() { let currentOrientation: number; function initComponentCallbacks() { - // TODO: Verify whether the logic for triggerring application-wide events based on Activity callbacks is working properly let componentCallbacks = new android.content.ComponentCallbacks2({ onLowMemory: function() { gc(); @@ -216,7 +215,7 @@ export class AndroidApplication extends observable.Observable implements definit let lifecycleCallbacks = initLifecycleCallbacks(); let componentCallbacks = initComponentCallbacks(); this.nativeApp.registerActivityLifecycleCallbacks(lifecycleCallbacks); - this.nativeApp.unregisterComponentCallbacks(componentCallbacks); + this.nativeApp.registerComponentCallbacks(componentCallbacks); this._registerPendingReceivers(); } From 9847c9f8037a48f854d0504097ea768c3a6f61ee Mon Sep 17 00:00:00 2001 From: atanasovg Date: Mon, 6 Jun 2016 10:01:18 +0300 Subject: [PATCH 3/3] Improve the default nativeApp initialization. --- tns-core-modules/utils/utils.android.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tns-core-modules/utils/utils.android.ts b/tns-core-modules/utils/utils.android.ts index b0526a9d0..9242d3695 100644 --- a/tns-core-modules/utils/utils.android.ts +++ b/tns-core-modules/utils/utils.android.ts @@ -156,7 +156,9 @@ export module ad { if(com.tns.NativeScriptApplication) { nativeApp = com.tns.NativeScriptApplication.getInstance(); } - else { + + // the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type + if(!nativeApp) { // check whether application.android.init has been explicitly called let application = require("application"); nativeApp = application.android.nativeApp; @@ -173,6 +175,7 @@ export module ad { } } + // we cannot work without having the app instance if(!nativeApp) { throw new Error("Failed to retrieve native Android Application object. If you have a custom android.app.Application type implemented make sure that you've called the '.android.init' method.") }