UNPKG

@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
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.