├── .gitignore
├── LICENSE
├── README.md
├── assets
├── arch.png
├── boost.png
├── favor-example.png
├── logo.png
├── mmiot_ecdsa_sign.sh
├── seqDiag.png
├── wmpf-signup-device-multi-platform-util-jvm-0.0.1-sources.jar
├── wmpf-signup-device-multi-platform-util-jvm-0.0.1.jar
└── wxa_demo.gif
├── wmpf-activate-util
├── .gitignore
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── WmpfDeviceSignUpUtil.kt
│ ├── commonTest
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SampleTests.kt
│ ├── jsMain
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SignJs.kt
│ ├── jsTest
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SampleTestsJS.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SignJvm.kt
│ ├── jvmTest
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SampleTestsJVM.kt
│ ├── macosMain
│ └── kotlin
│ │ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── activate
│ │ └── util
│ │ └── SignMacos.kt
│ └── macosTest
│ └── kotlin
│ └── com
│ └── tencent
│ └── wmpf
│ └── activate
│ └── util
│ └── SampleTestsNative.kt
└── wmpf-demo
├── .gitignore
├── app
├── build.gradle
├── libs
│ └── wmpf-cli-2.2.0.aar
├── proguard-rules.pro
└── src
│ ├── experience
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── tencent
│ │ │ └── wmpf
│ │ │ └── demo
│ │ │ └── experience
│ │ │ └── ExperienceActivity.kt
│ └── res
│ │ └── layout
│ │ └── activity_experience.xml
│ ├── guide
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── tencent
│ │ │ └── wmpf
│ │ │ └── demo
│ │ │ └── activity
│ │ │ └── GuideActivity.kt
│ └── res
│ │ └── layout
│ │ └── activity_guide.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── tencent
│ │ └── wmpf
│ │ └── demo
│ │ ├── Cgi.kt
│ │ ├── DemoApplication.kt
│ │ ├── V1api.kt
│ │ ├── provider
│ │ └── ClientProvider.kt
│ │ ├── thirdpart
│ │ ├── ThirdPartApiDemoActivity.kt
│ │ └── ThirdpartConstants.kt
│ │ ├── ui
│ │ ├── ApiActivity.kt
│ │ ├── DocumentActivity.kt
│ │ ├── FastExperienceActivity.kt
│ │ ├── LaunchWxaAppByScanInvoker.kt
│ │ ├── MpDeviceActivity.kt
│ │ ├── PushMsgQuickStartActivity.kt
│ │ └── VoipActivity.kt
│ │ └── utils
│ │ ├── WMPFDemoLogger.kt
│ │ └── WMPFDemoUtil.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_document.xml
│ ├── activity_fast_experience.xml
│ ├── activity_mp_device.xml
│ ├── activity_push_msg_quick_start.xml
│ └── activity_voip.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── arrays.xml
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── checkstyle.xml
├── debug.keystore
├── gradle.properties
├── gradle
├── check.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lint.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Tencent
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | 在没有安装微信客户端的设备
15 | 运行微信小程序
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | > 当前分支建议用于 WMPF Service APK >= 2.2(最低支持 2.1)。若使用低版本,请参考 apiv1 分支。
30 | >
31 | > WMPF 版本发布已迁移至[官方发布页](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/download.html),github 仅提供 DEMO 代码,不再用做版本发布。
32 |
33 | ## 💡 WMPF是什么
34 |
35 |
36 |
37 |
38 |
39 |
40 | WMPF是微信小程序硬件框架(WeChat Mini-Program Framework)的简称,该运行环境能让硬件在脱离微信客户端的情况下运行微信小程序,目前已支持Android,未来会支持到更多的平台。
41 |
42 | WMPF 上运行的微信小程序,与手机客户端的微信小程序能力基本一致。通过 WMPF,开发者可以将微信平台能力赋能到硬件设备上。
43 |
44 | 想要顺利的运行小程序,需要两个apk
45 |
46 | * **WMPF Service apk**:由微信定期打包发布,作为小程序框架的宿主环境
47 | * **WMPF Client apk**:作为Service的调用方,需要你参考示例DEMO及文档进行定制
48 |
49 |
50 |
51 |
52 |
53 |
54 | 本项目提供了 WMPF Service apk下 载,WMPF Client 示例 DEMO 以及说明文档
55 |
56 | ```
57 | 目录结构
58 | wmpf_demo_external
59 | ├── wmpf-demo 示例 DEMO,帮助开发者快速构建自己的 client。(Android kotlin 项目)
60 | ├── wmpf-activate-util 设备签名工具,用来进行设备签名的生成和校验示例。(服务端 Java 项目)
61 | ```
62 |
63 | #### 关于flavors构建变体
64 |
65 | wmpf-demo 有两个flavor,flavor 可以在 Android Studio 中切换
66 |
67 | * experience:用于在不正式激活硬件的情况下体验 WMPF 运行小程序,不需要注册设备,只需要在[wecooper-快速体验-绑定小程序](https://wecooper.weixin.qq.com/)获取 ticket 即可运行。不能用于生产环境。
68 | * guide:提供API的使用示例,需要激活硬件后使用,可在此基础上做修改定制。
69 |
70 |
71 |
72 |
73 |
74 | ## 💻 WMPF使用场景
75 |
76 | WMPF可以应用在各行各业的安卓系统平板电脑、大屏设备等硬件,提供低成本屏幕互动解决方案,可接入设备包括但不限于:
77 |
78 | * 智慧零售:收银机 / 排号机 / 商场导航屏 / 自动贩卖机 / 点餐平板 / 互动广告屏幕等
79 | * 家用及娱乐设备:智能冰箱 / 儿童平板 / 跑步机 / 电视机 / KTV点唱机等
80 | * 公共服务:医院挂号机 / 图书租赁设备 / 美术馆办卡机等
81 | * 办公设备:教育平板 / 会议终端 / 会议投屏等
82 |
83 |
84 | ## 📖 文档
85 |
86 | 项目的[官方文档](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/)提供了更详细的接入指南,后台API,专有接口,硬件注册步骤等文档
87 |
88 | ## ⚡️ 快速体验
89 |
90 | 请阅读文档中[「快速体验」](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/demo.html)部分
91 |
92 | ## 🚀 正式接入流程
93 |
94 | 请阅读文档中[「接入指引」](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/process.html)和[「快速上手」](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/quick-start.html)部分
95 |
96 | ## 🙋 帮助
97 |
98 | 可以在[微信开放社区「硬件服务」板块](https://developers.weixin.qq.com/community/minihome/mixflow/2351405025148862470)发帖反馈
99 |
100 | ## ⚖️ License
101 |
102 | 该项目在[MIT](https://github.com/wmpf/LICENSE)协议的许可下使用
103 |
--------------------------------------------------------------------------------
/assets/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/arch.png
--------------------------------------------------------------------------------
/assets/boost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/boost.png
--------------------------------------------------------------------------------
/assets/favor-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/favor-example.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/logo.png
--------------------------------------------------------------------------------
/assets/mmiot_ecdsa_sign.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | time_stamp=`date +%s`
4 |
5 | function CheckStop()
6 | {
7 | if [ $? -ne 0 ]; then
8 | echo "execute fail, error on line_no:"$1" exit!!!"
9 | exit
10 | fi
11 | }
12 |
13 | function GenEcdsaKey()
14 | {
15 | ec_param_file_path="/tmp/ec_param.pem."$time_stamp
16 | openssl ecparam -out $ec_param_file_path -name prime256v1 -genkey
17 | CheckStop $LINENO
18 | openssl genpkey -paramfile $ec_param_file_path -out $1
19 | CheckStop $LINENO
20 | openssl pkey -in $1 -inform PEM -out $2 -outform PEM -pubout
21 | CheckStop $LINENO
22 | rm $ec_param_file_path
23 | echo "gen_ecdsa_key succ prikey_path:"$1" pubkey_path:"$2
24 | }
25 |
26 | function GenEcdsaSign()
27 | {
28 | ec_sign_info_file="/tmp/ec_sign_info_file."$time_stamp
29 | ec_sign_info_sha256="/tmp/ec_sign_info_sha256."$time_stamp
30 | ec_binary_sign_file="/tmp/ec_binary_sign_file."$time_stamp
31 | echo -n "$1"_"$2" > $ec_sign_info_file
32 | openssl dgst -sha256 -binary -out $ec_sign_info_sha256 $ec_sign_info_file
33 | CheckStop $LINENO
34 | openssl pkeyutl -sign -in $ec_sign_info_sha256 -out $ec_binary_sign_file -inkey $3 -keyform PEM
35 | CheckStop $LINENO
36 | openssl base64 -e -in $ec_binary_sign_file -out $4
37 | CheckStop $LINENO
38 | rm $ec_sign_info_file $ec_sign_info_sha256 $ec_binary_sign_file
39 | echo "gen_ecdsa_sign succ sign_file_path:"$4
40 | }
41 |
42 | function VerifyEcdsaSign()
43 | {
44 | ec_sign_info_file="/tmp/ec_sign_info_file."$time_stamp
45 | ec_sign_info_sha256="/tmp/ec_sign_info_sha256."$time_stamp
46 | ec_binary_sign_file="/tmp/ec_binary_sign_file."$time_stamp
47 | echo -n "$1"_"$2" > $ec_sign_info_file
48 | openssl dgst -sha256 -binary -out $ec_sign_info_sha256 $ec_sign_info_file
49 | CheckStop $LINENO
50 | openssl base64 -d -in $4 -out $ec_binary_sign_file
51 | CheckStop $LINENO
52 | openssl pkeyutl -verify -in $ec_sign_info_sha256 -sigfile $ec_binary_sign_file -pubin -inkey $3 -keyform PEM
53 | rm $ec_sign_info_file $ec_sign_info_sha256 $ec_binary_sign_file
54 | }
55 |
56 | function Usage()
57 | {
58 | echo "Usage:"
59 | echo "mmiot_ecdsa_sign.sh gen_ecdsa_key "
60 | echo "mmiot_ecdsa_sign.sh gen_ecdsa_sign "
61 | echo "mmiot_ecdsa_sign.sh verify_ecdsa_sign "
62 | }
63 |
64 |
65 |
66 | if [[ $# -eq 3 && $1 = "gen_ecdsa_key" ]];then
67 | GenEcdsaKey $2 $3
68 |
69 | elif [[ $# -eq 5 && $1 = "gen_ecdsa_sign" ]];then
70 | GenEcdsaSign $2 $3 $4 $5
71 |
72 | elif [[ $# -eq 5 && $1 = "verify_ecdsa_sign" ]];then
73 | VerifyEcdsaSign $2 $3 $4 $5
74 | else
75 | echo "------------------- Invalid Args !!! -------------------"
76 | Usage
77 | fi
78 |
79 |
80 |
--------------------------------------------------------------------------------
/assets/seqDiag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/seqDiag.png
--------------------------------------------------------------------------------
/assets/wmpf-signup-device-multi-platform-util-jvm-0.0.1-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/wmpf-signup-device-multi-platform-util-jvm-0.0.1-sources.jar
--------------------------------------------------------------------------------
/assets/wmpf-signup-device-multi-platform-util-jvm-0.0.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/wmpf-signup-device-multi-platform-util-jvm-0.0.1.jar
--------------------------------------------------------------------------------
/assets/wxa_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/assets/wxa_demo.gif
--------------------------------------------------------------------------------
/wmpf-activate-util/.gitignore:
--------------------------------------------------------------------------------
1 | #
2 | # Visual Studio Code
3 | #
4 | **/.vscode
5 |
6 | #
7 | # Eclipse
8 | #
9 | **/.settings
10 | **/.externalToolBuilders
11 |
12 | #
13 | # IntelliJ IDEA
14 | #
15 | **/.idea/
16 | *.iws
17 | *.ipr
18 | *.iml
19 | .idea/
20 |
21 | #
22 | # and Android Studio
23 | #
24 | build/
25 | **/build/
26 | .gradle/
27 | **/local.properties
28 | reports/
29 | captures/
30 | **/.externalNativeBuild/
31 | **/.classpath
32 |
33 | #
34 | # Python
35 | #
36 | *.pyc
37 |
38 | #
39 | # OSX files
40 | #
41 | **/.DS_Store
42 |
43 | #
44 | # SCM
45 | #
46 | **/.svn
47 |
--------------------------------------------------------------------------------
/wmpf-activate-util/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.multiplatform' version '1.3.72'
3 | }
4 | repositories {
5 | mavenCentral()
6 | }
7 | group 'com.tencent.wmpf.activate.util'
8 | version '0.0.1'
9 |
10 | apply plugin: 'maven-publish'
11 |
12 | kotlin {
13 | jvm()
14 | // js {
15 | // browser {
16 | // }
17 | // nodejs {
18 | // }
19 | // }
20 | // For ARM, should be changed to iosArm32 or iosArm64
21 | // For Linux, should be changed to e.g. linuxX64
22 | // For MacOS, should be changed to e.g. macosX64
23 | // For Windows, should be changed to e.g. mingwX64
24 | // macosX64("macos")
25 | sourceSets {
26 | commonMain {
27 | dependencies {
28 | implementation kotlin('stdlib-common')
29 | }
30 | }
31 | commonTest {
32 | dependencies {
33 | implementation kotlin('test-common')
34 | implementation kotlin('test-annotations-common')
35 | }
36 | }
37 | jvmMain {
38 | dependencies {
39 | implementation kotlin('stdlib-jdk8')
40 | implementation("com.squareup.okhttp3:okhttp:4.7.2")
41 | implementation 'com.google.code.gson:gson:2.8.6'
42 | }
43 | }
44 | jvmTest {
45 | dependencies {
46 | implementation kotlin('test')
47 | implementation kotlin('test-junit')
48 | }
49 | }
50 | // jsMain {
51 | // dependencies {
52 | // implementation kotlin('stdlib-js')
53 | // }
54 | // }
55 | // jsTest {
56 | // dependencies {
57 | // implementation kotlin('test-js')
58 | // }
59 | // }
60 | macosMain {
61 | }
62 | macosTest {
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/wmpf-activate-util/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 23 14:19:37 CST 2020
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/wmpf-activate-util/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'wmpf-signup-device-multi-platform-util'
2 | enableFeaturePreview('GRADLE_METADATA')
3 |
--------------------------------------------------------------------------------
/wmpf-activate-util/src/commonMain/kotlin/com/tencent/wmpf/activate/util/WmpfDeviceSignUpUtil.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | expect object WmpfDeviceSignUpUtil {
4 | fun getPrivateKeyPublicKeyPair(): Pair
5 |
6 | fun getSignature(productId: String, deviceId: String, privateKey: String): String
7 |
8 | fun verifySignature(productId: String, deviceId: String, publicKey: String, signature: String): Boolean
9 |
10 | fun addDevicesToWeChatServer(deviceInfo: DeviceInfo, accessToken: String): AddDeviceResp
11 |
12 | fun getAccessToken(appId: String, appSecret: String): AccessTokenResp
13 | }
14 |
15 | class AddDeviceResp {
16 | var errcode: Int = -1
17 | var errmsg: String = "not init"
18 |
19 | override fun toString(): String {
20 | return "AddDeviceResp(errcode=$errcode, errmsg='$errmsg')"
21 | }
22 | }
23 |
24 | class DeviceInfo {
25 | var product_id: String = ""
26 | var device_id_list: List = ArrayList()
27 | var model_name: String = ""
28 | }
29 |
30 | class AccessTokenResp{
31 | var errcode = -1
32 | var errmsg = "not init"
33 | var access_token = ""
34 | var expires_in = ""
35 | }
36 |
37 | expect object Platform {
38 | val name: String
39 | }
40 |
41 | fun getPlatformName(): String = Platform.name
--------------------------------------------------------------------------------
/wmpf-activate-util/src/commonTest/kotlin/com/tencent/wmpf/activate/util/SampleTests.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertTrue
5 |
6 | class SampleTests {
7 | @Test
8 | fun testMe() {
9 | }
10 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/jsMain/kotlin/com/tencent/wmpf/activate/util/SignJs.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | actual object WmpfDeviceSignUpUtil {
4 | actual fun getSignature(
5 | productId: String,
6 | deviceId: String,
7 | privateKey: String
8 | ): String {
9 | TODO("Not yet implemented")
10 | }
11 |
12 | actual fun verifySignature(
13 | productId: String,
14 | deviceId: String,
15 | privateKey: String,
16 | signature: String
17 | ): Boolean {
18 | TODO("Not yet implemented")
19 | }
20 |
21 | actual fun addDevicesToWeChatServer(
22 | accessToken: String,
23 | productId: String,
24 | deviceIds: Array,
25 | modelName: String
26 | ): String {
27 | TODO("Not yet implemented")
28 | }
29 |
30 | actual fun getPrivateKeyPublicKeyPair(keysStorageDir: String): Pair {
31 | TODO("Not yet implemented")
32 | }
33 | }
34 |
35 | actual object Platform {
36 | actual val name: String = "JS"
37 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/jsTest/kotlin/com/tencent/wmpf/activate/util/SampleTestsJS.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertTrue
5 |
6 | class SampleTestsJS {
7 | @Test
8 | fun testHello() {
9 | assertTrue("JS" in getPlatformName())
10 | }
11 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/jvmMain/kotlin/com/tencent/wmpf/activate/util/SignJvm.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | import com.google.gson.Gson
4 | import okhttp3.MediaType.Companion.toMediaTypeOrNull
5 | import okhttp3.OkHttpClient
6 | import okhttp3.Request
7 | import okhttp3.RequestBody
8 | import okhttp3.Response
9 | import java.io.File
10 | import java.io.IOException
11 | import java.nio.charset.Charset
12 | import java.util.*
13 | import java.util.concurrent.TimeUnit
14 |
15 |
16 | actual object WmpfDeviceSignUpUtil {
17 |
18 | private val client = OkHttpClient().newBuilder()
19 | .build()
20 |
21 | var workingDir: String = System.getProperty("java.io.tmpdir")
22 | set(value) {
23 | if (!value.endsWith(File.separator)) {
24 | field = value + File.separator
25 | } else {
26 | field = value
27 | }
28 | }
29 |
30 | @JvmStatic
31 | actual fun getPrivateKeyPublicKeyPair(): Pair {
32 | requestsCheck()
33 | var path = File(workingDir).canonicalPath
34 | if (!path.endsWith(File.separator)) {
35 | path += File.separator
36 | }
37 | workingDir = path
38 | val privateKeyFile = File(workingDir + "wmpfPrivateKeyPath.key")
39 | val publicKeyFile = File(workingDir + "wmpfPublicKeyPath.key")
40 | if (privateKeyFile.exists() && publicKeyFile.exists()) {
41 | return Pair(privateKeyFile.readText(), publicKeyFile.readText())
42 | }
43 | val ecParamFilePath = File("${workingDir}ec_param.pem.${secondsSinceEpoch()}").also { it.autoDelete() }
44 |
45 | "openssl ecparam -out ${ecParamFilePath.canonicalPath} -name prime256v1 -genkey".runCommand()
46 | "openssl genpkey -paramfile ${ecParamFilePath.canonicalPath} -out ${privateKeyFile.canonicalPath}".runCommand()
47 | "openssl pkey -in ${privateKeyFile.canonicalPath} -inform PEM -out ${publicKeyFile.canonicalPath} -outform PEM -pubout".runCommand()
48 | return Pair(privateKeyFile.readText(), publicKeyFile.readText())
49 | }
50 |
51 | @JvmStatic
52 | actual fun getSignature(productId: String, deviceId: String, privateKey: String): String {
53 | requestsCheck()
54 | val (ec_sign_info_file, ec_sign_info_sha256, ec_binary_sign_file) = createCommonFiles()
55 | val privateKeyFile = File("${workingDir}privateKey.${deviceId}.${secondsSinceEpoch()}").also { it.autoDelete() }
56 | val signFile = File("${workingDir}sign.${deviceId}.${secondsSinceEpoch()}").also { it.autoDelete() }
57 |
58 | privateKeyFile.writeText(privateKey)
59 | ec_sign_info_file.writeText("${productId}_$deviceId")
60 |
61 | "openssl dgst -sha256 -binary -out ${ec_sign_info_sha256.canonicalPath} ${ec_sign_info_file.canonicalPath}".runCommand()
62 | "openssl pkeyutl -sign -in ${ec_sign_info_sha256.canonicalPath} -out ${ec_binary_sign_file.canonicalPath} -inkey ${privateKeyFile.canonicalPath} -keyform PEM".runCommand()
63 | "openssl base64 -e -in ${ec_binary_sign_file.canonicalPath} -out ${signFile.canonicalPath}".runCommand()
64 | return signFile.readText()
65 | }
66 |
67 | @JvmStatic
68 | actual fun verifySignature(productId: String, deviceId: String, publicKey: String, signature: String): Boolean {
69 | requestsCheck()
70 | val (ec_sign_info_file, ec_sign_info_sha256, ec_binary_sign_file) = createCommonFiles()
71 | val signFile = File("${workingDir}signToCheck.${secondsSinceEpoch()}").also { it.autoDelete() }
72 | val publicFile = File("${workingDir}publicKeyToCheck.${secondsSinceEpoch()}").also { it.autoDelete() }
73 |
74 | publicFile.writeText(publicKey)
75 | signFile.writeText(signature)
76 | ec_sign_info_file.writeText("${productId}_$deviceId")
77 |
78 | "openssl dgst -sha256 -binary -out ${ec_sign_info_sha256.canonicalPath} ${ec_sign_info_file.canonicalPath}".runCommand()
79 | "openssl base64 -d -in ${signFile.canonicalPath} -out $ec_binary_sign_file".runCommand()
80 | val res =
81 | "openssl pkeyutl -verify -in ${ec_sign_info_sha256.canonicalPath} -sigfile ${ec_binary_sign_file.canonicalPath} -pubin -inkey ${publicFile.canonicalPath} -keyform PEM"
82 | .runCommandWithOutPut(redirect = ProcessBuilder.Redirect.PIPE)
83 | return res.contains("success", ignoreCase = true)
84 | }
85 |
86 |
87 | @JvmStatic
88 | actual fun getAccessToken(appId: String, appSecret: String): AccessTokenResp {
89 | val request = Request.Builder()
90 | .url("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}")
91 | .build()
92 | val ret = AccessTokenResp()
93 | try {
94 | val response = client.newCall(request).execute()
95 | if (response.isSuccessful) {
96 | return try {
97 | val respBody = response.body!!.source().readString(Charset.defaultCharset())
98 | val accessTokenResp = Gson().fromJson(respBody, AccessTokenResp::class.java)
99 | accessTokenResp.errmsg = ""
100 | accessTokenResp.errcode = 0
101 | accessTokenResp
102 | } catch (e: Exception) {
103 | ret.errmsg = e.message.toString()
104 | ret
105 | }
106 | } else {
107 | ret.errmsg = response.message
108 | ret.errcode = response.code
109 | return ret
110 | }
111 | } catch (e: Exception) {
112 | ret.errmsg = e.message.toString()
113 | return ret
114 | }
115 | }
116 |
117 | @JvmStatic
118 | actual fun addDevicesToWeChatServer(deviceInfo: DeviceInfo, accessToken: String): AddDeviceResp {
119 | val ret = AddDeviceResp()
120 | val gson = Gson()
121 | val reqData = gson.toJson(deviceInfo)
122 | try {
123 | val mediaType = "text/plain".toMediaTypeOrNull()
124 | val body: RequestBody = RequestBody.create(
125 | mediaType,
126 | reqData
127 | )
128 | val request: Request = Request.Builder()
129 | .url("https://api.weixin.qq.com/wxa/business/runtime/adddevice?access_token=${accessToken}")
130 | .method("POST", body)
131 | .addHeader("Content-Type", "text/plain")
132 | .build()
133 | val response: Response = client.newCall(request).execute()
134 | if (response.isSuccessful) {
135 | val resp = response.body!!.source().readString(Charset.defaultCharset())
136 | return Gson().fromJson(resp, AddDeviceResp::class.java)
137 | } else {
138 | ret.errcode = response.code
139 | ret.errmsg = response.message
140 | }
141 | } catch (e: Exception) {
142 | ret.errmsg = e.message.toString()
143 | }
144 | return ret
145 | }
146 |
147 |
148 | private fun secondsSinceEpoch(): Long {
149 | val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
150 | calendar.clear()
151 | calendar[2011, Calendar.OCTOBER] = 1
152 | return calendar.timeInMillis / 1000L
153 | }
154 |
155 | private fun File.autoDelete() {
156 | if (this.exists()) {
157 | this.delete()
158 | }
159 | this.createNewFile()
160 | this.deleteOnExit()
161 | }
162 |
163 | private fun createCommonFiles(): Triple {
164 | val ecSignInfoFile = File("${workingDir}ec_sign_info_file.${secondsSinceEpoch()}").also { it.autoDelete() }
165 | val ecSignInfoSha256 = File("${workingDir}ec_sign_info_sha256.${secondsSinceEpoch()}").also { it.autoDelete() }
166 | val ecBinarySignFile = File("${workingDir}ec_binary_sign_file.${secondsSinceEpoch()}").also { it.autoDelete() }
167 | return Triple(ecSignInfoFile, ecSignInfoSha256, ecBinarySignFile)
168 | }
169 |
170 | private fun String.runCommandWithOutPut(workingDir: File = File("./"), redirect: ProcessBuilder.Redirect): String {
171 | return try {
172 | val parts = this.split("\\s".toRegex())
173 | val proc = ProcessBuilder(*parts.toTypedArray())
174 | .directory(workingDir)
175 | .redirectOutput(redirect)
176 | .redirectError(redirect)
177 | .start()
178 | proc.waitFor(60, TimeUnit.MINUTES)
179 | val ret = proc.inputStream.bufferedReader().readText()
180 | ret
181 | } catch (e: IOException) {
182 | e.printStackTrace()
183 | ""
184 | }
185 | }
186 |
187 | private fun String.runCommand(workingDir: File = File("./")) {
188 | try {
189 | val parts = this.split("\\s".toRegex())
190 | val proc = ProcessBuilder(*parts.toTypedArray())
191 | .directory(workingDir)
192 | .redirectOutput(ProcessBuilder.Redirect.INHERIT)
193 | .redirectError(ProcessBuilder.Redirect.INHERIT)
194 | .start()
195 | proc.waitFor(60, TimeUnit.MINUTES)
196 | } catch (e: IOException) {
197 | e.printStackTrace()
198 | }
199 | }
200 |
201 | private fun requestsCheck() {
202 | // if ("which openssl".runCommandWithOutPut(redirect = ProcessBuilder.Redirect.INHERIT)
203 | // .contains("not found", ignoreCase = true)
204 | // ) {
205 | // throw RuntimeException("dependencies not satisfied, openssl is requested")
206 | // }
207 | }
208 | }
209 |
210 | actual object Platform {
211 | actual val name: String = "JVM"
212 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/jvmTest/kotlin/com/tencent/wmpf/activate/util/SampleTestsJVM.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | import kotlin.test.Test
4 |
5 |
6 | class SampleTestsJVM {
7 | @Test
8 | fun testSignUpDevice() {
9 | println("platform: " + getPlatformName())
10 | // 0. 设置该脚本的工作目录
11 | WmpfDeviceSignUpUtil.workingDir = System.getProperty("java.io.tmpdir")
12 | // 1. 生成公私钥
13 | // 如果你已经有一对公私钥,重命名为,放在workingDir下面即可
14 | // 公钥会被命名为:$workingDir/wmpfPrivateKeyPath.key
15 | // 私钥会被命名为:$workingDir/wmpfPublicKeyPath.key
16 | val (privateKey, publicKey) = WmpfDeviceSignUpUtil.getPrivateKeyPublicKeyPair()
17 | println("public key = [$publicKey]")
18 | val productId = "your-product-id-from-we-cooper"
19 | val deviceId = "your-customized-device-id"
20 | // 2. 获取Signature
21 | val signature = WmpfDeviceSignUpUtil.getSignature(
22 | productId,
23 | deviceId,
24 | privateKey
25 | )
26 | println("signature = [$signature]")
27 | val deviceInfo = DeviceInfo()
28 | deviceInfo.model_name = "your-model-name"
29 | deviceInfo.product_id = productId
30 | deviceInfo.device_id_list = Array(1) { deviceId }.toList()
31 | // 3. 获取accessToken
32 | // 入参:微信开放平台注册的移动应用appId,移动应用appSecret
33 | val accessTokenResp =
34 | WmpfDeviceSignUpUtil.getAccessToken("your-app-id", "your-app-secret")
35 | if (accessTokenResp.errcode == 0) {
36 | println("access token [${accessTokenResp.access_token}] expires = " + accessTokenResp.expires_in)
37 | // 4. 添加设备信息到微信后台
38 | val resp = WmpfDeviceSignUpUtil.addDevicesToWeChatServer(
39 | accessToken = accessTokenResp.access_token,
40 | deviceInfo = deviceInfo
41 | )
42 | println("addDevicesToWeChatServer resp = [$resp]")
43 | // 5. 校验结果和签名
44 | if (resp.errcode == 0 && WmpfDeviceSignUpUtil.verifySignature(
45 | productId,
46 | deviceId,
47 | publicKey,
48 | signature
49 | )
50 | ) {
51 | println("success")
52 | } else {
53 | println("fail: code = ${resp.errcode}, msg = ${resp.errmsg}")
54 | }
55 | } else {
56 | println("get access token fail code = ${accessTokenResp.errcode}, msg = ${accessTokenResp.errmsg}")
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/macosMain/kotlin/com/tencent/wmpf/activate/util/SignMacos.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | actual object WmpfDeviceSignUpUtil {
4 | actual fun getPrivateKeyPublicKeyPair(keysStorageDir: String): Pair {
5 | TODO("Not yet implemented")
6 | }
7 |
8 | actual fun getSignature(
9 | productId: String,
10 | deviceId: String,
11 | privateKey: String
12 | ): String {
13 | TODO("Not yet implemented")
14 | }
15 |
16 | actual fun verifySignature(
17 | productId: String,
18 | deviceId: String,
19 | privateKey: String,
20 | signature: String
21 | ): Boolean {
22 | TODO("Not yet implemented")
23 | }
24 |
25 | actual fun addDevicesToWeChatServer(deviceInfo: DeviceInfo): AddDeviceResp {
26 | TODO("Not yet implemented")
27 | }
28 |
29 | actual fun getAccessToken(
30 | appId: String,
31 | appSecret: String
32 | ): AccessTokenResp {
33 | TODO("Not yet implemented")
34 | }
35 |
36 | }
37 |
38 | actual object Platform {
39 | actual val name: String = "Native"
40 | }
--------------------------------------------------------------------------------
/wmpf-activate-util/src/macosTest/kotlin/com/tencent/wmpf/activate/util/SampleTestsNative.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.activate.util
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertTrue
5 |
6 | class SampleTestsNative {
7 | @Test
8 | fun testHello() {
9 | assertTrue("Native" in getPlatformName())
10 | }
11 | }
--------------------------------------------------------------------------------
/wmpf-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # IntelliJ
38 | *.iml
39 | .idea/
40 |
41 | # Keystore files
42 | # Uncomment the following line if you do not want to check your keystore files in.
43 | #*.jks
44 |
45 | # External native build folder generated in Android Studio 2.2 and later
46 | .externalNativeBuild
47 |
48 | # Google Services (e.g. APIs or Firebase)
49 | google-services.json
50 |
51 | # Freeline
52 | freeline.py
53 | freeline/
54 | freeline_project_description.json
55 |
56 | # fastlane
57 | fastlane/report.xml
58 | fastlane/Preview.html
59 | fastlane/screenshots
60 | fastlane/test_output
61 | fastlane/readme.md
62 |
63 |
--------------------------------------------------------------------------------
/wmpf-demo/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdk 32
6 | defaultConfig {
7 | applicationId "com.tencent.wmpf.demo"
8 | minSdkVersion 25
9 | //noinspection ExpiredTargetSdkVersion
10 | targetSdkVersion 32
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | // WARNING: WXA Apps run on each host AppId could be limited.
15 | // TODO: 替换成开发者在微信开放平台申请的 hostAppId
16 | buildConfigField 'String', 'HOST_APPID', '"REPLACE_WITH_REAL_VALUE"'
17 | // TODO: DANGEROUS! 危险!仅供测试使用,APP_SECRET 需要严格保密,不应流出服务端后台环境,更不应该放到客户端代码中。
18 | buildConfigField 'String', 'HOST_APPSECRET', '"REPLACE_WITH_REAL_VALUE"'
19 |
20 | // 仅在需要使用小程序设备认证的情况下需要指定 WXA_APPID, WXA_APPSECRET, WXA_MODEL_ID
21 | // TODO: 替换成开发者在微信公众平台申请的小程序 AppId
22 | buildConfigField 'String', 'WXA_APPID', '"REPLACE_WITH_REAL_VALUE"'
23 | // TODO: DANGEROUS! 危险!仅供测试使用,APP_SECRET 需要严格保密,不应流出服务端后台环境,更不应该放到客户端代码中。
24 | buildConfigField 'String', 'WXA_APPSECRET', '"REPLACE_WITH_REAL_VALUE"'
25 | buildConfigField 'String', 'WXA_MODEL_ID', '"REPLACE_WITH_REAL_VALUE"'
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = "1.8"
33 | }
34 | signingConfigs {
35 | debug {
36 | keyAlias 'androiddebugkey'
37 | keyPassword 'android'
38 | storeFile rootProject.file('debug.keystore')
39 | storePassword 'android'
40 | }
41 | release {
42 | keyAlias 'androiddebugkey'
43 | keyPassword 'android'
44 | storeFile rootProject.file('debug.keystore')
45 | storePassword 'android'
46 | }
47 | }
48 | buildTypes {
49 | release {
50 | signingConfig signingConfigs.debug
51 | minifyEnabled false
52 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
53 | }
54 | }
55 | flavorDimensions += 'product'
56 | productFlavors {
57 | guide {
58 | dimension 'product'
59 | applicationIdSuffix ".demo"
60 | versionNameSuffix "-demo"
61 | }
62 | experience {
63 | dimension 'product'
64 | applicationIdSuffix ".experience"
65 | versionNameSuffix "-experience"
66 | }
67 | }
68 | namespace 'com.tencent.wmpf.demo'
69 | }
70 |
71 | dependencies {
72 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
73 |
74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
75 | implementation 'androidx.appcompat:appcompat:1.1.0'
76 | implementation 'androidx.preference:preference:1.2.1'
77 | implementation 'androidx.multidex:multidex:2.0.1'
78 |
79 | api 'com.squareup.okhttp3:okhttp:4.11.0'
80 | api 'cn.yipianfengye.android:zxing-library:2.2'
81 | }
82 |
--------------------------------------------------------------------------------
/wmpf-demo/app/libs/wmpf-cli-2.2.0.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmpf/wmpf_demo_external/c2f9272c6e746c7ec8f486804bb29f2a15fb185b/wmpf-demo/app/libs/wmpf-cli-2.2.0.aar
--------------------------------------------------------------------------------
/wmpf-demo/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/wmpf-demo/app/src/experience/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/wmpf-demo/app/src/experience/java/com/tencent/wmpf/demo/experience/ExperienceActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tencent.wmpf.demo.experience
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import android.view.inputmethod.InputMethodManager
6 | import android.widget.Button
7 | import android.widget.Switch
8 | import android.widget.TextView
9 | import androidx.appcompat.app.AppCompatActivity
10 | import androidx.preference.PreferenceManager
11 | import com.tencent.wmpf.app.WMPFBoot
12 | import com.tencent.wmpf.cli.api.WMPF
13 | import com.tencent.wmpf.cli.api.WMPFApiException
14 | import com.tencent.wmpf.cli.api.WMPFMiniProgramApi.LandscapeMode
15 | import com.tencent.wmpf.cli.model.WMPFDevice
16 | import com.tencent.wmpf.cli.model.WMPFStartAppParams
17 | import com.tencent.wmpf.cli.model.WMPFStartAppParams.WMPFAppType
18 | import com.tencent.wmpf.cli.task.TaskError
19 | import com.tencent.wmpf.demo.Cgi
20 | import com.tencent.wmpf.demo.R
21 | import com.tencent.wmpf.demo.utils.WMPFDemoLogger
22 | import com.tencent.wmpf.demo.utils.WMPFDemoUtil
23 |
24 | /**
25 | * Created by complexzeng on 2020/6/17 2:59 PM.
26 | */
27 | class ExperienceActivity : AppCompatActivity() {
28 | private companion object {
29 | private const val TAG = "ExperienceActivity"
30 | private const val DEMO_HOST_APPID = "wx94ef6bb77b573d05"
31 | }
32 |
33 | private var landscapeMode = LandscapeMode.NORMAL
34 | private lateinit var logger: WMPFDemoLogger
35 | private var wmpfDevice: WMPFDevice? = null
36 |
37 | private fun hideKeyboard() {
38 | val view = this.currentFocus
39 | if (view != null) {
40 | val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
41 | imm.hideSoftInputFromWindow(view.windowToken, 0)
42 | }
43 | }
44 |
45 | private fun init(
46 | appId: String,
47 | ticket: String,
48 | ) {
49 | if (appId.isEmpty() || ticket.isEmpty()) {
50 | throw Exception("请输入 appId 和 ticket")
51 | }
52 |
53 | var newDevice: WMPFDevice?
54 |
55 | try {
56 | val res = Cgi.getTestDeviceInfo(ticket, appId, DEMO_HOST_APPID)
57 | newDevice = WMPFDevice(
58 | DEMO_HOST_APPID, res.productId, res.keyVersion, res.deviceId, res.signature
59 | )
60 | logger.i("设备信息获取成功: $newDevice")
61 | } catch (e: Exception) {
62 | throw Exception("请求设备信息失败: " + e.message)
63 | }
64 |
65 |
66 | if (wmpfDevice == null) {
67 | wmpfDevice = newDevice
68 | // init 只能调用一次
69 | WMPFBoot.init(this.applicationContext, wmpfDevice)
70 | } else if (wmpfDevice != newDevice) {
71 | throw Exception("设备信息发生变化,请重新启动应用。")
72 | }
73 |
74 | // 从 WMPF-cli 2.2 开始,可以不显式调用 activateDevice
75 | try {
76 | logger.i("--------设备激活中--------")
77 | WMPF.getInstance().deviceApi.activateDevice()
78 | } catch (e: WMPFApiException) {
79 | Log.e(TAG, "error: $e")
80 | if (e.errCode == TaskError.DISCONNECTED.errCode) {
81 | throw Exception("设备激活失败,请确认 WMPF 处于运行状态")
82 | } else if (e.errMsg == "DEVICE_CHANGED") {
83 | throw Exception("deviceId 发生变化,请清除 WMPF Apk 缓存后重试")
84 | } else {
85 | throw Exception("设备激活失败: " + e.message)
86 | }
87 | }
88 | logger.i("设备激活成功,初始化完成")
89 | }
90 |
91 | private fun launchMiniProgram(
92 | appId: String, ticket: String, path: String, landscapeMode: LandscapeMode
93 | ) {
94 | this.hideKeyboard()
95 | logger.clear()
96 | try {
97 | init(appId, ticket)
98 | } catch (e: Exception) {
99 | logger.e("初始化失败", e)
100 | return
101 | }
102 |
103 | logger.i("--------开始启动小程序--------")
104 | try {
105 | WMPF.getInstance().miniProgramApi.launchMiniProgram(
106 | WMPFStartAppParams(appId, path, WMPFAppType.APP_TYPE_RELEASE), false, landscapeMode
107 | )
108 | logger.i("启动小程序成功")
109 | } catch (e: Exception) {
110 | logger.e("启动小程序失败", e)
111 | }
112 | }
113 |
114 | override fun onCreate(savedInstanceState: Bundle?) {
115 | super.onCreate(savedInstanceState)
116 | setContentView(R.layout.activity_experience)
117 | logger = WMPFDemoLogger(TAG, this, findViewById(R.id.resp_tv))
118 |
119 | val landscapeSwitch = findViewById(R.id.switch_landscape)
120 | landscapeSwitch.setOnCheckedChangeListener { _, isClicked ->
121 | landscapeMode = if (isClicked) {
122 | LandscapeMode.LANDSCAPE_COMPAT
123 | } else {
124 | LandscapeMode.NORMAL
125 | }
126 | }
127 |
128 | val appIdView = findViewById(R.id.et_launch_app_id)
129 | val ticketView = findViewById(R.id.et_ticket)
130 | val pathView = findViewById(R.id.et_path)
131 |
132 | val perf = PreferenceManager.getDefaultSharedPreferences(applicationContext)
133 |
134 | val savedAppId = perf.getString("appId", "")
135 | val savedTicket = perf.getString("ticket", "")
136 | val savedPath = perf.getString("path", "")
137 |
138 | if (!savedAppId.isNullOrBlank()) {
139 | appIdView.text = savedAppId
140 | }
141 |
142 | if (!savedTicket.isNullOrBlank()) {
143 | ticketView.text = savedTicket
144 | }
145 |
146 | if (!savedPath.isNullOrBlank()) {
147 | pathView.text = savedPath
148 | }
149 |
150 | findViewById