import { AndroidActivityBundleEventData, AndroidActivityEventData, ApplicationEventData, OrientationChangedEventData, AndroidApplication as AndroidApplicationDefinition } from "."; import { notify, lowMemoryEvent, orientationChangedEvent, suspendEvent, resumeEvent, setApplication, livesync, Observable } from "./application-common"; // First reexport so that app module is initialized. export * from "./application-common"; import { NavigationEntry } from "../ui/frame"; const ActivityCreated = "activityCreated"; const ActivityDestroyed = "activityDestroyed"; const ActivityStarted = "activityStarted"; const ActivityPaused = "activityPaused"; const ActivityResumed = "activityResumed"; const ActivityStopped = "activityStopped"; const SaveActivityState = "saveActivityState"; const ActivityResult = "activityResult"; const ActivityBackPressed = "activityBackPressed"; const ActivityRequestPermissions = "activityRequestPermissions"; export class AndroidApplication extends Observable implements AndroidApplicationDefinition { public static activityCreatedEvent = ActivityCreated; public static activityDestroyedEvent = ActivityDestroyed; public static activityStartedEvent = ActivityStarted; public static activityPausedEvent = ActivityPaused; public static activityResumedEvent = ActivityResumed; public static activityStoppedEvent = ActivityStopped; public static saveActivityStateEvent = SaveActivityState; public static activityResultEvent = ActivityResult; public static activityBackPressedEvent = ActivityBackPressed; public static activityRequestPermissionsEvent = ActivityRequestPermissions; public paused: boolean; public nativeApp: android.app.Application; public context: android.content.Context; public foregroundActivity: android.app.Activity; public startActivity: android.app.Activity; public packageName: string; public get currentContext(): android.content.Context { return this.foregroundActivity; } public init(nativeApp: android.app.Application) { if (this.nativeApp === nativeApp) { return; } if (this.nativeApp) { throw new Error("application.android already initialized."); } this.nativeApp = nativeApp; this.packageName = nativeApp.getPackageName(); this.context = nativeApp.getApplicationContext(); let lifecycleCallbacks = initLifecycleCallbacks(); let componentCallbacks = initComponentCallbacks(); this.nativeApp.registerActivityLifecycleCallbacks(lifecycleCallbacks); this.nativeApp.registerComponentCallbacks(componentCallbacks); this._registerPendingReceivers(); } private _registeredReceivers = {}; private _pendingReceiverRegistrations = new Array<(context: android.content.Context) => void>(); private _registerPendingReceivers() { this._pendingReceiverRegistrations.forEach(func => func(this.context)); this._pendingReceiverRegistrations.length = 0; } public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) { ensureBroadCastReceiverClass(); const that = this; const registerFunc = function (context: android.content.Context) { const receiver: android.content.BroadcastReceiver = new BroadcastReceiverClass(onReceiveCallback); context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter)); that._registeredReceivers[intentFilter] = receiver; }; if (this.context) { registerFunc(this.context); } else { this._pendingReceiverRegistrations.push(registerFunc); } } public unregisterBroadcastReceiver(intentFilter: string) { const receiver = this._registeredReceivers[intentFilter]; if (receiver) { this.context.unregisterReceiver(receiver); this._registeredReceivers[intentFilter] = undefined; delete this._registeredReceivers[intentFilter]; } } } const androidApp = new AndroidApplication(); // use the exports object instead of 'export var' due to global namespace collision exports.android = androidApp; setApplication(androidApp); let mainEntry: NavigationEntry; let started = false; export function start(entry?: NavigationEntry | string) { if (started) { throw new Error("Application is already started."); } started = true; mainEntry = typeof entry === "string" ? { moduleName: entry } : entry; if (!androidApp.nativeApp) { const nativeApp = getNativeApplication(); androidApp.init(nativeApp); } } export function getMainEntry() { return mainEntry; } export function getNativeApplication(): android.app.Application { // Try getting it from module - check whether application.android.init has been explicitly called let nativeApp = androidApp.nativeApp; if (!nativeApp) { // check whether the com.tns.NativeScriptApplication type exists if (!nativeApp && com.tns.NativeScriptApplication) { nativeApp = com.tns.NativeScriptApplication.getInstance(); } // the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type 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? const clazz = java.lang.Class.forName("android.app.ActivityThread"); if (clazz) { const method = clazz.getMethod("currentApplication", null); if (method) { nativeApp = method.invoke(null, null); } } } // 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.") } } return nativeApp; } global.__onLiveSync = function () { if (androidApp && androidApp.paused) { return; } livesync(); }; function initLifecycleCallbacks() { // TODO: Verify whether the logic for triggerring application-wide events based on Activity callbacks is working properly const lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({ onActivityCreated: function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) { // Set app theme after launch screen was used during startup const activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA); if (activityInfo.metaData) { const setThemeOnLaunch = activityInfo.metaData.getInt("SET_THEME_ON_LAUNCH", -1); if (setThemeOnLaunch !== -1) { activity.setTheme(setThemeOnLaunch); } } if (!androidApp.startActivity) { androidApp.startActivity = activity; } androidApp.notify({ eventName: ActivityCreated, object: androidApp, activity: activity, bundle: savedInstanceState }); }, onActivityDestroyed: function (activity: android.app.Activity) { if (activity === androidApp.foregroundActivity) { androidApp.foregroundActivity = undefined; } if (activity === androidApp.startActivity) { androidApp.startActivity = undefined; } androidApp.notify({ eventName: ActivityDestroyed, object: androidApp, activity: activity }); // TODO: This is a temporary workaround to force the V8's Garbage Collector, which will force the related Java Object to be collected. gc(); }, onActivityPaused: function (activity: android.app.Activity) { if ((activity).isNativeScriptActivity) { androidApp.paused = true; notify({ eventName: suspendEvent, object: androidApp, android: activity }); } androidApp.notify({ eventName: ActivityPaused, object: androidApp, activity: activity }); }, onActivityResumed: function (activity: android.app.Activity) { androidApp.foregroundActivity = activity; if ((activity).isNativeScriptActivity) { notify({ eventName: resumeEvent, object: androidApp, android: activity }); androidApp.paused = false; } androidApp.notify({ eventName: ActivityResumed, object: androidApp, activity: activity }); }, onActivitySaveInstanceState: function (activity: android.app.Activity, outState: android.os.Bundle) { androidApp.notify({ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState }); }, onActivityStarted: function (activity: android.app.Activity) { androidApp.notify({ eventName: ActivityStarted, object: androidApp, activity: activity }); }, onActivityStopped: function (activity: android.app.Activity) { androidApp.notify({ eventName: ActivityStopped, object: androidApp, activity: activity }); } }); return lifecycleCallbacks; } let currentOrientation: number; function initComponentCallbacks() { let componentCallbacks = new android.content.ComponentCallbacks2({ onLowMemory: function () { gc(); java.lang.System.gc(); notify({ eventName: 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 newValue; switch (newOrientation) { case android.content.res.Configuration.ORIENTATION_LANDSCAPE: newValue = "landscape"; break; case android.content.res.Configuration.ORIENTATION_PORTRAIT: newValue = "portrait"; break; default: newValue = "unknown"; break; } notify({ eventName: orientationChangedEvent, android: androidApp.nativeApp, newValue: newValue, object: androidApp }); } }); return componentCallbacks; } let BroadcastReceiverClass; function ensureBroadCastReceiverClass() { if (BroadcastReceiverClass) { return; } class BroadcastReceiver extends android.content.BroadcastReceiver { private _onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void; constructor(onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) { super(); this._onReceiveCallback = onReceiveCallback; return global.__native(this); } public onReceive(context: android.content.Context, intent: android.content.Intent) { if (this._onReceiveCallback) { this._onReceiveCallback(context, intent); } } } BroadcastReceiverClass = BroadcastReceiver; } declare namespace com { namespace tns { class NativeScriptApplication extends android.app.Application { static getInstance(): NativeScriptApplication; } } }