// noinspection JSUnusedLocalSymbols,UnnecessaryLocalVariableJS /** * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Notice} */ module.exports = function (scriptRuntime, scope) { const AppUtils = org.autojs.autojs.runtime.api.AppUtils; const NotificationUtils = org.autojs.autojs.util.NotificationUtils; const NotificationCompat = androidx.core.app.NotificationCompat; const Builder = androidx.core.app.NotificationCompat.Builder; const BigTextStyle = androidx.core.app.NotificationCompat.BigTextStyle; const Notification = android.app.Notification; const NotificationManager = android.app.NotificationManager; // @Caution by SuperMonster003 on May 6, 2023. // ! On device running with Android 7.x, // ! importing NotificationManagerCompat will cause java.lang.ClassNotFoundException: // ! Didn't find class "android.app.NotificationChannel" on path: DexPathList ... // const NotificationManagerCompat = androidx.core.app.NotificationManagerCompat; const configDefaults = { /** * @type {number} * @const */ defaultStaticNotificationId: (/* @IIFE */ () => { // noinspection JSValidateTypes return new java.lang.String('script_notification').hashCode(); })(), /** * @type {string} * @const */ defaultStaticChannelId: 'script_channel', useScriptNameAsDefaultChannelId: true, useDynamicDefaultNotificationId: true, enableChannelInvalidModificationWarnings: true, }; const config = (/* @IIFE */ () => { /** * @implements {Notice.Config} */ const Config = function () { /* Empty body. */ }; Config.prototype = { constructor: Config, useScriptNameAsDefaultChannelId: configDefaults.useScriptNameAsDefaultChannelId, useDynamicDefaultNotificationId: configDefaults.useDynamicDefaultNotificationId, enableChannelInvalidModificationWarnings: configDefaults.enableChannelInvalidModificationWarnings, defaultTitle: null, defaultContent: null, defaultBigContent: null, defaultAppendScriptName: null, defaultAutoCancel: null, defaultIsSilent: null, defaultPriority: null, defaultChannelName: null, defaultChannelDescription: null, defaultImportanceForChannel: null, defaultEnableVibrationForChannel: null, defaultVibrationPatternForChannel: null, defaultEnableLightsForChannel: null, defaultLightColorForChannel: null, defaultLockscreenVisibilityForChannel: null, }; return new Config(); })(); const service = context.getSystemService(NotificationManager); const channel = (/* @IIFE */ () => { /** * @implements {Notice.Channel} */ const Channel = function () { /* Empty body. */ }; Channel.prototype = { constructor: Channel, create(id, options) { if (species.isObject(arguments[0])) { /** @type {Notice.Channel.Options} */ let opt = arguments[0]; return this.create(opt.id, opt); } /** @type {Notice.Channel.Options} */ let opt = options || {}; let niceId = _.getNiceChannelId(id); let niceName = (/* @IIFE */ () => { if ('name' in opt) { return String(opt.name); } let v = config.defaultChannelName; return isNullish(v) ? null : String(v); })(); let niceDescription = (/* @IIFE */ () => { if ('description' in opt) { return String(opt.description); } let v = config.defaultChannelDescription; return isNullish(v) ? null : String(v); })(); let niceImportance = (() => { let importance = 'importance' in opt ? opt.importance : config.defaultImportanceForChannel; if (isNullish(importance)) { return null; } if (typeof importance === 'number') { return importance; } if (typeof importance === 'string') { switch (importance.toLowerCase()) { case 'unspecified': return NotificationManager.IMPORTANCE_UNSPECIFIED; case 'none': return NotificationManager.IMPORTANCE_NONE; case 'min': return NotificationManager.IMPORTANCE_MIN; case 'low': return NotificationManager.IMPORTANCE_LOW; case 'default': return NotificationManager.IMPORTANCE_DEFAULT; case 'high': return NotificationManager.IMPORTANCE_HIGH; case 'max': return NotificationManager.IMPORTANCE_MAX; default: throw TypeError(`Unknown importance (${importance})`); } } throw TypeError(`Unknown species of importance (${species(importance)})`); })(); let niceEnableVibration = (/* @IIFE */ () => { if ('enableVibration' in opt) { return Boolean(opt.enableVibration); } let v = config.defaultEnableVibrationForChannel; return isNullish(v) ? null : Boolean(v); })(); let niceVibrationPattern = (/* @IIFE */ () => { if ('vibrationPattern' in opt) { return opt.vibrationPattern; } let v = config.defaultVibrationPatternForChannel; return isNullish(v) ? null : v; })(); let niceEnableLights = (/* @IIFE */ () => { if ('enableLights' in opt) { return Boolean(opt.enableLights); } let v = config.defaultEnableLightsForChannel; return isNullish(v) ? null : Boolean(v); })(); let niceLightColor = (/* @IIFE */ () => { if ('lightColor' in opt) { return s13n.color(opt.lightColor); } let v = config.defaultLightColorForChannel; return isNullish(v) ? null : s13n.color(v); })(); let niceLockscreenVisibility = (() => { let lockscreenVisibility = 'lockscreenVisibility' in opt ? opt.lockscreenVisibility : config.defaultLockscreenVisibilityForChannel; if (isNullish(lockscreenVisibility)) { return null; } if (typeof lockscreenVisibility === 'number') { return lockscreenVisibility; } if (typeof lockscreenVisibility === 'string') { switch (lockscreenVisibility.toLowerCase()) { case 'no_override' : return NotificationManager.VISIBILITY_NO_OVERRIDE; case 'public' : return Notification.VISIBILITY_PUBLIC; case 'private' : return Notification.VISIBILITY_PRIVATE; case 'secret' : return Notification.VISIBILITY_SECRET; default: throw TypeError(`Unknown lockscreenVisibility "${lockscreenVisibility}" in ${'lockscreenVisibility' in opt ? 'options' : 'config'}`); } } throw TypeError(`Unknown species "${species(lockscreenVisibility)}" of lockscreenVisibility`); })(); (function invalidModificationWarnings$iiFe() { let channel = this; if (!config.enableChannelInvalidModificationWarnings || !channel.contains(niceId)) { return; } Object.keys(opt).forEach((k) => { if (k === 'name' || k === 'description') { return; } if (k === 'importance') { let channelImportance = channel.get(niceId).getImportance(); if (channel.get(niceId).hasUserSetImportance()) { console.warn(`Property "${k}" for channel with ID "${niceId}" will not take effect, ` + `as the user has altered importance on this channel`); } else if (niceImportance > channelImportance) { console.warn(`Property "${k}" for channel with ID "${niceId}" will not take effect, ` + `as the new ${k} "${opt[k]}" should not be higher than the current value "${channelImportance}"`); } } else { console.warn(`Property "${k}" for channel with ID "${niceId}" will not take effect, ` + `as "${k}" of channel can not be modified programmatically ` + `after the channel has created and submitted to the notification manager`); } }); }).call(this); NotificationUtils.createChannel( niceId, /** @type {string} */ niceName, /** @type {string} */ niceDescription, /** @type {java.lang.Integer} */ niceImportance, /** @type {java.lang.Boolean} */ niceEnableVibration, /** @type {number[]} */ niceVibrationPattern, /** @type {java.lang.Boolean} */ niceEnableLights, /** @type {java.lang.Integer} */ niceLightColor, /** @type {java.lang.Integer} */ niceLockscreenVisibility, ); return niceId; }, createIfNeeded(id, options) { let channelId = (/* @IIFE */ () => { if (species.isObject(arguments[0])) { /** @type {Notice.Options} */ let opt = arguments[0]; id = opt.channelId; } return _.getNiceChannelId(id); })(); if (!this.contains(channelId)) { this.create(channelId, species.isObject(arguments[0]) ? /* as options */ arguments[0] : options); } }, remove(id) { if (!this.contains(id)) { return false; } service.deleteNotificationChannel(id); return true; }, contains(id) { return this.getAll().some(o => o.getId() === id); }, get(id) { if (isNullish(id)) { return null; } if (util.version.sdkInt < util.versionCodes.O) { return null; } return service.getNotificationChannel(String(id)); }, getAll() { if (util.version.sdkInt < util.versionCodes.O) { return []; } return service.getNotificationChannels().toArray(); }, }; return new Channel(); })(); let _ = { NoticeCtor: (/* @IIFE */ () => { /** * @implements Internal.Notice */ const NoticeCtor = function () { /** @global */ const notice = function () { if (arguments[0] instanceof Builder) { // @Signature notice(builder: androidx.core.app.NotificationCompat.Builder, options?: Notice.Options): void; let signature = `notice(builder: androidx.core.app.NotificationCompat.Builder, options?: Notice.Options): void`; /** @type {androidx.core.app.NotificationCompat.Builder} */ let builder = arguments[0]; if (arguments.length > 2) { throw TypeError(`"${signature}" can't take more than 2 arguments`); } if (arguments.length > 1) { if (!species.isObject(arguments[1])) { throw TypeError(`"${signature}" invoked with incorrect type of argument "options"`); } } /** @type {Notice.Options} */ let opt = Object.create(arguments[1] || {}); let niceChannelId = _.getNiceChannelId(opt.channelId); builder.setChannelId(niceChannelId); channel.createIfNeeded(niceChannelId); _.appendScriptName(opt); if (!isNullish(opt.title)) { builder.setContentTitle(String(opt.title)); } if (!isNullish(opt.content)) { builder.setContentText(String(opt.content)); } if (!isNullish(opt.bigContent)) { builder.setStyle(new BigTextStyle().bigText(String(opt.bigContent))); } let niceNotificationId = _.getNiceNotificationId(opt); NotificationUtils.notice( builder, /** @type {?java.lang.Integer} */ niceNotificationId, /** @type {?java.lang.Boolean} */ _.getNiceAutoCancel(opt), /** @type {?java.lang.Boolean} */ _.getNiceIsSilent(opt), /** @type {?Intent} */ _.getNiceIntent(opt), /** @type {?java.lang.Integer} */ _.getNicePriority(opt), ); return niceNotificationId; } let asContentType = o => typeof o === 'string' || o === null; if (asContentType(arguments[0]) && !asContentType(arguments[1])) { // @Signature notice(content: string, options?: Notice.Options): void; let signature = `notice(content: string, options?: Notice.Options): void`; if (arguments.length > 2) { throw TypeError(`"${signature}" can't take more than 2 arguments`); } if (arguments.length > 1) { if (!species.isObject(arguments[1])) { throw TypeError(`"${signature}" invoked with incorrect type of argument "options"`); } } let opt = Object.create(arguments[1] || {}); if (isNullish(opt.content)) { opt.content = arguments[0]; } return notice(opt); } if (asContentType(arguments[0]) && asContentType(arguments[1])) { // @Signature notice(title: string, content: string, options?: Notice.Options): void; let signature = `notice(title: string, content: string, options?: Notice.Options): void`; if (arguments.length > 3) { throw TypeError(`"${signature}" can't take more than 3 arguments`); } if (arguments.length > 2) { if (!species.isObject(arguments[2])) { throw TypeError(`"${signature}" invoked with incorrect type of argument "options"`); } } let title = arguments[0]; let content = arguments[1]; /** @type {Notice.Options} */ let opt = Object.create(arguments[2] || {}); if (isNullish(opt.title) && !isNullish(title)) { opt.title = title; } if (isNullish(opt.content) && !isNullish(content)) { opt.content = content; } return notice(opt); } // @Signature notice(options: Notice.Options): void; /** @type {Notice.Options} */ let opt = Object.create(arguments[0] || {}); if (isNullish(opt.title) && isNullish(opt.content) && isNullish(opt.bigContent)) { opt.title = NotificationUtils.defaultTitle; opt.content = NotificationUtils.defaultContent; } _.appendScriptName(opt); let niceChannelId = _.getNiceChannelId(opt.channelId); let niceTitle = (/* @IIFE */ () => { if (!isNullish(opt.title)) { return String(opt.title); } let v = config.defaultTitle; return isNullish(v) ? null : String(v); })(); let niceContent = (/* @IIFE */ () => { if (!isNullish(opt.content)) { return String(opt.content); } let v = config.defaultContent; return isNullish(v) ? null : String(v); })(); let niceBigContent = (/* @IIFE */ () => { if (!isNullish(opt.bigContent)) { return String(opt.bigContent); } let v = config.defaultBigContent; return isNullish(v) ? null : String(v); })(); channel.createIfNeeded(niceChannelId); let niceNotificationId = _.getNiceNotificationId(opt); NotificationUtils.notice( niceChannelId, niceTitle, niceContent, niceBigContent, /** @type {?java.lang.Integer} */ niceNotificationId, /** @type {?java.lang.Boolean} */ _.getNiceAutoCancel(opt), /** @type {?java.lang.Boolean} */ _.getNiceIsSilent(opt), /** @type {?Intent} */ _.getNiceIntent(opt), /** @type {?java.lang.Integer} */ _.getNicePriority(opt), ); return niceNotificationId; }; return Object.assign(notice, NoticeCtor.prototype); }; NoticeCtor.prototype = { constructor: NoticeCtor, channel: channel, isEnabled() { return NotificationUtils.isEnabled(); }, ensureEnabled() { NotificationUtils.ensureEnabled(); }, launchSettings() { NotificationUtils.launchSettings(); }, config(cfg) { util.ensureObjectType(cfg); Object.entries(cfg).forEach((entry) => { let [ k, v ] = entry; if (!(k in config && isPrimitive(config[k]))) { throw TypeError(`Unknown key "${k}" for notice.config`); } if (isNullish(v) /* to reset */) { config[k] = k in configDefaults ? configDefaults[k] : null; } else { config[k] = v; } }); }, cancel(id) { if (!isNullish(id)) { let niceId = Number(id); if (!isNaN(niceId)) { if (util.version.sdkInt >= util.versionCodes.O) { androidx.core.app.NotificationManagerCompat.from(context).cancel(niceId); } } } }, getBuilder: () => { let builder = NotificationUtils.getSimpleBuilder(); if (!isNullish(config.defaultPriority)) { builder.setPriority(_.getNicePriority({ priority: config.defaultPriority })); } return builder; }, }; return NoticeCtor; })(), getNiceChannelId(id) { if (!isNullish(id)) { return String(id); } return config.useScriptNameAsDefaultChannelId ? _.getDefaultChannelId() : configDefaults.defaultStaticChannelId; }, getDefaultChannelId() { return engines.myEngine().getSource().getFullName(); }, /** @returns {number} */ getNiceNotificationId(opt) { if ('notificationId' in opt) { let id = Number(opt.notificationId); if (!isNaN(id)) { return id; } } return config.useDynamicDefaultNotificationId ? NotificationUtils.getDefaultNotificationId() : configDefaults.defaultStaticNotificationId; }, getNiceIntent(opt) { let intent = opt.intent; if (typeof intent === 'string') { if (AppUtils.isActivityShortForm(intent)) { let prop = runtime.getProperty(`class.${intent}`); if (!prop) { throw Error(`Activity short form ${intent} not found`); } return new Intent(context, prop).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } let rexWebSiteWithoutProtocol = /^(www.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#?\/?[a-zA-Z0-9#]+)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/; if (rexWebSiteWithoutProtocol.test(intent)) { // noinspection HttpUrlsUsage intent = `http://${intent}`; } return app.intent({ data: intent }); } if (species.isObject(intent)) { return app.intent(intent); } return intent instanceof Intent ? intent : null; }, getNicePriority(opt) { let priority = 'priority' in opt ? opt.priority : null; if (isNullish(priority)) { return null; } if (typeof priority === 'number') { return priority; } if (typeof priority === 'string') { switch (priority.toLowerCase()) { case 'default': return NotificationCompat.PRIORITY_DEFAULT; case 'low': return NotificationCompat.PRIORITY_LOW; case 'min': return NotificationCompat.PRIORITY_MIN; case 'high': return NotificationCompat.PRIORITY_HIGH; case 'max': return NotificationCompat.PRIORITY_MAX; default: throw TypeError(`Unknown priority (${priority})`); } } throw TypeError(`Unknown species of priority (${species(priority)})`); }, getNiceAutoCancel(opt) { if ('autoCancel' in opt) { return opt.autoCancel; } let v = config.defaultAutoCancel; return isNullish(v) ? null : Boolean(v); }, getNiceIsSilent(opt) { if ('isSilent' in opt) { return opt.isSilent; } let v = config.defaultIsSilent; return isNullish(v) ? null : Boolean(v); }, appendScriptName(opt) { util.ensureObjectType(opt); let strategy = opt.appendScriptName; if (strategy === false) { return; } if (isNullish(strategy)) { let def = config.defaultAppendScriptName; if (isNullish(def) || def === false) { return; } strategy = def; } let append = strategy === true ? 'auto' : String(strategy); let scriptName = _.getDefaultChannelId(); switch (append) { case 'auto': if (!isNullish(opt.bigContent)) { opt.bigContent = `${opt.bigContent} (${scriptName})`; } else if (!isNullish(opt.content)) { opt.content = `${opt.content} (${scriptName})`; } else if (!isNullish(opt.title)) { opt.title = `${opt.title} (${scriptName})`; } break; case 'title': opt.title = isNullish(opt.title) ? scriptName : `${opt.title} (${scriptName})`; break; case 'content': opt.content = isNullish(opt.content) ? scriptName : `${opt.content} (${scriptName})`; break; case 'bigContent': opt.bigContent = isNullish(opt.bigContent) ? scriptName : `${opt.bigContent} (${scriptName})`; break; default: throw TypeError(`Unknown destination (${append}) for script name to append to`); } }, }; /** * @type {Internal.Notice} */ const notice = new _.NoticeCtor(); return notice; };