diff --git a/uni_modules/uni-push/changelog.md b/uni_modules/uni-push/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uni_modules/uni-push/package.json b/uni_modules/uni-push/package.json new file mode 100644 index 0000000000000000000000000000000000000000..7674d4c16b2f4a8e848dafab5f70dddf0f1e2cf3 --- /dev/null +++ b/uni_modules/uni-push/package.json @@ -0,0 +1,125 @@ +{ + "id": "uni-push", + "displayName": "uni-push", + "version": "1.0.0", + "description": "uni-push", + "keywords": [ + "uni-push" + ], + "repository": "", + "engines": { + "HBuilderX": "^3.6.8" + }, + "dcloudext": { + "type": "uts", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "", + "data": "", + "permissions": "" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "uni-ext-api": { + "uni": { + "getPushClientId": { + "name": "getPushClientId", + "app": { + "js": false, + "kotlin": true, + "swift": false + } + }, + "onPushMessage": { + "name": "onPushMessage", + "app": { + "js": false, + "kotlin": true, + "swift": false + } + }, + "offPushMessage": { + "name": "offPushMessage", + "app": { + "js": false, + "kotlin": true, + "swift": false + } + }, + "getChannelManager": { + "name": "getChannelManager", + "app": { + "js": false, + "kotlin": true, + "swift": false + } + }, + "createPushMessage": { + "name": "createPushMessage", + "app": { + "js": false, + "kotlin": true, + "swift": false + } + } + } + }, + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "u", + "aliyun": "u" + }, + "client": { + "Vue": { + "vue2": "u", + "vue3": "u" + }, + "App": { + "app-android": "u", + "app-ios": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-push/readme.md b/uni_modules/uni-push/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..c5dde5d47b0ebff0c34cf361d7140765c1893b44 --- /dev/null +++ b/uni_modules/uni-push/readme.md @@ -0,0 +1,142 @@ +# uni-push + +`uni-push` 工程,是基于 DCloud-UTS 架构之上的封装个推消息推送 `SDK` 的插件工程,使用此模块可轻松实现服务端向客户端推送通知和透传消息的功能。 + +### 插件使用说明 + +#### 导入插件 +```uts +import * as GTPlugin from "../../uni_modules/uni-push" +``` + +#### 初始化 + +```typescript +//初始化个推推送 +GTPlugin.initPush(); +``` + +#### 推送消息事件 + +> 添加透传消息回调,对应的GTPlugin.offPushMessage()可移除对应监听callback(传入null,可移除所有监听callback) + +```typescript +GTPlugin.onPushMessage((res) => { + console.log("onPushMessage => " + JSON.stringify(res)) +}) +``` + +| 名称 | 类型 | 描述 | +| ---- | ------------- | ----------------------------------------------------------- | +| type | String | 事件类型,"click"-从系统推送服务点击消息启动应用事件;"receive"-应用从推送服务器接收到推送消息事件。 | +| data | String、Object | 消息内容 | + + + +#### 日志 + +开发阶段,需要使用到日志辅助。 + +```typescript +//设置日志回调,可以在控制台看到[GT-PUSH]的日志 +GTPlugin.setDebugLogger(function(res) { + console.log(res) +}); +``` + +当插件正常初始化会出现以下日志: + +```uts +16:47:53.254 [GT-PUSH] [LogController] Sdk version = 3.3.0.0 at pages/index/index.vue:25 +16:47:54.052 [GT-PUSH] [ServiceManager] ServiceManager start from initialize... at pages/index/index.vue:25 +16:47:54.073 [GT-PUSH] PushCore started at pages/index/index.vue:25 +16:47:54.274 [GT-PUSH] onHandleIntent() = get sdk service pid at pages/index/index.vue:25 +16:47:54.292 [GT-PUSH] onHandleIntent() areNotificationsEnabled at pages/index/index.vue:25 +16:47:54.353 [GT-PUSH] [LoginInteractor] Start login appid = nU*******wzf at pages/index/index.vue:25 +16:47:54.571 收到 cid onReceiveClientId : 3061f********ce7578eb24 at pages/index/index.vue:29 +16:47:54.592 [GT-PUSH] onHandleIntent() = received client id at pages/index/index.vue:25 +16:47:54.593 [GT-PUSH] [LoginResult] Login successed with cid = 3061f********ce7578eb24 at pages/index/index.vue:25 +``` + +#### 推送相关动作 + +> 设置推送相关动作回调,更多可查看`app-android/index.uts`下面的 `UserPushAction`类 + +```typescript +GTPlugin.setPushAction({ + onReceiveClientId: function(cid) { + console.log("收到 cid onReceiveClientId : " + cid) + } +}); +``` + + + +#### 唯一的推送标识 + +获取客户端唯一的推送标识 + +```typescript +GTPlugin.getPushClientId({ + success: (res) => { + console.log("getPushClientId success => " + JSON.stringify(res)); + }, + fail: (res) => { + console.log("getPushClientId fail => " + JSON.stringify(res)); + }, + complete: (res) => { + console.log("getPushClientId complete => " + JSON.stringify(res)); + } +}); +``` + +**OBJECT 参数说明** + +| 参数名 | 类型 | 必填 | 说明 | +| -------- | -------- | --- | ------------------------ | +| success | Function | 是 | 接口调用的回调函数,详见返回参数说明 | +| fail | Function | 否 | 接口调用失败的回调函数 | +| complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) | + +**success 返回参数说明** + +| 参数 | 类型 | 说明 | +| ------ | ------ | ---------------------------------------- | +| cid | String | 个推客户端推送id,对应uni-id-device表的push_clientid | +| errMsg | String | 错误描述 | + +**fail 返回参数说明** + +| 参数 | 类型 | 说明 | +| ------ | ------ | ---- | +| errMsg | String | 错误描述 | + + + +### APP_ID申请 + +可登录[个推官网](https://dev.getui.com/)注册申请应用,获取APP相关信息。 + + + +### 多厂商 + +多厂商渠道可以参考[[厂商应用开通指南-个推文档中心](https://docs.getui.com/getui/mobile/vendor/vendor_open/) 和 [厂商 SDK 集成指南-个推文档中心](https://docs.getui.com/getui/mobile/vendor/androidstudio/) + +> 注意:华为厂商需要把`agconnect-services.json` 放到${工程根目录}/nativeResources/android/ 目录下 + +### 开发文档 + +[个推推送SDK](https://docs.getui.com/getui/start/accessGuide/) +[多厂商接入](https://docs.getui.com/getui/mobile/vendor/vendor_open/) + + + +### 注意事项 + +在`AndroidManifest.xml`中,必须声明插件`flag` + +```xml + + +``` diff --git a/uni_modules/uni-push/utssdk/app-android/AndroidManifest.xml b/uni_modules/uni-push/utssdk/app-android/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..0272d5637496aceebc0a71a2908eb3d5eb81cfaf --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/config.json b/uni_modules/uni-push/utssdk/app-android/config.json new file mode 100644 index 0000000000000000000000000000000000000000..82b9613f2c85c6060d2af8bb064882b2b5003711 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/config.json @@ -0,0 +1,42 @@ +{ + "parameters": { + "appid": { + "placeholder": "GETUI_APPID" + }, + "mipush_appid": { + "placeholder": "MIPUSH_APPID" + }, + "mipush_appkey": { + "placeholder": "MIPUSH_APPKEY" + }, + "meizupush_appid": { + "placeholder": "MEIZUPUSH_APPID" + }, + "meizupush_appkey": { + "placeholder": "MEIZUPUSH_APPKEY" + }, + "oppopush_appkey": { + "placeholder": "OPPOPUSH_APPKEY" + }, + "oppopush_appsecret": { + "placeholder": "OPPOPUSH_APPSECRET" + }, + "huaweipush_appid": { + "placeholder": "com.huawei.hms.client.appid" + }, + "vivopush_appid": { + "placeholder": "com.vivo.push.app_id" + }, + "vivopush_appkey": { + "placeholder": "com.vivo.push.api_key" + }, + "dcloud_unipush_auto_notification": { + "placeholder": "dcloud_unipush_auto_notification" + } + }, + "files": [{ + "source": "push_unipush_huaweipush_agconnect-services.json", + "target": "agconnect-services.json", + "des": "HMS配置文件" + }] +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/gt-sdk/GTPush.uts b/uni_modules/uni-push/utssdk/app-android/gt-sdk/GTPush.uts new file mode 100644 index 0000000000000000000000000000000000000000..0ebb25a3c9b0bf9ddb234557ce6e4f506ff2db3c --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/gt-sdk/GTPush.uts @@ -0,0 +1,144 @@ +import Context from "android.content.Context"; +import GTPlugin from "com.getui.sdk.GTPlugin"; +import IPushAction from "com.getui.sdk.IPushAction"; +import GTCmdMessage from "com.igexin.sdk.message.GTCmdMessage"; +import GTNotificationMessage from "com.igexin.sdk.message.GTNotificationMessage"; +import GTTransmitMessage from "com.igexin.sdk.message.GTTransmitMessage"; +import IUserLoggerInterface from "com.igexin.sdk.IUserLoggerInterface"; + +export function gtInit(context : Context) : void { + GTPlugin.initialize(context); +} + +export function getClientId(context : Context) : string { + return GTPlugin.getClientId(context); +} + +export function setPushAction(action : UserPushAction) : void { + GTPlugin.setPushAction(action); +} + + +export type GTPushActionOptions = { + + onReceiveServicePid ?: (res : number) => void + + /** + * 接收clientId(cid) + */ + onReceiveClientId ?: (res : string) => void + + /** + * 此方法用于接收和处理透传消息。透传消息个推只传递数据,不做任何处理,客户端接收到透传消息后需要自己去做后续动作处理,如通知栏展示、弹框等。 + * 如果开发者在客户端将透传消息创建了通知栏展示,建议将展示和点击回执上报给个推。 + */ + onReceiveMessageData ?: (res : string) => void + + /** + * cid 离线上线通知 + */ + onReceiveOnlineState ?: (res : boolean) => void + + /** + * 各种事件处理回执 + */ + onReceiveCommandResult ?: (res : GTCmdMessage) => void + + /** + * 通知点击,只有个推通道下发的通知会回调此方法 + */ + onNotificationMessageClicked ?: (res : string) => void + + /** + * 通知到达,只有个推通道下发的通知会回调此方法 + */ + onNotificationMessageArrived ?: (res : GTNotificationMessage) => void +} + + + +export class UserPushAction implements IPushAction { + + constructor( + private options : GTPushActionOptions) { + } + + override onReceiveServicePid(ctx : Context, pid : Int) { + this.options.onReceiveServicePid?.(pid) + } + + /** + * 接收clientId(cid) + */ + override onReceiveClientId(ctx : Context, cid : string) { + this.options.onReceiveClientId?.(cid) + } + + /** + * 此方法用于接收和处理透传消息。透传消息个推只传递数据,不做任何处理,客户端接收到透传消息后需要自己去做后续动作处理,如通知栏展示、弹框等。 + * 如果开发者在客户端将透传消息创建了通知栏展示,建议将展示和点击回执上报给个推。 + * + * class GTTransmitMessage { + * private String taskId; + * private String messageId; + * private String payloadId; + * private byte[] payload; + * } + */ + override onReceiveMessageData(ctx : Context, message : GTTransmitMessage) { + this.options.onReceiveMessageData?.(new String(message.getPayload())) + } + + /** + * cid 离线/上线通知 + */ + override onReceiveOnlineState(ctx : Context, state : boolean) { + this.options.onReceiveOnlineState?.(state) + } + + /** + * 各种事件处理回执 + */ + override onReceiveCommandResult(ctx : Context, message : GTCmdMessage) { + this.options.onReceiveCommandResult?.(message) + } + + /** + * 通知点击,只有个推通道下发的通知会回调此方法 + */ + override onNotificationMessageClicked(ctx : Context, message : GTNotificationMessage) { + const params = { + "title": message.getTitle(), + "content": message.getContent() + }; + this.options.onNotificationMessageClicked?.(JSON.stringify(params)) + } + + /** + * 通知到达,只有个推通道下发的通知会回调此方法 + */ + override onNotificationMessageArrived(ctx : Context, message : GTNotificationMessage) { + this.options.onNotificationMessageArrived?.(message) + } +} + + +class UserLoggerInterface implements IUserLoggerInterface { + constructor(private callback : (log : string) => void) { + } + + override log(s : string) { + this.callback?.(s) + } +} + +/** + * 个推推送sdk调试日志信息 + * setDebugLogger 接口仅限调试的时候使用,切勿发布到线上版本,重复调用仅以第一次为准。 + */ +export function setDebugLogger(callback : (log : string) => void) { + const ctx = UTSAndroid.getAppContext(); + if (ctx != null) { + GTPlugin.setDebugLogger(ctx, new UserLoggerInterface(callback)) + } +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/index.uts b/uni_modules/uni-push/utssdk/app-android/index.uts new file mode 100644 index 0000000000000000000000000000000000000000..34379f2b9c0c8aaba15f09e095b767966c947f8c --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/index.uts @@ -0,0 +1,364 @@ + +import { GetPushClientIdOptions, GetPushClientIdSuccess, GetPushClientIdFail, OnPushMessageCallback, OnPushMessageCallbackResult, OnPushMessageType, CreatePushMessageOptions, ChannelManager } from '../interface.uts' + +import { gtInit, GTPushActionOptions, UserPushAction, setPushAction } from './gt-sdk/GTPush.uts' +import Context from 'android.content.Context'; +import { PushMessage } from './push/PushMessage.uts'; +import PackageManager from 'android.content.pm.PackageManager'; +import ApplicationInfo from 'android.content.pm.ApplicationInfo'; +import Activity from 'android.app.Activity'; +import TextUtils from 'android.text.TextUtils'; +import { PushState } from './push/PushState.uts'; +import { PushManager } from './push/PushManager.uts'; +import { StringUtil } from './push/utils/StringUtil.uts'; +import SharedPreferences from 'android.content.SharedPreferences'; +import Handler from 'android.os.Handler'; +import { PushChannelManager } from './push/PushChannelManager.uts'; +import Bundle from 'android.os.Bundle'; +import Intent from 'android.content.Intent'; +import Uri from 'android.net.Uri'; +import { globalPushMessageCallbacks, sendEvent } from './push/PushManager.uts' +export { PushActionService } from './push/PushActionService.uts'; + +const SP_NAME = "clientid_unipush"; +const SP_KEY_CLIENT_ID = "clientid"; + + +let gtCallBack : UserPushAction | null = null; +let gtPushInitialize = false + +/** + * 个推推送sdk初始化 + */ +function initPush() { + const ctx = UTSAndroid.getAppContext(); + if (ctx != null && !gtPushInitialize) { + gtPushInitialize = true; + gtInit(ctx); + setPushAction(getGTCallBack()); + } +} + +/** + * 获取客户端唯一的推送标识(注意:这是一个异步的方法) + */ +export function getPushClientId(options : GetPushClientIdOptions) { + initPush() + const ctx = UTSAndroid.getAppContext() as Context; + const sp = ctx.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE) as SharedPreferences; + const clientId = sp.getString(SP_KEY_CLIENT_ID, "") + if (TextUtils.isEmpty(clientId)) { + const handler = new Handler() + const changeListener = new (class implements SharedPreferences.OnSharedPreferenceChangeListener { + override onSharedPreferenceChanged(sharedPreferences : SharedPreferences, key : string) : void { + if (key != SP_KEY_CLIENT_ID) { + return + } + handler.removeCallbacksAndMessages(null) + sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + const cid = sharedPreferences.getString(SP_KEY_CLIENT_ID, "") + const res : GetPushClientIdSuccess = { errMsg: "success", cid: cid!! } + options.success?.(res) + options.complete?.(res) + } + }) + sp.registerOnSharedPreferenceChangeListener(changeListener) + + const runnable = new (class implements Runnable { + override run() { + const clientId = sp.getString(SP_KEY_CLIENT_ID, "") + if (!TextUtils.isEmpty(clientId)) { + const res : GetPushClientIdSuccess = { errMsg: "success", cid: clientId!! } + options.success?.(res) + options.complete?.(res) + } else { + const res : GetPushClientIdFail = { + errSubject: "uni-push", + errCode: -1, + errMsg: "failed,check appkey or appid", + } + options.fail?.(res) + options.complete?.(res) + } + } + }) + handler.postDelayed(runnable, 15000) + } else { + const res : GetPushClientIdSuccess = { errMsg: "success", cid: clientId!! } + options.success?.(res) + options.complete?.(res) + } +} + +/** + * 增加监听推送消息事件(应用在线的时候没有通知栏消息,全部是透传。), 注意: 使用时,开发者需要注册写到第一个activity的周期内 , 即首页. + */ +export function onPushMessage(callback : OnPushMessageCallback | null) { + initPush() + if (callback == null) { + return; + } + if (globalPushMessageCallbacks.indexOf(callback) == -1) { + globalPushMessageCallbacks.push(callback) + } + + processOfflineMessage() + // 处理没有注册监听时,已经接到的消息,此时从缓存里取 + PushManager.getInstance().comsumeMessages("click", (msgs : PushMessage[]) => { + msgs.forEach((msg) => { + sendEvent("click", msg) + }) + }) + PushManager.getInstance().comsumeMessages("receive", (msgs : PushMessage[]) => { + msgs.forEach((msg) => { + sendEvent("receive", msg) + }) + }) +} + +/** + * 移除推送消息监听事件(没有传入参数,则移除App级别的所有事件监听器。) + */ +export function offPushMessage(callback : OnPushMessageCallback | null) { + if (callback == null) { + const len = globalPushMessageCallbacks.length; + globalPushMessageCallbacks.splice(0, len) + return; + } + + let index = globalPushMessageCallbacks.indexOf(callback) + if (index == -1) { + return; + } + globalPushMessageCallbacks.splice(index, 1) +} + +export function getChannelManager() : ChannelManager { + return PushChannelManager.getInstance() +} + +export function createPushMessage(options : CreatePushMessageOptions) : void { + const context = UTSAndroid.getAppContext() as Context + const appId = UTSAndroid.getAppId() + const pushMessage = new PushMessage(JSON.stringify(options), getApplicationName(), false) + + const min = 0 + if (pushMessage.mDelay == min.toLong()) { + PushManager.getInstance().addPushMessage(appId, pushMessage) + PushManager.getInstance().createNotification(context, pushMessage) + } else { + new Handler().postDelayed(new (class implements Runnable { + override run() { + PushManager.getInstance().addPushMessage(appId, pushMessage) + PushManager.getInstance().createNotification(context, pushMessage) + } + }), pushMessage.mDelay * 1000) + } +} + + +function getGTCallBack() : UserPushAction { + if (gtCallBack == null) { + const options = { + onReceiveClientId(cid : string) { + const context = UTSAndroid.getAppContext() as Context; + const sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + const editor = sp.edit(); + editor.putString(SP_KEY_CLIENT_ID, cid); + editor.commit(); + }, + onNotificationMessageClicked(res : string) { + const pushMessage = new PushMessage(res, getApplicationName(), false) + if (!sendEvent("click", pushMessage)) { + PushManager.getInstance().addNeedExecClickMessage(pushMessage) + } + }, + onReceiveMessageData(res : string) { + if (!TextUtils.isEmpty(res)) { + let isUniPush2 = false + const jsonObject = JSON.parseObject(res) + const unipushVersionStr = jsonObject?.getString("unipush_version") + if (!TextUtils.isEmpty(unipushVersionStr)) { + const unipushVersion = parseFloat(unipushVersionStr!!) + if (unipushVersion == 2.0) { + isUniPush2 = true; + } + } + if (isUniPush2) { + processForUniPush2(res); + } else { + processForUniPush(res); + } + } + }, + } as GTPushActionOptions; + gtCallBack = new UserPushAction(options); + } + + return gtCallBack!!; +} + +/** + * 处理离线消息 + */ +function processOfflineMessage() { + const activity = UTSAndroid.getUniActivity() as Activity + const intent = activity.getIntent() + + // const testStr = "intent://io.dcloud.unipush/?#Intent;scheme=unipush;launchFlags=0x4000000;package=uni.UNI8CD4C4C;component=uni.UNI8CD4C4C/io.dcloud.uniapp.UniAppActivity;S.UP-OL-SU=true;S.unipush_version=2.0;S.payload={\"cccccforceNotification\":\"xxx\",\"path\":\"XXX\"};S.title=xxx;S.content=xxx;S.unipush_data={\"forceNotification\":\"xxx\",\"path\":\"XXX\"};end" + // const intent = Intent.parseUri(testStr , 0) + + if (intent.hasExtra("UP-OL-SU")) { + let isUniPush2 = false; + const unipushVersionStr = intent.getStringExtra("unipush_version"); + if (!TextUtils.isEmpty(unipushVersionStr)) { + const unipushVersion = parseFloat(unipushVersionStr!!) + if (unipushVersion == 2.0) { + isUniPush2 = true; + } + } + + const params = {} + + if (isUniPush2) { + try { + params["title"] = intent.getStringExtra("title") + params["content"] = intent.getStringExtra("content") + params["unipush_version"] = intent.getStringExtra("unipush_version") + + const channelId = intent.getStringExtra("channelId"); + const category = intent.getStringExtra("category"); + if (!TextUtils.isEmpty(channelId)) { + params["channelId"] = channelId + } + if (!TextUtils.isEmpty(category)) { + params["category"] = category + } + + let payload = intent.getStringExtra("payload"); + const payloadJsonObject = JSON.parseObject(payload ?? "") + if (payloadJsonObject == null) { + if (payload != null) { + //如果后端传的不是json,而是纯字符串,就需要单独处理,去掉多余的引号,并且枚举一下类型. + //双引号套双引号,就说明是传的字符串. + if (payload.startsWith("\"")) { + payload = StringUtil.trimString(payload, '"'); + params["payload"] = payload + } else { + const payloadInt = StringUtil.getInt(payload); + const payloadDouble = StringUtil.getDouble(payload); + if (payloadInt != null) { + params["payload"] = payloadInt + } else if (payloadDouble != null) { + params["payload"] = payloadDouble + } else if (payload == "true" || payload == "false") { + params["payload"] = payload.toBoolean() + } else { + params["payload"] = payload + } + } + } + } else { + params["payload"] = payloadJsonObject + } + + const unipush_data = intent.getStringExtra("unipush_data"); + const unipushDataJsonObject = JSON.parseObject(unipush_data ?? ""); + if (unipushDataJsonObject != null) { + unipushDataJsonObject.toMap().forEach((value, key) => { + params[key] = value + }) + } + + intent.removeExtra("UP-OL-SU"); + intent.removeExtra("title"); + intent.removeExtra("content"); + intent.removeExtra("payload"); + intent.removeExtra("unipush_version"); + intent.removeExtra("unipush_data"); + intent.removeExtra("channelId"); + intent.removeExtra("category"); + + const data = JSON.stringify(params) + const pushMessage = new PushMessage(data, getApplicationName(), true); + if (!sendEvent("click", pushMessage)) { + PushManager.getInstance().addNeedExecClickMessage(pushMessage) + } + } catch (e : Exception) { + e.printStackTrace(); + } + } else { + try { + params["title"] = intent.getStringExtra("title") + params["content"] = intent.getStringExtra("content") + params["payload"] = intent.getStringExtra("payload") + const channelId = intent.getStringExtra("channelId"); + const category = intent.getStringExtra("category"); + if (!TextUtils.isEmpty(channelId)) { + params["channelId"] = channelId + } + if (!TextUtils.isEmpty(category)) { + params["category"] = category + } + intent.removeExtra("UP-OL-SU"); + intent.removeExtra("title"); + intent.removeExtra("content"); + intent.removeExtra("payload"); + intent.removeExtra("channelId"); + intent.removeExtra("category"); + const data = JSON.stringify(params) + const pushMessage = new PushMessage(data, getApplicationName(), true); + if (!sendEvent("click", pushMessage)) { + PushManager.getInstance().addNeedExecClickMessage(pushMessage) + } + } catch (e : Exception) { + e.printStackTrace(); + } + } + } +} + + +function processForUniPush(data : string) : void { + const context = UTSAndroid.getAppContext() as Context + const pushMessage = new PushMessage(data, getApplicationName(), false) + const needPush = PushState.getAutoNotification() + if (needPush && pushMessage.getNeedCreateNotification()) { + PushManager.getInstance().createNotification(context, pushMessage) + } else if (!sendEvent("receive", pushMessage)) { + PushManager.getInstance().addNeedExecReceiveMessage(pushMessage); + + } + PushManager.getInstance().addPushMessage(UTSAndroid.getAppId(), pushMessage); +} + +function processForUniPush2(data : string) : void { + const context = UTSAndroid.getAppContext() as Context + const jsonObject = JSON.parseObject(data) + if (jsonObject != null) { + const forceNotification = jsonObject.getBoolean("force_notification") + const pushMessage = new PushMessage(data, getApplicationName(), true) + if (forceNotification != null && forceNotification) { + PushManager.getInstance().createNotification(context, pushMessage) + } else if (!sendEvent("receive", pushMessage)) { + PushManager.getInstance().addNeedExecReceiveMessage(pushMessage); + } + PushManager.getInstance().addPushMessage(UTSAndroid.getAppId(), pushMessage); + } +} + +function getApplicationName() : string { + let packageManager : PackageManager | null = null + let applicationInfo : ApplicationInfo | null = null + const context = UTSAndroid.getAppContext() as Context; + try { + packageManager = context.getApplicationContext().getPackageManager() + applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0) + } catch (_ : Exception) { + } + if (applicationInfo == null) { + return "" + } + + return packageManager?.getApplicationLabel(applicationInfo).toString() ?? "" +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/libs/getui-uts-android-release-20230928135005.aar b/uni_modules/uni-push/utssdk/app-android/libs/getui-uts-android-release-20230928135005.aar new file mode 100644 index 0000000000000000000000000000000000000000..93354738c7fef06d3cd9a7d1d48aecd68204616a Binary files /dev/null and b/uni_modules/uni-push/utssdk/app-android/libs/getui-uts-android-release-20230928135005.aar differ diff --git a/uni_modules/uni-push/utssdk/app-android/push/PushActionService.uts b/uni_modules/uni-push/utssdk/app-android/push/PushActionService.uts new file mode 100644 index 0000000000000000000000000000000000000000..886709c8f29ca335d70fff3c7ad886df655d2790 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/PushActionService.uts @@ -0,0 +1,21 @@ +import Service from 'android.app.Service'; +import Intent from 'android.content.Intent'; +import IBinder from 'android.os.IBinder'; +import { PushManager } from './PushManager.uts'; +export class PushActionService extends Service { + + constructor(){ + super(); + } + + override onBind(intent : Intent) : IBinder | null { + return null + } + + override onStartCommand(intent : Intent | null, flag : Int, startId : Int) : Int { + if (intent != null) { + PushManager.getInstance().processAction(this.getBaseContext(), intent) + } + return super.onStartCommand(intent, flag, startId) + } +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/push/PushChannelManager.uts b/uni_modules/uni-push/utssdk/app-android/push/PushChannelManager.uts new file mode 100644 index 0000000000000000000000000000000000000000..07f55431709507f1a2b50c6d6ffe946b8266bf3c --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/PushChannelManager.uts @@ -0,0 +1,98 @@ +import { ChannelManager, SetPushChannelOptions } from "../../interface.uts"; +import Context from 'android.content.Context'; +import Build from 'android.os.Build'; +import NotificationManager from 'android.app.NotificationManager'; +import NotificationChannelGroup from 'android.app.NotificationChannelGroup'; +import NotificationChannel from 'android.app.NotificationChannel'; +import TextUtils from 'android.text.TextUtils'; +import ContentResolver from 'android.content.ContentResolver'; +import Uri from 'android.net.Uri'; +import RingtoneManager from 'android.media.RingtoneManager'; + +export class PushChannelManager implements ChannelManager { + + static LOCAL_PUSH_CHANNEL_ID = "DcloudChannelID"; + static LOCAL_PUSH_GROUP_ID = "DcloudGroupID"; + + private static INSTANCE : PushChannelManager | null = null + + static getInstance() : PushChannelManager { + if (this.INSTANCE == null) { + this.INSTANCE = new PushChannelManager() + } + return this.INSTANCE!! + } + + createDefaultChannel(context : Context) { + if (Build.VERSION.SDK_INT >= 26) { + const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + const pChannelId = PushChannelManager.LOCAL_PUSH_CHANNEL_ID + const pChannelName = context.getResources().getString(R.string.dcloud_feature_aps_notification_channel) + if (notificationManager.getNotificationChannel(pChannelId) == null) { + notificationManager.createNotificationChannelGroup(new NotificationChannelGroup(PushChannelManager.LOCAL_PUSH_GROUP_ID, context.getResources().getString(R.string.dcloud_feature_aps_notification_group))) + const channel = new NotificationChannel(pChannelId, pChannelName, NotificationManager.IMPORTANCE_DEFAULT) + channel.enableLights(true) + channel.setShowBadge(true) + notificationManager.createNotificationChannel(channel) + } + } + } + + /** + * 设置推送渠道 + */ + setPushChannel(options : SetPushChannelOptions) : void { + if (Build.VERSION.SDK_INT >= 26) { + const context = UTSAndroid.getAppContext() as Context + const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + if (notificationManager.getNotificationChannel(options.channelId) == null) { + const notificationChannel = new NotificationChannel(options.channelId, options.channelDesc, NotificationManager.IMPORTANCE_DEFAULT); + notificationChannel.setShowBadge(true); + let sound = 0; + if (!TextUtils.isEmpty(options.soundName)) { + const packName = context.getApplicationInfo().packageName + sound = context.getResources().getIdentifier(options.soundName!!, "raw", packName) + } + let uriStr = ""; + if (sound != 0) { + uriStr = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + sound; + } + + if (!TextUtils.isEmpty(uriStr)) { + notificationChannel.setSound(Uri.parse(uriStr), null); + } else { + const uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);//默认铃音 + notificationChannel.setSound(uri, null); + } + + if (options.importance != null) { + notificationChannel.setImportance(options.importance!!.toInt()); + } + if (options.lockscreenVisibility != null) { + notificationChannel.setLockscreenVisibility(options.lockscreenVisibility!!.toInt()); + } + notificationChannel.enableLights(options.enableLights ?? false); + notificationChannel.enableVibration(options.enableVibration ?? false); + notificationManager.createNotificationChannel(notificationChannel); + } + } + } + /** + * 获取当前应用注册的所有的通知渠道。 + */ + getAllChannels() : string[] { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + const context = UTSAndroid.getAppContext() as Context + const nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + const channels : string[] = [] + const list = nm.getNotificationChannels() + for (let i:Int = 0; i < list.size; i++) { + channels.push(list.get(i).toString()) + } + return channels + }else{ + return [] as string[] + } + } + +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/push/PushManager.uts b/uni_modules/uni-push/utssdk/app-android/push/PushManager.uts new file mode 100644 index 0000000000000000000000000000000000000000..bbc8b88effc02986f81d65de0df74742033baff3 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/PushManager.uts @@ -0,0 +1,320 @@ +import Context from 'android.content.Context'; +import Intent from 'android.content.Intent'; +import { PushChannelManager } from './PushChannelManager.uts'; +import { PushMessage } from './PushMessage.uts'; +import NotificationManager from 'android.app.NotificationManager'; +import ComponentName from 'android.content.ComponentName'; +import PendingIntent from 'android.app.PendingIntent'; +import Build from 'android.os.Build'; +import TextUtils from 'android.text.TextUtils'; +import Notification from 'android.app.Notification'; +import Bitmap from 'android.graphics.Bitmap'; +import BitmapFactory from 'android.graphics.BitmapFactory'; +import File from 'java.io.File'; +import { OnPushMessageCallback, OnPushMessageType, OnPushMessageCallbackResult } from '../../interface.uts' + +export const globalPushMessageCallbacks : OnPushMessageCallback[] = [] + +export function sendEvent(type : OnPushMessageType, pushMessage : PushMessage) : boolean { + const data = pushMessage.getJsonObject() + const result : OnPushMessageCallbackResult = { + type: type, + data: data + } + if (globalPushMessageCallbacks.length == 0) { + return false + } else { + globalPushMessageCallbacks.forEach((cb : OnPushMessageCallback) => { + cb(result) + }) + return true + } +} + +export class PushManager { + private static INSTANCE : PushManager | null = null + + private ACTION_TYPE_CREATE = "ACTION_TYPE_CREATE" + private ACTION_TYPE_REMOVE = "ACTION_TYPE_REMOVE" + private ACTION_TYPE_CLEAR = "ACTION_TYPE_CLEAR" + private ACTION_TYPE_CLICK = "ACTION_TYPE_CLICK" + + private mAppMessages : Map> = new Map() + private mNeedExecClickMessages : PushMessage[] = [] + private mNeedExecReceiveMessages : PushMessage[] = [] + + static getInstance() : PushManager { + if (this.INSTANCE == null) { + this.INSTANCE = new PushManager() + } + return this.INSTANCE!! + } + + + + + + createNotification(context : Context, message : PushMessage) { + PushChannelManager.getInstance().createDefaultChannel(context) + const intent = new Intent(this.ACTION_TYPE_CREATE) + intent.putExtras(message.toBundle()) + this.processAction(context, intent) + } + + addPushMessage(pAppid : string, pMsg : PushMessage) { + let _arr = this.mAppMessages.get(pAppid); + if (_arr == null) { + _arr = new Array(); + this.mAppMessages.set(pAppid, _arr); + } + _arr.push(pMsg); + } + + + addNeedExecClickMessage(pushMessage : PushMessage) { + if (this.mNeedExecClickMessages.length > 0) { + this.mNeedExecClickMessages = [] + } + this.mNeedExecClickMessages.push(pushMessage) + } + + addNeedExecReceiveMessage(pushMessage : PushMessage) { + this.mNeedExecReceiveMessages.push(pushMessage) + } + + removePushMessage(pAppid : String, pPushMsg : PushMessage) { + const _arr = this.mAppMessages.get(pAppid); + if (_arr != null && _arr.indexOf(pPushMsg) > 0) { + _arr.splice(_arr.indexOf(pPushMsg), 1); + } + } + + /** + * 消费缓存的消息 + */ + comsumeMessages(type : string, cb : (msgs : PushMessage[]) => void) { + if (type == "click") { + cb(this.mNeedExecClickMessages) + this.mNeedExecClickMessages.splice(0) + } else if (type == "receive") { + cb(this.mNeedExecReceiveMessages) + this.mNeedExecReceiveMessages.splice(0) + } + } + + + processAction(context : Context, intent : Intent) { + const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + const action = intent.getAction() + switch (action) { + case this.ACTION_TYPE_CREATE: + { + const title = intent.getStringExtra("title"); + const message = intent.getStringExtra("content"); + const nId = intent.getIntExtra("nId", 0); + const when = intent.getLongExtra("when", 0); + const appid = intent.getStringExtra("appid"); + const icon = intent.getStringExtra("icon"); + const sound = intent.getStringExtra("sound"); + const category = intent.getStringExtra("category"); + let channelId = intent.getStringExtra("channelId"); + const i = new Intent(this.ACTION_TYPE_CLICK); + i.setComponent(new ComponentName(context.getPackageName(), "uts.sdk.modules.DCloudUniPush.PushActionService")); + i.putExtras(intent.getExtras()!!); + let flags = PendingIntent.FLAG_ONE_SHOT; + if (Build.VERSION.SDK_INT >= 23) { + flags = PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE; + } + const contentIntent = PendingIntent.getService(context, nId, i, flags); + + let builder : Notification.Builder | null = null; + if (Build.VERSION.SDK_INT >= 26) { + if (TextUtils.isEmpty(channelId)) { + channelId = PushChannelManager.LOCAL_PUSH_CHANNEL_ID; + } + builder = new Notification.Builder(context, channelId); + } else { + builder = new Notification.Builder(context); + } + + let bitmap : Bitmap | null = null; + try { + if (!TextUtils.isEmpty(icon) && this.fileIsExist(icon!!)) { + bitmap = BitmapFactory.decodeFile(icon!!); + } + } catch (e : Exception) { + e.printStackTrace(); + } + if (bitmap != null) { + builder.setLargeIcon(bitmap); + } + + const id_small = context.getResources().getIdentifier("push_small", "drawable", context.getPackageName()) + if (id_small <= 0) { + builder.setSmallIcon(context.getApplicationInfo().icon); //设置图标 + } else { + builder.setSmallIcon(id_small); //设置图标 + } + + const id = context.getResources().getIdentifier("push", "drawable", context.getPackageName()) + if (bitmap == null) { + let largeBitmap : Bitmap | null = null; + if (id <= 0) { + largeBitmap = BitmapFactory.decodeResource(context.getResources(), context.getApplicationInfo().icon); + } else { + largeBitmap = BitmapFactory.decodeResource(context.getResources(), id); + } + if (null != largeBitmap) { + builder.setLargeIcon(largeBitmap); + } + } + builder.setContentTitle(title); //设置标题 + builder.setContentText(message); //消息内容 + if (Build.VERSION.SDK_INT >= 24) { + builder.setShowWhen(true); + } + builder.setWhen(when); //发送时间 + + // 添加声音提示 + if ("system" == sound) { + builder.setDefaults(Notification.DEFAULT_SOUND); //设置默认的提示音,振动方式,灯光 + } + builder.setAutoCancel(true);//打开程序后图标消失 + builder.setContentIntent(contentIntent); + builder.setCategory(category); + const notification = builder.build(); + try { + notificationManager.notify(nId, notification); + } catch (e : Exception) { + e.printStackTrace(); + } + } + + break; + case this.ACTION_TYPE_REMOVE: + { + const _id = intent.getIntExtra("id", 0); + if (_id != null) { + notificationManager.cancel(_id); + } + } + break; + case this.ACTION_TYPE_CLEAR: + { + notificationManager.cancelAll(); + const _appid = intent.getStringExtra("_appId"); + if (_appid != null) { + const appMsg = PushManager.getInstance().mAppMessages; + appMsg.delete(_appid); + } + } + break; + case this.ACTION_TYPE_CLICK: + { + this.clickHandle(intent, notificationManager); + const packagename = context.getPackageName();// 启动类所在包名 + const pm = context.getPackageManager(); + const _intent = pm.getLaunchIntentForPackage(packagename); + const appid = intent.getStringExtra("appid"); + _intent?.putExtra("appid", appid); + const isStartWeb = intent?.getBooleanExtra("__start_first_web__", false) ?? false; + if (isStartWeb) { + _intent?.putExtra("__start_first_web__", isStartWeb); + _intent?.putExtra("__first_web_url__", intent?.getStringExtra("__first_web_url__")); + } + _intent?.putExtra("__start_from__", 3); + _intent?.putExtra("__payload__", intent?.getStringExtra("payload")); + _intent?.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(_intent); + } + break; + } + + } + + clickHandle(intent : Intent, _notificationManager : NotificationManager) { + const _bundle = intent.getExtras()!!; + const appid = _bundle.getString("appid"); + const uuid = _bundle.getString("uuid"); + if (_notificationManager != null) {//作为插件时,手助负责创建通知栏消息 + const _id = intent.getIntExtra("id", 0); + _notificationManager.cancel(_id); + } + let _pushMessage : PushMessage | null = null + if (appid != null && uuid != null) { + _pushMessage = this.findPushMessage(appid!!, uuid!!); + } + if (_pushMessage != null) { + let isStartWeb = false; + if (!TextUtils.isEmpty(_pushMessage.mPayload)) { + try { + const payLoadJson = JSON.parseObject(_pushMessage.mPayload ?? "") + const url = payLoadJson?.getString("__adurl") + if (!TextUtils.isEmpty(url)) { + intent.putExtra("__start_first_web__", true); + intent.putExtra("__first_web_url__", url); + isStartWeb = true; + } + } catch (e : Exception) { + e.printStackTrace(); + } + } + + if (!isStartWeb && !sendEvent("click", _pushMessage)) { + this.addNeedExecClickMessage(_pushMessage); + } + // 点击后的消息,需要移除消息记录,避免getAllMessage时不正确 + if (appid != null) { + this.removePushMessage(appid, _pushMessage); + } + } else { + _pushMessage = new PushMessage(_bundle); + if (!TextUtils.isEmpty(_pushMessage.mPayload)) { + try { + const payLoadJson = JSON.parseObject(_pushMessage.mPayload!!) + const url = payLoadJson?.getString("__adurl") + if (!TextUtils.isEmpty(url)) { + intent.putExtra("__start_first_web__", true); + intent.putExtra("__first_web_url__", url); + } + } catch (e : Exception) { + e.printStackTrace(); + } + } + this.addNeedExecClickMessage(_pushMessage); + } + _bundle.clear(); + } + + + + findPushMessage(pAppid : String, pUuid : String) : PushMessage | null { + let _ret : PushMessage | null = null; + const _arr = this.mAppMessages.get(pAppid); + if (_arr == null) {//若没有通过appid获取到消息集合,则通过uuid遍历所有消息 + this.mAppMessages.forEach((value : PushMessage[], key : string) => { + if (value != null) { + value.forEach((value : PushMessage) => { + if (pUuid == value.getMessageUUID()) { + _ret = value + } + }) + } + }) + } else if (_arr != null) { + _arr.forEach((value : PushMessage) => { + if (pUuid == value.getMessageUUID()) { + _ret = value + } + }) + } + return _ret; + } + + + fileIsExist(path : string) : boolean { + const realPath = UTSAndroid.convert2AbsFullPath(path) + const file = new File(realPath) + return file.exists() + } +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/push/PushMessage.uts b/uni_modules/uni-push/utssdk/app-android/push/PushMessage.uts new file mode 100644 index 0000000000000000000000000000000000000000..ef84878939440a73a869e785081e1ea50f74b66c --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/PushMessage.uts @@ -0,0 +1,275 @@ +import TextUtils from 'android.text.TextUtils' +import Bundle from 'android.os.Bundle'; + +export class PushMessage { + private mTitle : string | null = null + private contentJson : string | null = null + private mContent : string | null = null + + mPayloadJSON : UTSJSONObject | null = null + mPayload : string | null = null + + private mWhen : Long = 0 + mDelay : Long = 0 + private mPath : string | null = null + private mForceNotification : string | null = null + private channelId = "" + private category = "" + private mMessageAppid : string | null = null + private mIconPath : string | null = null + + private mUUID : string | null = null + private nID : number = 0 + private isCover = false + private sound = "system" + private static mNotificationId = 1 + private needCreateNotification = true + + private pushVersion : Float = 1.0.toFloat() + private extJSON : UTSJSONObject | null = null + + constructor(data : string, defaultTitle : string, isUniPush2 : boolean) { + if (!isUniPush2) { + this.contentJson = data + this.parseJson(data, UTSAndroid.getAppId(), defaultTitle) + } else { + this.extJSON = JSON.parseObject(data) + this.mMessageAppid = UTSAndroid.getAppId() + this.pushVersion = 2.0.toFloat() + if (this.extJSON != null) { + this.channelId = this.extJSON!!.getString("channelId") ?? "" + this.category = this.extJSON!!.getString("category") ?? "" + } + } + this.setMessageUUID() + this.setNotificationID() + } + + $constructor(b : Bundle) { + this.mTitle = b.getString("title"); + this.mContent = b.getString("content"); + this.nID = b.getInt("nId"); + this.mWhen = b.getLong("when"); + this.sound = b.getString("sound") ?? "system"; + this.mMessageAppid = b.getString("appid"); + this.mUUID = b.getString("uuid"); + this.mPayload = b.getString("payload"); + this.mIconPath = b.getString("icon"); + this.channelId = b.getString("channelId", ""); + this.category = b.getString("category", ""); + } + + + + getNeedCreateNotification() : boolean { + return this.needCreateNotification;//payload为空串时需要创建 + } + + getMessageUUID() : string | null { + return this.mUUID + } + + getJsonObject() : UTSJSONObject { + if (this.extJSON != null) { + if (this.pushVersion == (2.0.toFloat())) { + try { + this.extJSON!!["__UUID__"] = this.mUUID + this.extJSON!!["appid"] = this.mMessageAppid + } catch (e : Exception) { + e.printStackTrace() + return {}; + } + } + return this.extJSON!! + } else { + const result = {} as UTSJSONObject; + result["__UUID__"] = this.mUUID + result["title"] = this.mTitle + result["appid"] = this.mMessageAppid + result["content"] = this.mContent + if (this.mPayloadJSON != null) { + result["payload"] = this.cleanNullValue(this.mPayloadJSON!!) + } else { + let payLoadObj : UTSJSONObject | null = null + if (this.mPayload != null) { + payLoadObj = JSON.parseObject(this.mPayload!!) + } + + if (payLoadObj != null) { + result["payload"] = this.cleanNullValue(payLoadObj) + } else { + result["payload"] = this.mPayload + } + } + if (!TextUtils.isEmpty(this.mPath)) { + result["path"] = this.mPath + } + if (!TextUtils.isEmpty(this.mForceNotification)) { + result["force_notification"] = this.mForceNotification + } + if (!TextUtils.isEmpty(this.channelId)) { + result["channelId"] = this.channelId + } + if (!TextUtils.isEmpty(this.category)) { + result["category"] = this.category + } + return result + } + } + + cleanNullValue(json : UTSJSONObject) : UTSJSONObject { + const result = {} + json.toMap().forEach((value, key) => { + if (value != null) { + result[key] = value + } + }) + return result + } + + + toBundle() : Bundle { + const bundle = new Bundle() + if (this.extJSON != null && this.pushVersion == (2.0.toFloat())) { + bundle.putInt("nId", this.nID.toInt()); + bundle.putLong("when", this.mWhen); + bundle.putString("sound", this.sound); + bundle.putString("appid", this.mMessageAppid); + bundle.putString("uuid", this.mUUID); + bundle.putString("icon", this.mIconPath); + + const map = this.extJSON!!.toMap() + map.forEach((value, key) => { + if (value != null) { + if (typeof value == 'string') { + bundle.putString(key, value as string); + } else if (value instanceof Integer) { + bundle.putInt(key, value as Int); + } else if (value instanceof Double) { + bundle.putDouble(key, value); + } else if (typeof value == 'boolean') { + bundle.putBoolean(key, value as boolean); + } else if (value instanceof UTSJSONObject) { + bundle.putString(key, (value as UTSJSONObject).toJSONString()); + } + } + }) + return bundle + } + + bundle.putString("title", this.mTitle); + bundle.putString("content", this.mContent); + bundle.putInt("nId", this.nID.toInt()); + bundle.putLong("when", this.mWhen); + bundle.putString("sound", this.sound); + bundle.putString("appid", this.mMessageAppid); + bundle.putString("uuid", this.mUUID); + if (this.mPayloadJSON != null) { + bundle.putString("payload", this.mPayloadJSON!!.toJSONString()); + } else { + bundle.putString("payload", this.mPayload); + } + bundle.putString("icon", this.mIconPath); + bundle.putString("channelId", this.channelId); + bundle.putString("category", this.category); + + return bundle + } + + + + private setMessageUUID() : void { + this.mUUID = "androidPushMsg" + this.hashCode() + } + + private setNotificationID() : void { + if (!this.isCover) { + PushMessage.mNotificationId++ + } + this.nID = PushMessage.mNotificationId + } + + + /** + * 解析消息的数据 + * @param defaultAppid 通过appid 查询到icon资源 , 1.0强需求, 2.0 不需求 + */ + private parseJson(data : string, defaultAppid : string, defaultTitle : string) : void { + const json = JSON.parseObject(data) + if (json != null) { + let t_appid = json.getString("appid") + const content = json.getString("content") + if (content != null) { + this.mContent = content + } else { + const message = json.getString("message") + if (message != null) { + this.mContent = message + } else { + this.needCreateNotification = true + this.mContent = data + } + } + + if (this.hasOwnProperty(json, "payload")) { + const payloadJson = json.getJSON("payload") + if (payloadJson != null) { + this.mPayloadJSON = payloadJson + } else { + this.mPayload = json.getString("payload") + } + } else { + if (this.hasOwnProperty(json, "Payload")) { + const payloadJson = json.getJSON("Payload") + if (payloadJson != null) { + this.mPayloadJSON = payloadJson + } else { + this.mPayload = json.getString("Payload") + } + } else { + this.needCreateNotification = false + this.mPayload = data + } + } + + if (this.hasOwnProperty(json, "title")) { + this.mTitle = json.getString("title") + } else { + this.needCreateNotification = false + this.mTitle = defaultTitle + } + + this.isCover = json.getBoolean("cover") ?? false + if ("none" == json.getString("sound")) { + this.sound = "none" + } + this.mWhen = (json.getNumber("when") ?? 0).toLong() + this.mDelay = (json.getNumber("delay") ?? 0).toLong() + + this.mPath = json.getString("path") + this.mForceNotification = json.getString("force_notification") + this.channelId = json.getString("channelId") ?? "" + this.category = json.getString("category") ?? "" + + if (TextUtils.isEmpty(t_appid)) { + t_appid = defaultAppid + } + this.mMessageAppid = t_appid + const iconPath = json.getString("icon") ?? "" + this.mIconPath = UTSAndroid.convert2AbsFullPath(iconPath) + } else { + this.needCreateNotification = false + this.mContent = data + this.mPayload = data + this.mTitle = defaultTitle + } + } + + + private hasOwnProperty(jsonObject : UTSJSONObject, key : string) : boolean { + return jsonObject.getAny(key) != null + } + + + +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/push/PushState.uts b/uni_modules/uni-push/utssdk/app-android/push/PushState.uts new file mode 100644 index 0000000000000000000000000000000000000000..c80852d5c975a9af825366fe6c71a6d723f0adf1 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/PushState.uts @@ -0,0 +1,42 @@ +import Context from 'android.content.Context'; +import PackageManager from 'android.content.pm.PackageManager'; +import Bundle from 'android.os.Bundle'; +import TextUtils from 'android.text.TextUtils'; +export class PushState { + + private static sMetaDatas : Bundle | null = null + + + static getAutoNotification() : boolean { + const context = UTSAndroid.getAppContext() as Context + const sp = context.getSharedPreferences("push_db_name", Context.MODE_PRIVATE) + const autoNotification = this.getMetaValue(context, "dcloud_unipush_auto_notification") + let needPush = true + if (autoNotification != null) { + if (!autoNotification.equals("ture", true)) { + needPush = false; + } + } + needPush = sp.getBoolean("auto_notification", needPush); + return needPush + } + + + private static getMetaValue(context : Context, metaKey : string) : string | null { + if (this.sMetaDatas == null) { + try { + this.sMetaDatas = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData; + } catch (e : Exception) { + e.printStackTrace(); + return null; + } + } + if (this.sMetaDatas != null) { + const value = this.sMetaDatas!!.get(metaKey) + if (value != null && !TextUtils.isEmpty(value as string)) { + return value as string; + } + } + return null; + } +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/push/utils/StringUtil.uts b/uni_modules/uni-push/utssdk/app-android/push/utils/StringUtil.uts new file mode 100644 index 0000000000000000000000000000000000000000..97bc29a1408d4a5be16314cd31c4e18751147513 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/push/utils/StringUtil.uts @@ -0,0 +1,31 @@ +export class StringUtil { + static trimString(pSrc : string, removed : string) : string { + const pTrimChar = removed.charAt(0) + let _ret = pSrc; + if (_ret != null && _ret != "") { + const _startPosi = _ret.charAt(0) == pTrimChar ? 1 : 0; + const _count = _ret.length; + const _endPosi = _ret.charAt(_count - 1) == pTrimChar ? _count - 1 : _count; + _ret = _ret.substring(_startPosi, _endPosi); + } + return _ret; + } + + static getInt(content : string) : number | null { + try { + return content.toInt(); + } catch (e : Exception) { + return null; + } + } + + + static getDouble(content : string) : number | null { + try { + return content.toDouble() + } catch (e : Exception) { + return null; + } + } + +} \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push.png b/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push.png new file mode 100755 index 0000000000000000000000000000000000000000..ed3d29bd26d2b611a328fba26aac73b44e916a10 Binary files /dev/null and b/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push.png differ diff --git a/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push_small.png b/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push_small.png new file mode 100755 index 0000000000000000000000000000000000000000..800dbd4a677f3c838095d7f31a4bc60ef85e75ed Binary files /dev/null and b/uni_modules/uni-push/utssdk/app-android/res/drawable-xxhdpi/push_small.png differ diff --git a/uni_modules/uni-push/utssdk/app-android/res/raw/keep.xml b/uni_modules/uni-push/utssdk/app-android/res/raw/keep.xml new file mode 100644 index 0000000000000000000000000000000000000000..35edb41154a913b6042713dad4b709968f2b9970 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/res/raw/keep.xml @@ -0,0 +1,7 @@ + + + + 消息推送 + 推送消息 + + \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-android/res/values/values.xml b/uni_modules/uni-push/utssdk/app-android/res/values/values.xml new file mode 100644 index 0000000000000000000000000000000000000000..012adb5e607f5fee15fbdd8653591adaedf7a808 --- /dev/null +++ b/uni_modules/uni-push/utssdk/app-android/res/values/values.xml @@ -0,0 +1,6 @@ + + + unipush + message push + + \ No newline at end of file diff --git a/uni_modules/uni-push/utssdk/app-ios/index.uts b/uni_modules/uni-push/utssdk/app-ios/index.uts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uni_modules/uni-push/utssdk/interface.uts b/uni_modules/uni-push/utssdk/interface.uts new file mode 100644 index 0000000000000000000000000000000000000000..84490f8ac43178f6aa5003271af83a36dff8e0d6 --- /dev/null +++ b/uni_modules/uni-push/utssdk/interface.uts @@ -0,0 +1,396 @@ +export interface Uni { + /** + * getPushClientId() + * @description + * 获取客户端唯一的推送标识 + * @param {GetPushClientIdOptions} + * @return {void} + * @tutorial http://uniapp.dcloud.io/api/plugins/push.html#getpushclientid + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + * @example + ```typescript + uni.getPushClientId({ + complete: (res: any) => { + console.log("getPushClientId complete => " + JSON.stringify(res)); + } + }); + ``` + */ + getPushClientId(options : GetPushClientIdOptions) : void; + /** + * onPushMessage() + * @description + * 启动监听推送消息事件 + * @param {OnPushMessageCallback} + * @return {void} + * @tutorial http://uniapp.dcloud.io/api/plugins/push.html#onpushmessage + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + * @example + ```typescript + uni.onPushMessage((res : OnPushMessageCallbackResult) => { + console.log("onPushMessage => " + JSON.stringify(res)) + }); + ``` + */ + onPushMessage(callback : OnPushMessageCallback) : void; + /** + * offPushMessage() + * @description + * 关闭推送消息监听事件 + * @param {OnPushMessageCallback} + * @return {void} + * @tutorial http://uniapp.dcloud.io/api/plugins/push.html#offpushmessage + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + * @example + ```typescript + const cb = (res : OnPushMessageCallbackResult) => { + console.log("onPushMessage => " + JSON.stringify(res)) + } + uni.offPushMessage(cb); + ``` + */ + offPushMessage(callback : OnPushMessageCallback) : void; + /** + * getChannelManager() + * @description + * 获取通知渠道管理器,Android 8系统以上才可以设置通知渠道,Android 8系统以下返回null。 + * @param {void} + * @return {ChannelManager} + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "3.97", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + * @example + ```typescript + const channelManager = uni.getChannelManager(); + channelManager.setPushChannel({ + channelId: "test1", + channelDesc: "test1 desc", + soundName: "pushsound" + }) + const channels = channelManager.getAllChannels() as string[] + console.log("channels : " + channels); + ``` + */ + getChannelManager() : ChannelManager; + + /** + * createPushMessage() + * @description + * 创建本地通知栏消息 + * @param {CreatePushMessageOptions} + * @return {void} + * @tutorial http://uniapp.dcloud.io/api/plugins/push.html#createpushmessage + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + * @example + ```typescript + uni.createPushMessage({ + title:"hello", + content: "content" + }); + ``` + */ + createPushMessage(options : CreatePushMessageOptions) : void; +} + +export type GetPushClientId = (options : GetPushClientIdOptions) => void; +export type GetPushClientIdSuccess = { + /** + * 个推客户端推送id,对应uni-id-device表的push_clientid + */ + cid : string, + /** + * 错误描述 + */ + errMsg : string +}; +export type GetPushClientIdSuccessCallback = (result : GetPushClientIdSuccess) => void; +export type GetPushClientIdFail = UniError; +export type GetPushClientIdFailCallback = (result : GetPushClientIdFail) => void; +export type GetPushClientIdComplete = any; +export type GetPushClientIdCompleteCallback = (result : GetPushClientIdComplete) => void; +export type GetPushClientIdOptions = { + /** + * 接口调用成功的回调函数 + * @defaultValue null + */ + success : GetPushClientIdSuccessCallback | null, + /** + * 接口调用失败的回调函数 + * @defaultValue null + */ + fail : GetPushClientIdFailCallback | null, + /** + * 接口调用结束的回调函数(调用成功、失败都会执行) + * @defaultValue null + */ + complete : GetPushClientIdCompleteCallback | null +}; +/** + * 事件类型 + * - click 从系统推送服务点击消息启动应用事件 + * - receive 应用从推送服务器接收到推送消息事件 + */ +export type OnPushMessageType = "click" | "receive"; + +export type OnPushMessageCallbackResult = { + /** + * 事件类型 + * @type{OnPushMessageType} + */ + type : OnPushMessageType, + /** + * 消息内容 + */ + data : UTSJSONObject +}; + +export type OnPushMessageCallback = (result : OnPushMessageCallbackResult) => void; +export type OnPushMessage = (callback : OnPushMessageCallback) => void; +export type OffPushMessage = (callback : OnPushMessageCallback) => void; + + +export type GetChannelManager = () => ChannelManager; +export type SetPushChannelOptions = { + /** + * 添加的声音文件,注意raw目录下必须要有 ,不传此字段将使用默认铃音。 + * @defaultValue null + */ + soundName? : string | null, + /** + * 通知渠道id + */ + channelId : string, + /** + * 通知渠道描述 + */ + channelDesc : string, + /** + * 呼吸灯闪烁 + * @defaultValue false + */ + enableLights? : boolean | null, + /** + * 震动 + * @defaultValue false + */ + enableVibration? : boolean | null, + /** + * 通知的重要性级别,可选范围IMPORTANCE_LOW:2、IMPORTANCE_DEFAULT:3、IMPORTANCE_HIGH:4 。 + * @defaultValue 3 + */ + importance? : number | null, + /** + * 锁屏可见性,可选范围VISIBILITY_PRIVATE:0、VISIBILITY_PUBLIC:1、VISIBILITY_SECRET:-1、VISIBILITY_NO_OVERRIDE:-1000。 + * @defaultValue -1000 + */ + lockscreenVisibility? : number | null +} +export interface ChannelManager { + /** + * 设置推送渠道 + * + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + */ + setPushChannel(options : SetPushChannelOptions) : void; + /** + * 获取当前应用注册的所有的通知渠道。 + * + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + */ + getAllChannels() : Array; +} + +export type CreatePushMessage = (options : CreatePushMessageOptions) => void; +export type CreatePushMessageSuccess = {}; +export type CreatePushMessageSuccessCallback = (result : CreatePushMessageSuccess) => void; +export type CreatePushMessageFail = UniError; +export type CreatePushMessageFailCallback = (result : CreatePushMessageFail) => void; +export type CreatePushMessageComplete = any; +export type CreatePushMessageCompleteCallback = (result : CreatePushMessageComplete) => void; +export type CreatePushMessageOptions = { + /** + * 是否覆盖上一次提示的消息 + * @type boolean + * @defaultValue false + */ + cover? : boolean | null, + /** + * 提示消息延迟显示的时间,单位为s + * @defaultValue 0 + */ + delay? : number | null, + /** + * 推送消息的图标 + * @defaultValue null + */ + icon? : string | null, + /** + * 推送消息的提示音 + * - system: 使用系统通知提示音(默认值) + * - none: 不使用提示音 + * @type 'system' | 'none' + * @defaultValue "system" + */ + sound? : string | null, + /** + * 推送消息的标题 + * @defaultValue "" + */ + title? : string | null, + /** + * 消息显示的内容,在系统通知中心中显示的文本内容 + */ + content : string, + /** + * 消息承载的数据,可根据业务逻辑自定义数据格式 + * @defaultValue null + */ + payload? : any | null, + /** + * 消息上显示的提示时间 + * @defaultValue 当前时间 + */ + when? : number | null, + /** + * 渠道id + * @defaultValue "DcloudChannelID" + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + */ + channelId? : string | null, + /** + * 通知类别 + * @defaultValue null + * @uniPlatform { + * "app": { + * "android": { + * "osVer": "4.4", + * "uniVer": "√", + * "unixVer": "3.97" + * }, + * "ios": { + * "osVer": "9.0", + * "uniVer": "x", + * "unixVer": "x" + * } + * } + * } + */ + category? : string | null, + /** + * 接口调用成功的回调函数 + * @defaultValue null + */ + success : CreatePushMessageSuccessCallback | null, + /** + * 接口调用失败的回调函数 + * @defaultValue null + */ + fail : CreatePushMessageFailCallback | null, + /** + * 接口调用结束的回调函数(调用成功、失败都会执行) + * @defaultValue null + */ + complete : CreatePushMessageCompleteCallback | null +};