Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 94 additions & 9 deletions packages/core/application/application.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import {
isA11yEnabled,
setA11yEnabled,
} from '../accessibility/accessibility-common';
import { androidGetForegroundActivity, androidGetStartActivity, androidPendingReceiverRegistrations, androidRegisterBroadcastReceiver, androidRegisteredReceivers, androidSetForegroundActivity, androidSetStartActivity, androidUnregisterBroadcastReceiver, applyContentDescription } from './helpers';
import { androidGetForegroundActivity, androidGetStartActivity, androidSetForegroundActivity, androidSetStartActivity, applyContentDescription } from './helpers';
import { getImageFetcher, getNativeApp, getRootView, initImageCache, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setNativeApp, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
import { getNativeScriptGlobals } from '../globals/global-utils';
import type { AndroidApplication as IAndroidApplication } from './application';
import lazy from '../utils/lazy';

declare class NativeScriptLifecycleCallbacks extends android.app.Application.ActivityLifecycleCallbacks {}

Expand Down Expand Up @@ -276,7 +278,36 @@ function initNativeScriptComponentCallbacks() {
return NativeScriptComponentCallbacks_;
}

export class AndroidApplication extends ApplicationCommon {
interface RegisteredReceiverInfo {
receiver: android.content.BroadcastReceiver;
intent: string;
callback: (context: android.content.Context, intent: android.content.Intent) => void;
id: number;
flags: number;
}

const BroadcastReceiver = lazy(() => {
@NativeClass
class BroadcastReceiverImpl 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);
}
}
}
return BroadcastReceiverImpl;
});

export class AndroidApplication extends ApplicationCommon implements IAndroidApplication {
static readonly activityCreatedEvent = 'activityCreated';
static readonly activityDestroyedEvent = 'activityDestroyed';
static readonly activityStartedEvent = 'activityStarted';
Expand Down Expand Up @@ -332,10 +363,13 @@ export class AndroidApplication extends ApplicationCommon {

this._registerPendingReceivers();
}

private _registeredReceivers: Record<string, RegisteredReceiverInfo[]> = {};
private _registeredReceiversById: Record<number, RegisteredReceiverInfo> = {};
private _nextReceiverId: number = 1;
private _pendingReceiverRegistrations: Omit<RegisteredReceiverInfo, 'receiver'>[] = [];
private _registerPendingReceivers() {
androidPendingReceiverRegistrations.forEach((func) => func(this.context));
androidPendingReceiverRegistrations.length = 0;
this._pendingReceiverRegistrations.forEach((info) => this._registerReceiver(this.context, info.intent, info.callback, info.flags, info.id));
this._pendingReceiverRegistrations.length = 0;
}

onConfigurationChanged(configuration: android.content.res.Configuration): void {
Expand Down Expand Up @@ -414,18 +448,69 @@ export class AndroidApplication extends ApplicationCommon {
// RECEIVER_EXPORTED (2)
// RECEIVER_NOT_EXPORTED (4)
// RECEIVER_VISIBLE_TO_INSTANT_APPS (1)
public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {
androidRegisterBroadcastReceiver(intentFilter, onReceiveCallback, flags);
public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): () => void {
const receiverId = this._nextReceiverId++;
if (this.context) {
this._registerReceiver(this.context, intentFilter, onReceiveCallback, flags, receiverId);
} else {
this._pendingReceiverRegistrations.push({
intent: intentFilter,
callback: onReceiveCallback,
id: receiverId,
flags,
});
}
let removed = false;
return () => {
if (removed) {
return;
}
removed = true;
if (this._registeredReceiversById[receiverId]) {
const receiverInfo = this._registeredReceiversById[receiverId];
this.context.unregisterReceiver(receiverInfo.receiver);
this._registeredReceivers[receiverInfo.intent] = this._registeredReceivers[receiverInfo.intent]?.filter((ri) => ri.id !== receiverId);
delete this._registeredReceiversById[receiverId];
} else {
this._pendingReceiverRegistrations = this._pendingReceiverRegistrations.filter((ri) => ri.id !== receiverId);
}
};
}
private _registerReceiver(context: android.content.Context, intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags: number, id: number): android.content.BroadcastReceiver {
const receiver: android.content.BroadcastReceiver = new (BroadcastReceiver())(onReceiveCallback);
if (SDK_VERSION >= 26) {
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter), flags);
} else {
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter));
}
const receiverInfo: RegisteredReceiverInfo = { receiver, intent: intentFilter, callback: onReceiveCallback, id: typeof id === 'number' ? id : this._nextReceiverId++, flags };
this._registeredReceivers[intentFilter] ??= [];
this._registeredReceivers[intentFilter].push(receiverInfo);
this._registeredReceiversById[receiverInfo.id] = receiverInfo;
return receiver;
}

public unregisterBroadcastReceiver(intentFilter: string): void {
androidUnregisterBroadcastReceiver(intentFilter);
const receivers = this._registeredReceivers[intentFilter];
if (receivers) {
receivers.forEach((receiver) => {
this.context.unregisterReceiver(receiver.receiver);
});
this._registeredReceivers[intentFilter] = [];
}
}

