mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(android): support independent broadcast listeners (#10936)
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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';
|
||||
@@ -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 {
|
||||
@@ -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) {
|
||||
|
||||
9
packages/core/application/application.d.ts
vendored
9
packages/core/application/application.d.ts
vendored
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
@@ -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;
|
||||
@@ -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' {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
packages/core/application/helpers.d.ts
vendored
10
packages/core/application/helpers.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getNativeApp } from '../application/helpers-common';
|
||||
import { androidRegisterBroadcastReceiver, androidUnregisterBroadcastReceiver } from '../application/helpers';
|
||||
import { SDK_VERSION } from '../utils/constants';
|
||||
import { Application } from '../application';
|
||||
|
||||
export enum connectionType {
|
||||
none = 0,
|
||||
@@ -110,7 +110,7 @@ function startMonitoringLegacy(connectionTypeChangedCallback) {
|
||||
connectionTypeChangedCallback(newConnectionType);
|
||||
};
|
||||
const zoneCallback = zonedCallback(onReceiveCallback);
|
||||
androidRegisterBroadcastReceiver(android.net.ConnectivityManager.CONNECTIVITY_ACTION, zoneCallback);
|
||||
Application.android.registerBroadcastReceiver(android.net.ConnectivityManager.CONNECTIVITY_ACTION, zoneCallback);
|
||||
}
|
||||
|
||||
let callback;
|
||||
@@ -171,6 +171,6 @@ export function stopMonitoring(): void {
|
||||
callback = null;
|
||||
}
|
||||
} else {
|
||||
androidUnregisterBroadcastReceiver(android.net.ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
Application.android.unregisterBroadcastReceiver(android.net.ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Init globals first (use import to ensure it's always at the top)
|
||||
import './globals';
|
||||
export * from './application';
|
||||
export { androidRegisterBroadcastReceiver, androidUnregisterBroadcastReceiver, androidRegisteredReceivers, iosAddNotificationObserver, iosRemoveNotificationObserver, iosNotificationObservers } from './application/helpers';
|
||||
export { getNativeApp, setNativeApp } from './application/helpers-common';
|
||||
export * as ApplicationSettings from './application-settings';
|
||||
import * as Accessibility from './accessibility';
|
||||
|
||||
Reference in New Issue
Block a user