Files
NativeScript/tns-core-modules/application/application.android.ts
Hristo Hristov e6250e718a Disable recycling of native views
createNativeView will set iOS nativeView if it is null/undefined
2017-03-28 18:08:59 +03:00

315 lines
12 KiB
TypeScript

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 '<application-module>.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(<AndroidActivityBundleEventData>{ 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(<AndroidActivityEventData>{ 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 ((<any>activity).isNativeScriptActivity) {
androidApp.paused = true;
notify(<ApplicationEventData>{ eventName: suspendEvent, object: androidApp, android: activity });
}
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityPaused, object: androidApp, activity: activity });
},
onActivityResumed: function (activity: android.app.Activity) {
androidApp.foregroundActivity = activity;
if ((<any>activity).isNativeScriptActivity) {
notify(<ApplicationEventData>{ eventName: resumeEvent, object: androidApp, android: activity });
androidApp.paused = false;
}
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityResumed, object: androidApp, activity: activity });
},
onActivitySaveInstanceState: function (activity: android.app.Activity, outState: android.os.Bundle) {
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState });
},
onActivityStarted: function (activity: android.app.Activity) {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStarted, object: androidApp, activity: activity });
},
onActivityStopped: function (activity: android.app.Activity) {
androidApp.notify(<AndroidActivityEventData>{ 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(<ApplicationEventData>{ 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(<OrientationChangedEventData>{
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;
}
}
}