public getRegisteredBroadcastReceiver(intentFilter: string): android.content.BroadcastReceiver | undefined {
return androidRegisteredReceivers[intentFilter];
return this._registeredReceivers[intentFilter]?.[0].receiver;
}

public getRegisteredBroadcastReceivers(intentFilter: string): android.content.BroadcastReceiver[] {
const receiversInfo = this._registeredReceivers[intentFilter];
if (receiversInfo) {
return receiversInfo.map((info) => info.receiver);
}
return [];
}
getRootView(): View {
const activity = this.foregroundActivity || this.startActivity;
if (!activity) {
Expand Down
9 changes: 8 additions & 1 deletion packages/core/application/application.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ export class AndroidApplication extends ApplicationCommon {
* For more information, please visit 'http://developer.android.com/reference/android/content/Context.html#registerReceiver%28android.content.BroadcastReceiver,%20android.content.IntentFilter%29'
* @param intentFilter A string containing the intent filter.
* @param onReceiveCallback A callback function that will be called each time the receiver receives a broadcast.
* @return A function that can be called to unregister the receiver.
*/
registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void): void;
registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void): () => void;

/**
* Unregister a previously registered BroadcastReceiver.
Expand All @@ -126,8 +127,14 @@ export class AndroidApplication extends ApplicationCommon {
/**
* Get a registered BroadcastReceiver, then you can get the result code of BroadcastReceiver in onReceiveCallback method.
* @param intentFilter A string containing the intent filter.
* @deprecated Use `getRegisteredBroadcastReceivers` instead.
*/
getRegisteredBroadcastReceiver(intentFilter: string): android.content.BroadcastReceiver;
/**
* Get all registered BroadcastReceivers for a specific intent filter.
* @param intentFilter a string containing the intent filter
*/
getRegisteredBroadcastReceivers(intentFilter: string): android.content.BroadcastReceiver[];

on(event: 'activityCreated', callback: (args: AndroidActivityBundleEventData) => void, thisArg?: any): void;
on(event: 'activityDestroyed', callback: (args: AndroidActivityEventData) => void, thisArg?: any): void;
Expand Down
35 changes: 32 additions & 3 deletions packages/core/application/application.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,28 @@ import {
setA11yEnabled,
enforceArray,
} from '../accessibility/accessibility-common';
import { iosAddNotificationObserver, iosRemoveNotificationObserver } from './helpers';
import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';

@NativeClass
class NotificationObserver extends NSObject {
private _onReceiveCallback: (notification: NSNotification) => void;

public static initWithCallback(onReceiveCallback: (notification: NSNotification) => void): NotificationObserver {
const observer = <NotificationObserver>super.new();
observer._onReceiveCallback = onReceiveCallback;

return observer;
}

public onReceive(notification: NSNotification): void {
this._onReceiveCallback(notification);
}

public static ObjCExposedMethods = {
onReceive: { returns: interop.types.void, params: [NSNotification] },
};
}

@NativeClass
class CADisplayLinkTarget extends NSObject {
private _owner: WeakRef<iOSApplication>;
Expand Down Expand Up @@ -243,6 +262,8 @@ export class iOSApplication extends ApplicationCommon {
private _primaryScene: UIWindowScene | null = null;
private _openedScenesById = new Map<string, UIWindowScene>();

private _notificationObservers: NotificationObserver[] = [];

displayedOnce = false;
displayedLinkTarget: CADisplayLinkTarget;
displayedLink: CADisplayLink;
Expand Down Expand Up @@ -477,11 +498,19 @@ export class iOSApplication extends ApplicationCommon {
}

addNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void) {
return iosAddNotificationObserver(notificationName, onReceiveCallback);
const observer = NotificationObserver.initWithCallback(onReceiveCallback);
NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(observer, 'onReceive', notificationName, null);
this._notificationObservers.push(observer);

return observer;
}

removeNotificationObserver(observer: any /* NotificationObserver */, notificationName: string) {
iosRemoveNotificationObserver(observer, notificationName);
const index = this._notificationObservers.indexOf(observer);
if (index >= 0) {
this._notificationObservers.splice(index, 1);
NSNotificationCenter.defaultCenter.removeObserverNameObject(observer, notificationName, null);
}
}

protected getSystemAppearance(): 'light' | 'dark' {
Expand Down
65 changes: 0 additions & 65 deletions packages/core/application/helpers.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,68 +26,6 @@ function getApplicationContext(): android.content.Context {
return getNativeApp<android.app.Application>().getApplicationContext();
}

export const androidRegisteredReceivers: { [key: string]: android.content.BroadcastReceiver } = {};
export const androidPendingReceiverRegistrations = new Array<(context: android.content.Context) => void>();

declare class BroadcastReceiver extends android.content.BroadcastReceiver {
constructor(onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void);
}

let BroadcastReceiver_: typeof BroadcastReceiver;
function initBroadcastReceiver() {
if (BroadcastReceiver_) {
return BroadcastReceiver_;
}

@NativeClass
class BroadcastReceiverImpl 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);
}
}
}

