@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
1,051 lines • 56.9 kB
JavaScript
import { CoreTypes } from '../core-types';
import { profile } from '../profiling';
import { SDK_VERSION } from '../utils/constants';
import { android as androidUtils } from '../utils';
import { ApplicationCommon, initializeSdkVersionClass } from './application-common';
import { Observable } from '../data/observable';
import { Trace } from '../trace';
import { CommonA11YServiceEnabledObservable, SharedA11YObservable, notifyAccessibilityFocusState, a11yServiceClasses, a11yServiceDisabledClass, a11yServiceEnabledClass, fontScaleCategoryClasses, fontScaleExtraLargeCategoryClass, fontScaleExtraSmallCategoryClass, fontScaleMediumCategoryClass, getCurrentA11YServiceClass, getCurrentFontScaleCategory, getCurrentFontScaleClass, getFontScaleCssClasses, setCurrentA11YServiceClass, setCurrentFontScaleCategory, setCurrentFontScaleClass, setFontScaleCssClasses, setFontScale, getFontScale, setInitFontScale, getFontScaleCategory, setInitAccessibilityCssHelper, FontScaleCategory, getClosestValidFontScale, VALID_FONT_SCALES, AccessibilityRole, AccessibilityState, AndroidAccessibilityEvent, isA11yEnabled, setA11yEnabled, } from '../accessibility/accessibility-common';
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 { enableEdgeToEdge } from '../utils/native-helper-for-android';
import lazy from '../utils/lazy';
let NativeScriptLifecycleCallbacks_;
function initNativeScriptLifecycleCallbacks() {
if (NativeScriptLifecycleCallbacks_) {
return NativeScriptLifecycleCallbacks_;
}
var NativeScriptLifecycleCallbacksImpl = (function (_super) {
__extends(NativeScriptLifecycleCallbacksImpl, _super);
function NativeScriptLifecycleCallbacksImpl() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.activitiesCount = 0;
return _this;
}
NativeScriptLifecycleCallbacksImpl.prototype.onActivityCreated = function (activity, savedInstanceState) {
this.setThemeOnLaunch(activity);
enableEdgeToEdge(activity);
if (!Application.android.startActivity) {
Application.android.setStartActivity(activity);
}
if (!this.nativescriptActivity && "isNativeScriptActivity" in activity) {
this.nativescriptActivity = activity;
}
this.notifyActivityCreated(activity, savedInstanceState);
if (Application.hasListeners(Application.displayedEvent)) {
this.subscribeForGlobalLayout(activity);
}
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivityDestroyed = function (activity) {
if (activity === Application.android.foregroundActivity) {
Application.android.setForegroundActivity(undefined);
}
if (activity === this.nativescriptActivity) {
this.nativescriptActivity = undefined;
}
if (activity === Application.android.startActivity) {
Application.android.setStartActivity(undefined);
if (this.nativescriptActivity) {
Application.android.setStartActivity(this.nativescriptActivity);
}
}
Application.android.notify({
eventName: Application.android.activityDestroyedEvent,
object: Application.android,
activity: activity,
});
gc();
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivityPaused = function (activity) {
if ("isNativeScriptActivity" in activity) {
Application.setSuspended(true, {
android: activity,
activity: activity,
});
}
Application.android.notify({
eventName: Application.android.activityPausedEvent,
object: Application.android,
activity: activity,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivityResumed = function (activity) {
Application.android.setForegroundActivity(activity);
Application.android.notify({
eventName: Application.android.activityResumedEvent,
object: Application.android,
activity: activity,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivitySaveInstanceState = function (activity, bundle) {
Application.android.notify({
eventName: Application.android.saveActivityStateEvent,
object: Application.android,
activity: activity,
bundle: bundle,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivityStarted = function (activity) {
this.activitiesCount++;
if (this.activitiesCount === 1) {
Application.android.setInBackground(false, {
android: activity,
activity: activity,
});
}
Application.android.notify({
eventName: Application.android.activityStartedEvent,
object: Application.android,
activity: activity,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.onActivityStopped = function (activity) {
this.activitiesCount--;
if (this.activitiesCount === 0) {
Application.android.setInBackground(true, {
android: activity,
activity: activity,
});
}
Application.android.notify({
eventName: Application.android.activityStoppedEvent,
object: Application.android,
activity: activity,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.setThemeOnLaunch = function (activity) {
var activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA);
if (activityInfo.metaData) {
var setThemeOnLaunch = activityInfo.metaData.getInt("SET_THEME_ON_LAUNCH", -1);
if (setThemeOnLaunch !== -1) {
activity.setTheme(setThemeOnLaunch);
}
}
};
NativeScriptLifecycleCallbacksImpl.prototype.notifyActivityCreated = function (activity, bundle) {
Application.android.notify({
eventName: Application.android.activityCreatedEvent,
object: Application.android,
activity: activity,
bundle: bundle,
});
};
NativeScriptLifecycleCallbacksImpl.prototype.subscribeForGlobalLayout = function (activity) {
var rootView = activity.getWindow().getDecorView().getRootView();
global.onGlobalLayoutListener = new android.view.ViewTreeObserver.OnGlobalLayoutListener({
onGlobalLayout: function () {
Application.android.notify({
eventName: Application.displayedEvent,
object: Application,
android: Application.android,
activity: activity,
});
var viewTreeObserver = rootView.getViewTreeObserver();
viewTreeObserver.removeOnGlobalLayoutListener(global.onGlobalLayoutListener);
},
});
rootView.getViewTreeObserver().addOnGlobalLayoutListener(global.onGlobalLayoutListener);
};
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityCreated", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityDestroyed", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityPaused", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityResumed", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivitySaveInstanceState", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityStarted", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "onActivityStopped", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "setThemeOnLaunch", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "notifyActivityCreated", null);
__decorate([
profile
], NativeScriptLifecycleCallbacksImpl.prototype, "subscribeForGlobalLayout", null);
NativeScriptLifecycleCallbacksImpl = __decorate([
JavaProxy("org.nativescript.NativeScriptLifecycleCallbacks")
], NativeScriptLifecycleCallbacksImpl);
return NativeScriptLifecycleCallbacksImpl;
}(android.app.Application.ActivityLifecycleCallbacks));
NativeScriptLifecycleCallbacks_ = NativeScriptLifecycleCallbacksImpl;
return NativeScriptLifecycleCallbacks_;
}
let NativeScriptComponentCallbacks_;
function initNativeScriptComponentCallbacks() {
if (NativeScriptComponentCallbacks_) {
return NativeScriptComponentCallbacks_;
}
var NativeScriptComponentCallbacksImpl = (function (_super) {
__extends(NativeScriptComponentCallbacksImpl, _super);
function NativeScriptComponentCallbacksImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
NativeScriptComponentCallbacksImpl.prototype.onLowMemory = function () {
gc();
java.lang.System.gc();
Application.notify({
eventName: Application.lowMemoryEvent,
object: Application,
android: this,
});
};
NativeScriptComponentCallbacksImpl.prototype.onTrimMemory = function (level) {
};
NativeScriptComponentCallbacksImpl.prototype.onConfigurationChanged = function (newConfiguration) {
Application.android.onConfigurationChanged(newConfiguration);
};
__decorate([
profile
], NativeScriptComponentCallbacksImpl.prototype, "onLowMemory", null);
__decorate([
profile
], NativeScriptComponentCallbacksImpl.prototype, "onTrimMemory", null);
__decorate([
profile
], NativeScriptComponentCallbacksImpl.prototype, "onConfigurationChanged", null);
NativeScriptComponentCallbacksImpl = __decorate([
JavaProxy("org.nativescript.NativeScriptComponentCallbacks")
], NativeScriptComponentCallbacksImpl);
return NativeScriptComponentCallbacksImpl;
}(android.content.ComponentCallbacks2));
NativeScriptComponentCallbacks_ = NativeScriptComponentCallbacksImpl;
return NativeScriptComponentCallbacks_;
}
const BroadcastReceiver = lazy(() => {
var BroadcastReceiverImpl = (function (_super) {
__extends(BroadcastReceiverImpl, _super);
function BroadcastReceiverImpl(onReceiveCallback) {
var _this = _super.call(this) || this;
_this._onReceiveCallback = onReceiveCallback;
return global.__native(_this);
}
BroadcastReceiverImpl.prototype.onReceive = function (context, intent) {
if (this._onReceiveCallback) {
this._onReceiveCallback(context, intent);
}
};
return BroadcastReceiverImpl;
}(android.content.BroadcastReceiver));
return BroadcastReceiverImpl;
});
export class AndroidApplication extends ApplicationCommon {
constructor() {
super(...arguments);
this.activityCreatedEvent = AndroidApplication.activityCreatedEvent;
this.activityDestroyedEvent = AndroidApplication.activityDestroyedEvent;
this.activityStartedEvent = AndroidApplication.activityStartedEvent;
this.activityPausedEvent = AndroidApplication.activityPausedEvent;
this.activityResumedEvent = AndroidApplication.activityResumedEvent;
this.activityStoppedEvent = AndroidApplication.activityStoppedEvent;
this.saveActivityStateEvent = AndroidApplication.saveActivityStateEvent;
this.activityResultEvent = AndroidApplication.activityResultEvent;
this.activityBackPressedEvent = AndroidApplication.activityBackPressedEvent;
this.activityNewIntentEvent = AndroidApplication.activityNewIntentEvent;
this.activityRequestPermissionsEvent = AndroidApplication.activityRequestPermissionsEvent;
this._registeredReceivers = {};
this._registeredReceiversById = {};
this._nextReceiverId = 1;
this._pendingReceiverRegistrations = [];
}
init(nativeApp) {
if (this.nativeApp === nativeApp) {
return;
}
if (this.nativeApp) {
throw new Error('Application.android already initialized.');
}
this._nativeApp = nativeApp;
setNativeApp(nativeApp);
this._context = nativeApp.getApplicationContext();
this._packageName = nativeApp.getPackageName();
// we store those callbacks and add a function for clearing them later so that the objects will be eligable for GC
this.lifecycleCallbacks = new (initNativeScriptLifecycleCallbacks())();
this.nativeApp.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
this.componentCallbacks = new (initNativeScriptComponentCallbacks())();
this.nativeApp.registerComponentCallbacks(this.componentCallbacks);
this._registerPendingReceivers();
}
_registerPendingReceivers() {
this._pendingReceiverRegistrations.forEach((info) => this._registerReceiver(this.context, info.intent, info.callback, info.flags, info.id));
this._pendingReceiverRegistrations.length = 0;
}
onConfigurationChanged(configuration) {
this.setOrientation(this.getOrientationValue(configuration));
this.setSystemAppearance(this.getSystemAppearanceValue(configuration));
this.setLayoutDirection(this.getLayoutDirectionValue(configuration));
}
getNativeApplication() {
let nativeApp = this.nativeApp;
if (nativeApp) {
return nativeApp;
}
nativeApp = getNativeApp();
// 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.android.init' method.");
}
return nativeApp;
}
get nativeApp() {
return this._nativeApp;
}
run(entry) {
if (this.started) {
throw new Error('Application is already started.');
}
this.started = true;
setAppMainEntry(typeof entry === 'string' ? { moduleName: entry } : entry);
if (!this.nativeApp) {
const nativeApp = this.getNativeApplication();
this.init(nativeApp);
}
}
get startActivity() {
return androidGetStartActivity();
}
get foregroundActivity() {
return androidGetForegroundActivity();
}
setStartActivity(value) {
androidSetStartActivity(value);
}
setForegroundActivity(value) {
androidSetForegroundActivity(value);
}
get paused() {
return this.suspended;
}
get backgrounded() {
return this.inBackground;
}
get context() {
return this._context;
}
get packageName() {
return this._packageName;
}
// Possible flags are:
// RECEIVER_EXPORTED (2)
// RECEIVER_NOT_EXPORTED (4)
// RECEIVER_VISIBLE_TO_INSTANT_APPS (1)
registerBroadcastReceiver(intentFilter, onReceiveCallback, flags = 2) {
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);
}
};
}
_registerReceiver(context, intentFilter, onReceiveCallback, flags, id) {
var _a;
const receiver = 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 = { receiver, intent: intentFilter, callback: onReceiveCallback, id: typeof id === 'number' ? id : this._nextReceiverId++, flags };
(_a = this._registeredReceivers)[intentFilter] ?? (_a[intentFilter] = []);
this._registeredReceivers[intentFilter].push(receiverInfo);
this._registeredReceiversById[receiverInfo.id] = receiverInfo;
return receiver;
}
unregisterBroadcastReceiver(intentFilter) {
const receivers = this._registeredReceivers[intentFilter];
if (receivers) {
receivers.forEach((receiver) => {
this.context.unregisterReceiver(receiver.receiver);
});
this._registeredReceivers[intentFilter] = [];
}
}
getRegisteredBroadcastReceiver(intentFilter) {
return this._registeredReceivers[intentFilter]?.[0].receiver;
}
getRegisteredBroadcastReceivers(intentFilter) {
const receiversInfo = this._registeredReceivers[intentFilter];
if (receiversInfo) {
return receiversInfo.map((info) => info.receiver);
}
return [];
}
getRootView() {
const activity = this.foregroundActivity || this.startActivity;
if (!activity) {
return undefined;
}
const callbacks = activity['_callbacks'];
setRootView(callbacks ? callbacks.getRootView() : undefined);
return getRootView();
}
resetRootView(entry) {
super.resetRootView(entry);
const activity = this.foregroundActivity || this.startActivity;
if (!activity) {
throw new Error('Cannot find android activity.');
}
// this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry;
const callbacks = activity['_callbacks'];
if (!callbacks) {
throw new Error('Cannot find android activity callbacks.');
}
callbacks.resetActivityContent(activity);
}
getSystemAppearance() {
const resources = this.context.getResources();
const configuration = resources.getConfiguration();
return this.getSystemAppearanceValue(configuration);
}
// https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#configuration_changes
getSystemAppearanceValue(configuration) {
const systemAppearance = configuration.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
switch (systemAppearance) {
case android.content.res.Configuration.UI_MODE_NIGHT_YES:
return 'dark';
case android.content.res.Configuration.UI_MODE_NIGHT_NO:
case android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED:
return 'light';
}
}
getLayoutDirection() {
const resources = this.context.getResources();
const configuration = resources.getConfiguration();
return this.getLayoutDirectionValue(configuration);
}
getLayoutDirectionValue(configuration) {
const layoutDirection = configuration.getLayoutDirection();
switch (layoutDirection) {
case android.view.View.LAYOUT_DIRECTION_LTR:
return CoreTypes.LayoutDirection.ltr;
case android.view.View.LAYOUT_DIRECTION_RTL:
return CoreTypes.LayoutDirection.rtl;
}
}
getOrientation() {
const resources = this.context.getResources();
const configuration = resources.getConfiguration();
return this.getOrientationValue(configuration);
}
getOrientationValue(configuration) {
const orientation = configuration.orientation;
switch (orientation) {
case android.content.res.Configuration.ORIENTATION_LANDSCAPE:
return 'landscape';
case android.content.res.Configuration.ORIENTATION_PORTRAIT:
return 'portrait';
default:
return 'unknown';
}
}
get android() {
// ensures Application.android is defined when running on Android
return this;
}
}
AndroidApplication.activityCreatedEvent = 'activityCreated';
AndroidApplication.activityDestroyedEvent = 'activityDestroyed';
AndroidApplication.activityStartedEvent = 'activityStarted';
AndroidApplication.activityPausedEvent = 'activityPaused';
AndroidApplication.activityResumedEvent = 'activityResumed';
AndroidApplication.activityStoppedEvent = 'activityStopped';
AndroidApplication.saveActivityStateEvent = 'saveActivityState';
AndroidApplication.activityResultEvent = 'activityResult';
AndroidApplication.activityBackPressedEvent = 'activityBackPressed';
AndroidApplication.activityNewIntentEvent = 'activityNewIntent';
AndroidApplication.activityRequestPermissionsEvent = 'activityRequestPermissions';
export * from './application-common';
export const Application = new AndroidApplication();
export const iOSApplication = undefined;
function fontScaleChanged(origFontScale) {
const oldValue = getFontScale();
setFontScale(getClosestValidFontScale(origFontScale));
const currentFontScale = getFontScale();
if (oldValue !== currentFontScale) {
Application.notify({
eventName: Application.fontScaleChangedEvent,
object: Application,
newValue: currentFontScale,
});
}
}
export function getCurrentFontScale() {
setupConfigListener();
return getFontScale();
}
function useAndroidFontScale() {
fontScaleChanged(Number(Application.android.context.getResources().getConfiguration().fontScale));
}
let configChangedCallback;
function setupConfigListener() {
if (configChangedCallback) {
return;
}
Application.off(Application.launchEvent, setupConfigListener);
const context = Application.android?.context;
if (!context) {
Application.on(Application.launchEvent, setupConfigListener);
return;
}
useAndroidFontScale();
configChangedCallback = new android.content.ComponentCallbacks2({
onLowMemory() {
// Dummy
},
onTrimMemory() {
// Dummy
},
onConfigurationChanged(newConfig) {
fontScaleChanged(Number(newConfig.fontScale));
},
});
context.registerComponentCallbacks(configChangedCallback);
Application.on(Application.resumeEvent, useAndroidFontScale);
}
setInitFontScale(setupConfigListener);
function applyRootCssClass(cssClasses, newCssClass) {
const rootView = Application.getRootView();
if (!rootView) {
return;
}
Application.applyCssClass(rootView, cssClasses, newCssClass);
const rootModalViews = rootView._getRootModalViews();
rootModalViews.forEach((rootModalView) => Application.applyCssClass(rootModalView, cssClasses, newCssClass));
}
function applyFontScaleToRootViews() {
const rootView = Application.getRootView();
if (!rootView) {
return;
}
const fontScale = getCurrentFontScale();
rootView.style.fontScaleInternal = fontScale;
const rootModalViews = rootView._getRootModalViews();
rootModalViews.forEach((rootModalView) => (rootModalView.style.fontScaleInternal = fontScale));
}
export function getAndroidAccessibilityManager() {
const context = getNativeApp().getApplicationContext();
if (!context) {
return null;
}
return context.getSystemService(android.content.Context.ACCESSIBILITY_SERVICE);
}
const accessibilityStateEnabledPropName = 'accessibilityStateEnabled';
const touchExplorationStateEnabledPropName = 'touchExplorationStateEnabled';
class AndroidSharedA11YObservable extends SharedA11YObservable {
// @ts-ignore todo: fix
get accessibilityServiceEnabled() {
return !!this[accessibilityStateEnabledPropName] && !!this[touchExplorationStateEnabledPropName];
}
set accessibilityServiceEnabled(v) {
return;
}
}
let accessibilityStateChangeListener;
let touchExplorationStateChangeListener;
let sharedA11YObservable;
function updateAccessibilityState() {
const accessibilityManager = getAndroidAccessibilityManager();
if (!accessibilityManager) {
sharedA11YObservable.set(accessibilityStateEnabledPropName, false);
sharedA11YObservable.set(touchExplorationStateEnabledPropName, false);
return;
}
sharedA11YObservable.set(accessibilityStateEnabledPropName, !!accessibilityManager.isEnabled());
sharedA11YObservable.set(touchExplorationStateEnabledPropName, !!accessibilityManager.isTouchExplorationEnabled());
}
function ensureStateListener() {
if (sharedA11YObservable) {
return sharedA11YObservable;
}
const accessibilityManager = getAndroidAccessibilityManager();
sharedA11YObservable = new AndroidSharedA11YObservable();
if (!accessibilityManager) {
sharedA11YObservable.set(accessibilityStateEnabledPropName, false);
sharedA11YObservable.set(touchExplorationStateEnabledPropName, false);
return sharedA11YObservable;
}
accessibilityStateChangeListener = new android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener({
onAccessibilityStateChanged(enabled) {
updateAccessibilityState();
if (Trace.isEnabled()) {
Trace.write(`AccessibilityStateChangeListener state changed to: ${!!enabled}`, Trace.categories.Accessibility);
}
},
});
accessibilityManager.addAccessibilityStateChangeListener(accessibilityStateChangeListener);
if (SDK_VERSION >= 19) {
touchExplorationStateChangeListener = new android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener({
onTouchExplorationStateChanged(enabled) {
updateAccessibilityState();
if (Trace.isEnabled()) {
Trace.write(`TouchExplorationStateChangeListener state changed to: ${!!enabled}`, Trace.categories.Accessibility);
}
},
});
accessibilityManager.addTouchExplorationStateChangeListener(touchExplorationStateChangeListener);
}
updateAccessibilityState();
Application.on(Application.resumeEvent, updateAccessibilityState);
Application.on(Application.exitEvent, (args) => {
const activity = args.android;
if (activity && !activity.isFinishing()) {
return;
}
const accessibilityManager = getAndroidAccessibilityManager();
if (accessibilityManager) {
if (accessibilityStateChangeListener) {
accessibilityManager.removeAccessibilityStateChangeListener(accessibilityStateChangeListener);
}
if (touchExplorationStateChangeListener) {
accessibilityManager.removeTouchExplorationStateChangeListener(touchExplorationStateChangeListener);
}
}
accessibilityStateChangeListener = null;
touchExplorationStateChangeListener = null;
if (sharedA11YObservable) {
sharedA11YObservable.removeEventListener(Observable.propertyChangeEvent);
sharedA11YObservable = null;
}
Application.off(Application.resumeEvent, updateAccessibilityState);
});
return sharedA11YObservable;
}
export class AccessibilityServiceEnabledObservable extends CommonA11YServiceEnabledObservable {
constructor() {
super(ensureStateListener());
}
}
let accessibilityServiceObservable;
export function ensureClasses() {
if (accessibilityServiceObservable) {
return;
}
setFontScaleCssClasses(new Map(VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`])));
accessibilityServiceObservable = new AccessibilityServiceEnabledObservable();
// Initialize SDK version CSS class once
initializeSdkVersionClass(Application.getRootView());
}
export function updateCurrentHelperClasses(applyRootCssClass) {
const fontScale = getFontScale();
const fontScaleCategory = getFontScaleCategory();
const fontScaleCssClasses = getFontScaleCssClasses();
const oldFontScaleClass = getCurrentFontScaleClass();
if (fontScaleCssClasses.has(fontScale)) {
setCurrentFontScaleClass(fontScaleCssClasses.get(fontScale));
}
else {
setCurrentFontScaleClass(fontScaleCssClasses.get(1));
}
if (oldFontScaleClass !== getCurrentFontScaleClass()) {
applyRootCssClass([...fontScaleCssClasses.values()], getCurrentFontScaleClass());
}
const oldActiveFontScaleCategory = getCurrentFontScaleCategory();
switch (fontScaleCategory) {
case FontScaleCategory.ExtraSmall: {
setCurrentFontScaleCategory(fontScaleExtraSmallCategoryClass);
break;
}
case FontScaleCategory.Medium: {
setCurrentFontScaleCategory(fontScaleMediumCategoryClass);
break;
}
case FontScaleCategory.ExtraLarge: {
setCurrentFontScaleCategory(fontScaleExtraLargeCategoryClass);
break;
}
default: {
setCurrentFontScaleCategory(fontScaleMediumCategoryClass);
break;
}
}
if (oldActiveFontScaleCategory !== getCurrentFontScaleCategory()) {
applyRootCssClass(fontScaleCategoryClasses, getCurrentFontScaleCategory());
}
const oldA11YStatusClass = getCurrentA11YServiceClass();
if (accessibilityServiceObservable.accessibilityServiceEnabled) {
setCurrentA11YServiceClass(a11yServiceEnabledClass);
}
else {
setCurrentA11YServiceClass(a11yServiceDisabledClass);
}
if (oldA11YStatusClass !== getCurrentA11YServiceClass()) {
applyRootCssClass(a11yServiceClasses, getCurrentA11YServiceClass());
}
}
export function initAccessibilityCssHelper() {
ensureClasses();
Application.on(Application.fontScaleChangedEvent, () => {
updateCurrentHelperClasses(applyRootCssClass);
applyFontScaleToRootViews();
});
accessibilityServiceObservable.on(AccessibilityServiceEnabledObservable.propertyChangeEvent, () => updateCurrentHelperClasses(applyRootCssClass));
}
setInitAccessibilityCssHelper(initAccessibilityCssHelper);
let clickableRolesMap = new Set();
let lastFocusedView;
function accessibilityEventHelper(view, eventType) {
const eventName = accessibilityEventTypeMap.get(eventType);
if (!isAccessibilityServiceEnabled()) {
if (Trace.isEnabled()) {
Trace.write(`accessibilityEventHelper: Service not active`, Trace.categories.Accessibility);
}
return;
}
if (!eventName) {
Trace.write(`accessibilityEventHelper: unknown eventType: ${eventType}`, Trace.categories.Accessibility, Trace.messageType.error);
return;
}
if (!view) {
if (Trace.isEnabled()) {
Trace.write(`accessibilityEventHelper: no owner: ${eventName}`, Trace.categories.Accessibility);
}
return;
}
const androidView = view.nativeViewProtected;
if (!androidView) {
if (Trace.isEnabled()) {
Trace.write(`accessibilityEventHelper: no nativeView`, Trace.categories.Accessibility);
}
return;
}
switch (eventType) {
case android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED: {
/**
* Android API >= 26 handles accessibility tap-events by converting them to TYPE_VIEW_CLICKED
* These aren't triggered for custom tap events in NativeScript.
*/
if (SDK_VERSION >= 26) {
// Find all tap gestures and trigger them.
for (const tapGesture of view.getGestureObservers(1) ?? []) {
tapGesture.callback({
android: view.android,
eventName: 'tap',
ios: null,
object: view,
type: 1,
view: view,
});
}
}
return;
}
case android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
const lastView = lastFocusedView?.get();
if (lastView && view !== lastView) {
const lastAndroidView = lastView.nativeViewProtected;
if (lastAndroidView) {
lastAndroidView.clearFocus();
lastFocusedView = null;
notifyAccessibilityFocusState(lastView, false, true);
}
}
lastFocusedView = new WeakRef(view);
notifyAccessibilityFocusState(view, true, false);
return;
}
case android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
const lastView = lastFocusedView?.get();
if (lastView && view === lastView) {
lastFocusedView = null;
androidView.clearFocus();
}
notifyAccessibilityFocusState(view, false, true);
return;
}
}
}
let TNSAccessibilityDelegate;
const androidViewToTNSView = new WeakMap();
let accessibilityEventMap;
let accessibilityEventTypeMap;
function ensureNativeClasses() {
if (TNSAccessibilityDelegate) {
return;
}
// WORKAROUND: Typing refers to android.view.View.androidviewViewAccessibilityDelegate but it is called android.view.View.AccessibilityDelegate at runtime
const AccessibilityDelegate = android.view.View['AccessibilityDelegate'];
const RoleTypeMap = new Map([
[AccessibilityRole.Button, android.widget.Button.class.getName()],
[AccessibilityRole.Search, android.widget.EditText.class.getName()],
[AccessibilityRole.Image, android.widget.ImageView.class.getName()],
[AccessibilityRole.ImageButton, android.widget.ImageButton.class.getName()],
[AccessibilityRole.KeyboardKey, android.inputmethodservice.Keyboard.Key.class.getName()],
[AccessibilityRole.StaticText, android.widget.TextView.class.getName()],
[AccessibilityRole.Adjustable, android.widget.SeekBar.class.getName()],
[AccessibilityRole.Checkbox, android.widget.CheckBox.class.getName()],
[AccessibilityRole.RadioButton, android.widget.RadioButton.class.getName()],
[AccessibilityRole.SpinButton, android.widget.Spinner.class.getName()],
[AccessibilityRole.Switch, android.widget.Switch.class.getName()],
[AccessibilityRole.ProgressBar, android.widget.ProgressBar.class.getName()],
]);
clickableRolesMap = new Set([AccessibilityRole.Button, AccessibilityRole.ImageButton]);
const ignoreRoleTypesForTrace = new Set([AccessibilityRole.Header, AccessibilityRole.Link, AccessibilityRole.None, AccessibilityRole.Summary]);
var TNSAccessibilityDelegateImpl = (function (_super) {
__extends(TNSAccessibilityDelegateImpl, _super);
function TNSAccessibilityDelegateImpl() {
var _this = _super.call(this) || this;
return global.__native(_this);
}
TNSAccessibilityDelegateImpl.prototype.getTnsView = function (androidView) {
var _a;
var view = (_a = androidViewToTNSView.get(androidView)) === null || _a === void 0 ? void 0 : _a.get();
if (!view) {
androidViewToTNSView.delete(androidView);
return null;
}
return view;
};
TNSAccessibilityDelegateImpl.prototype.onInitializeAccessibilityNodeInfo = function (host, info) {
_super.prototype.onInitializeAccessibilityNodeInfo.call(this, host, info);
var view = this.getTnsView(host);
if (!view) {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(host, " ").concat(info, " no tns-view"), Trace.categories.Accessibility);
}
return;
}
var id = host.getTag(androidUtils.resources.getId(":id/nativescript_accessibility_id"));
if (id != null) {
info.setViewIdResourceName(id);
}
var accessibilityRole = view.accessibilityRole;
if (accessibilityRole) {
var androidClassName = RoleTypeMap.get(accessibilityRole);
if (androidClassName) {
var oldClassName = info.getClassName() || (SDK_VERSION >= 28 && host.getAccessibilityClassName()) || null;
info.setClassName(androidClassName);
if (Trace.isEnabled()) {
Trace.write("".concat(view, ".accessibilityRole = \"").concat(accessibilityRole, "\" is mapped to \"").concat(androidClassName, "\" (was ").concat(oldClassName, "). ").concat(info.getClassName()), Trace.categories.Accessibility);
}
}
else if (!ignoreRoleTypesForTrace.has(accessibilityRole)) {
if (Trace.isEnabled()) {
Trace.write("".concat(view, ".accessibilityRole = \"").concat(accessibilityRole, "\" is unknown"), Trace.categories.Accessibility);
}
}
if (clickableRolesMap.has(accessibilityRole)) {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set clickable role=").concat(accessibilityRole), Trace.categories.Accessibility);
}
info.setClickable(true);
}
if (SDK_VERSION >= 28) {
if (accessibilityRole === AccessibilityRole.Header) {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set heading role=").concat(accessibilityRole), Trace.categories.Accessibility);
}
info.setHeading(true);
}
else if (host.isAccessibilityHeading()) {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set heading from host"), Trace.categories.Accessibility);
}
info.setHeading(true);
}
else {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set not heading"), Trace.categories.Accessibility);
}
info.setHeading(false);
}
}
switch (accessibilityRole) {
case AccessibilityRole.Switch:
case AccessibilityRole.RadioButton:
case AccessibilityRole.Checkbox: {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set checkable and check=").concat(view.accessibilityState === AccessibilityState.Checked), Trace.categories.Accessibility);
}
info.setCheckable(true);
info.setChecked(view.accessibilityState === AccessibilityState.Checked);
break;
}
default: {
if (Trace.isEnabled()) {
Trace.write("onInitializeAccessibilityNodeInfo ".concat(view, " - set enabled=").concat(view.accessibilityState !== AccessibilityState.Disabled, " and selected=").concat(view.accessibilityState === AccessibilityState.Selected), Trace.categories.Accessibility);
}
info.setEnabled(view.accessibilityState !== AccessibilityState.Disabled);
info.setSelected(view.accessibilityState === AccessibilityState.Selected);
break;
}
}
}
if (view.accessible) {
info.setFocusable(true);
}
};
TNSAccessibilityDelegateImpl.prototype.sendAccessibilityEvent = function (host, eventType) {
_super.prototype.sendAccessibilityEvent.call(this, host, eventType);
var view = this.getTnsView(host);
if (!view) {
console.log("skip - ".concat(host, " - ").concat(accessibilityEventTypeMap.get(eventType)));
return;
}
try {
accessibilityEventHelper(view, eventType);
}
catch (err) {
console.error(err);
}
};
return TNSAccessibilityDelegateImpl;
}(AccessibilityDelegate));
TNSAccessibilityDelegate = new TNSAccessibilityDelegateImpl();
accessibilityEventMap = new Map([
/**
* Invalid selection/focus position.
*/
[AndroidAccessibilityEvent.INVALID_POSITION, android.view.accessibility.AccessibilityEvent.INVALID_POSITION],
/**
* Maximum length of the text fields.
*/
[AndroidAccessibilityEvent.MAX_TEXT_LENGTH, android.view.accessibility.AccessibilityEvent.MAX_TEXT_LENGTH],
/**
* Represents the event of clicking on a android.view.View like android.widget.Button, android.widget.CompoundButton, etc.
*/
[AndroidAccessibilityEvent.VIEW_CLICKED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED],
/**
* Represents the event of long clicking on a android.view.View like android.widget.Button, android.widget.CompoundButton, etc.
*/
[AndroidAccessibilityEvent.VIEW_LONG_CLICKED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED],
/**
* Represents the event of selecting an item usually in the context of an android.widget.AdapterView.
*/
[AndroidAccessibilityEvent.VIEW_SELECTED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SELECTED],
/**
* Represents the event of setting input focus of a android.view.View.
*/
[AndroidAccessibilityEvent.VIEW_FOCUSED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED],
/**
* Represents the event of changing the text of an android.widget.EditText.
*/
[AndroidAccessibilityEvent.VIEW_TEXT_CHANGED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED],
/**
* Represents the event of opening a android.widget.PopupWindow, android.view.Menu, android.app.Dialog, etc.
*/
[AndroidAccessibilityEvent.WINDOW_STATE_CHANGED, android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED],
/**
* Represents the event showing a android.app.Notification.
*/
[AndroidAccessibilityEvent.NOTIFICATION_STATE_CHANGED, android.view.accessibility.AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED],
/**
* Represents the event of a hover enter over a android.view.View.
*/
[AndroidAccessibilityEvent.VIEW_HOVER_ENTER, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_ENTER],
/**
* Represents the event of a hover exit over a android.view.View.
*/
[AndroidAccessibilityEvent.VIEW_HOVER_EXIT, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT],
/**
* Represents the event of starting a touch exploration gesture.
*/
[AndroidAccessibilityEvent.TOUCH_EXPLORATION_GESTURE_START, android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START],
/**
* Represents the event of ending a touch exploration gesture.
*/
[AndroidAccessibilityEvent.TOUCH_EXPLORATION_GESTURE_END, android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END],
/**
* Represents the event of changing the content of a window and more specifically the sub-tree rooted at the event's source.
*/
[AndroidAccessibilityEvent.WINDOW_CONTENT_CHANGED, android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED],
/**
* Represents the event of scrolling a view.
*/
[AndroidAccessibilityEvent.VIEW_SCROLLED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED],
/**
* Represents the event of changing the selection in an android.widget.EditText.
*/
[AndroidAccessibilityEvent.VIEW_TEXT_SELECTION_CHANGED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED],
/**
* Represents the event of an application making an announcement.
*/
[AndroidAccessibilityEvent.ANNOUNCEMENT, android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT],
/**
* Represents the event of gaining accessibility focus.
*/
[AndroidAccessibilityEvent.VIEW_ACCESSIBILITY_FOCUSED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED],
/**
* Represents the event of clearing accessibility focus.
*/
[AndroidAccessibilityEvent.VIEW_ACCESSIBILITY_FOCUS_CLEARED, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED],
/**
* Represents the event of traversing the text of a view at a given movement granularity.
*/
[AndroidAccessibilityEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY],
/**
* Represents the event of beginning gesture detection.
*/
[AndroidAccessibilityEvent.GESTURE_DETECTION_START, android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START],
/**
* Represents the event of ending gesture detection.
*/
[AndroidAccessibilityEvent.GESTURE_DETECTION_END, android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END],
/**
* Represents the event of the user starting to touch the screen.
*/
[AndroidAccessibilityEvent.TOUCH_INTERACTION_START, android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START],
/**
* Represents the event of the user ending to touch the screen.
*/
[AndroidAccessibilityEvent.TOUCH_INTERACTION_END, android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END],
/**
* Mask for AccessibilityEvent all types.
*/
[AndroidAccessibilityEvent.ALL_MASK, android.view.accessibility.AccessibilityEvent.TYPES_ALL_MASK],
]);
accessibilityEventTypeMap = new Map([...accessibilityEventMap].map(([k, v]) => [v, k]));
}
function updateAccessibilityServiceState() {
const accessibilityManager = getAndroidAccessibilityManager();
if (!accessibilityManager) {
return;
}
setA11yEnabled(!!accessibilityManager.isEnabled() && !!accessibilityManager.isTouchExplorationEnabled());
}
export function isAccessibilityServiceEnabled() {
const accessibilityServiceEnabled = isA11yEnabled();
if (typeof accessibilityServiceEnabled === 'boolean') {
return accessibilityServiceEnabled;
}
const accessibilityManager = getAndroidAccessibilityManager();
accessibilityStateChangeListener = new android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener({
onAccessibilityStateChanged(enabled) {
updateAccessibilityServiceState();
if (Trace.