BroadcastReceiver_ = BroadcastReceiverImpl;
return BroadcastReceiver_;
}

export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {
const registerFunc = (context: android.content.Context) => {
const receiver: android.content.BroadcastReceiver = new (initBroadcastReceiver())(onReceiveCallback);
if (SDK_VERSION >= 26) {
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter), flags);
} else {
context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter));
}
androidRegisteredReceivers[intentFilter] = receiver;
};

if (getApplicationContext()) {
registerFunc(getApplicationContext());
} else {
androidPendingReceiverRegistrations.push(registerFunc);
}
}

export function androidUnregisterBroadcastReceiver(intentFilter: string): void {
const receiver = androidRegisteredReceivers[intentFilter];
if (receiver) {
getApplicationContext().unregisterReceiver(receiver);
androidRegisteredReceivers[intentFilter] = undefined;
delete androidRegisteredReceivers[intentFilter];
}
}

export function updateContentDescription(view: any /* View */, forceUpdate?: boolean): string | null {
if (!view.nativeViewProtected) {
return;
Expand Down Expand Up @@ -204,6 +142,3 @@ export function setupAccessibleView(view: any /* any */): void {
}

// stubs
export const iosNotificationObservers: Array<any> = [];
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: any) => void) {}
export function iosRemoveNotificationObserver(observer: any, notificationName: string) {}
10 changes: 0 additions & 10 deletions packages/core/application/helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,8 @@ export function setupAccessibleView(view: View): void;
export const updateContentDescription: (view: any /* View */, forceUpdate?: boolean) => string | null;
export function applyContentDescription(view: any /* View */, forceUpdate?: boolean);
/* Android app-wide helpers */
export const androidRegisteredReceivers: { [key: string]: android.content.BroadcastReceiver };
export const androidPendingReceiverRegistrations: Array<(context: android.content.Context) => void>;
export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void;
export function androidUnregisterBroadcastReceiver(intentFilter: string): void;
export function androidGetCurrentActivity(): androidx.appcompat.app.AppCompatActivity;
export function androidGetForegroundActivity(): androidx.appcompat.app.AppCompatActivity;
export function androidSetForegroundActivity(activity: androidx.appcompat.app.AppCompatActivity): void;
export function androidGetStartActivity(): androidx.appcompat.app.AppCompatActivity;
export function androidSetStartActivity(activity: androidx.appcompat.app.AppCompatActivity): void;

/* iOS app-wide helpers */
export const iosNotificationObservers: NotificationObserver[];
class NotificationObserver extends NSObject {}
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void): NotificationObserver;
export function iosRemoveNotificationObserver(observer: NotificationObserver, notificationName: string): void;
43 changes: 0 additions & 43 deletions packages/core/application/helpers.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,12 @@ export const updateContentDescription = (view: any /* View */, forceUpdate?: boo
export function applyContentDescription(view: any /* View */, forceUpdate?: boolean) {
return null;
}
export const androidRegisteredReceivers = undefined;
export const androidPendingReceiverRegistrations = undefined;
export function androidRegisterBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void, flags = 2): void {}
export function androidUnregisterBroadcastReceiver(intentFilter: string): void {}
export function androidGetCurrentActivity() {}
export function androidGetForegroundActivity() {}
export function androidSetForegroundActivity(activity: androidx.appcompat.app.AppCompatActivity): void {}
export function androidGetStartActivity() {}
export function androidSetStartActivity(activity: androidx.appcompat.app.AppCompatActivity): void {}

@NativeClass
class NotificationObserver extends NSObject {
private _onReceiveCallback: (notification: NSNotification) => void;

public static initWithCallback(onReceiveCallback: (notification: NSNotification) => void): NotificationObserver {
const observer = <NotificationObserver>super.new();
observer._onReceiveCallback = onReceiveCallback;

return observer;
}

public onReceive(notification: NSNotification): void {
this._onReceiveCallback(notification);
}

public static ObjCExposedMethods = {
onReceive: { returns: interop.types.void, params: [NSNotification] },
};
}

export const iosNotificationObservers: NotificationObserver[] = [];
export function iosAddNotificationObserver(notificationName: string, onReceiveCallback: (notification: NSNotification) => void) {
const observer = NotificationObserver.initWithCallback(onReceiveCallback);
NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(observer, 'onReceive', notificationName, null);
iosNotificationObservers.push(observer);

return observer;
}

export function iosRemoveNotificationObserver(observer: NotificationObserver, notificationName: string) {
// TODO: test if this finds the right observer instance match everytime
// after circular dependencies are resolved
const index = iosNotificationObservers.indexOf(observer);
if (index >= 0) {
iosNotificationObservers.splice(index, 1);
NSNotificationCenter.defaultCenter.removeObserverNameObject(observer, notificationName, null);
}
}

export function setupAccessibleView(view: any /* any */): void {
const uiView = view.nativeViewProtected as UIView;
if (!uiView) {
Expand Down
Loading