@onekeyfe/react-native-jpush 0.0.0-stage → 6.7.0-alpha.436

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -2
  3. package/ReactNativeJpush.podspec +66 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +143 -0
  6. package/android/fcm/build.gradle +29 -0
  7. package/android/fcm/consumer-rules.pro +2 -0
  8. package/android/fcm/src/main/AndroidManifest.xml +16 -0
  9. package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmMessagingService.java +72 -0
  10. package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmPushManager.java +69 -0
  11. package/android/gradle.properties +4 -0
  12. package/android/src/main/AndroidManifest.xml +143 -0
  13. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  14. package/android/src/main/java/cn/android/service/JTransitActivity.java +19 -0
  15. package/android/src/main/java/cn/jiguang/android/BuildConfig.java +10 -0
  16. package/android/src/main/java/cn/jiguang/android/IDataShare.java +190 -0
  17. package/android/src/main/java/cn/jiguang/api/BaseLogger.java +82 -0
  18. package/android/src/main/java/cn/jiguang/api/JAction.java +22 -0
  19. package/android/src/main/java/cn/jiguang/api/JActionExtra.java +21 -0
  20. package/android/src/main/java/cn/jiguang/api/JAnalyticsAction.java +11 -0
  21. package/android/src/main/java/cn/jiguang/api/JCoreInterface.java +228 -0
  22. package/android/src/main/java/cn/jiguang/api/JCoreManager.java +156 -0
  23. package/android/src/main/java/cn/jiguang/api/JDispatchAction.java +66 -0
  24. package/android/src/main/java/cn/jiguang/api/MultiSpHelper.java +114 -0
  25. package/android/src/main/java/cn/jiguang/api/ReportCallBack.java +9 -0
  26. package/android/src/main/java/cn/jiguang/api/SdkType.java +13 -0
  27. package/android/src/main/java/cn/jiguang/api/utils/JCollectionAuth.java +23 -0
  28. package/android/src/main/java/cn/jiguang/core/JCoreCrypto.java +545 -0
  29. package/android/src/main/java/cn/jiguang/core/JCoreStorage.java +737 -0
  30. package/android/src/main/java/cn/jiguang/core/JCoreTcpEngine.java +1395 -0
  31. package/android/src/main/java/cn/jiguang/core/JCoreWireCodec.java +1716 -0
  32. package/android/src/main/java/cn/jiguang/internal/ActionManager.java +63 -0
  33. package/android/src/main/java/cn/jiguang/internal/JConstants.java +61 -0
  34. package/android/src/main/java/cn/jiguang/internal/JCoreHelperAction.java +12 -0
  35. package/android/src/main/java/cn/jiguang/internal/JCoreInternalHelper.java +102 -0
  36. package/android/src/main/java/cn/jiguang/net/DefaultHostVerifier.java +40 -0
  37. package/android/src/main/java/cn/jiguang/net/HttpRequest.java +78 -0
  38. package/android/src/main/java/cn/jiguang/net/HttpResponse.java +48 -0
  39. package/android/src/main/java/cn/jiguang/net/HttpUtils.java +106 -0
  40. package/android/src/main/java/cn/jiguang/plugins/core/JCoreModule.java +91 -0
  41. package/android/src/main/java/cn/jiguang/plugins/core/JCorePackage.java +27 -0
  42. package/android/src/main/java/cn/jiguang/plugins/push/JPushModule.java +773 -0
  43. package/android/src/main/java/cn/jiguang/plugins/push/JPushPackage.java +25 -0
  44. package/android/src/main/java/cn/jiguang/plugins/push/common/JConstants.java +88 -0
  45. package/android/src/main/java/cn/jiguang/plugins/push/common/JLogger.java +122 -0
  46. package/android/src/main/java/cn/jiguang/plugins/push/helper/JPushHelper.java +710 -0
  47. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushBroadcastReceiver.java +37 -0
  48. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushModuleReceiver.java +264 -0
  49. package/android/src/main/java/cn/jiguang/plugins/service/JCoreModuleService.java +7 -0
  50. package/android/src/main/java/cn/jpush/android/api/BasicPushNotificationBuilder.java +83 -0
  51. package/android/src/main/java/cn/jpush/android/api/CallBackParams.java +50 -0
  52. package/android/src/main/java/cn/jpush/android/api/CmdMessage.java +33 -0
  53. package/android/src/main/java/cn/jpush/android/api/CustomMessage.java +28 -0
  54. package/android/src/main/java/cn/jpush/android/api/CustomPushNotificationBuilder.java +81 -0
  55. package/android/src/main/java/cn/jpush/android/api/DefaultPushNotificationBuilder.java +90 -0
  56. package/android/src/main/java/cn/jpush/android/api/InAppSlotParams.java +65 -0
  57. package/android/src/main/java/cn/jpush/android/api/InstrumentedActivity.java +17 -0
  58. package/android/src/main/java/cn/jpush/android/api/InstrumentedListActivity.java +17 -0
  59. package/android/src/main/java/cn/jpush/android/api/JPushInterface.java +490 -0
  60. package/android/src/main/java/cn/jpush/android/api/JPushMessage.java +121 -0
  61. package/android/src/main/java/cn/jpush/android/api/JThirdPlatFormInterface.java +247 -0
  62. package/android/src/main/java/cn/jpush/android/api/MultiActionsNotificationBuilder.java +72 -0
  63. package/android/src/main/java/cn/jpush/android/api/NotificationCustomButton.java +50 -0
  64. package/android/src/main/java/cn/jpush/android/api/NotificationMessage.java +67 -0
  65. package/android/src/main/java/cn/jpush/android/api/PushNotificationBuilder.java +13 -0
  66. package/android/src/main/java/cn/jpush/android/api/TagAliasCallback.java +8 -0
  67. package/android/src/main/java/cn/jpush/android/data/JPushCollectControl.java +96 -0
  68. package/android/src/main/java/cn/jpush/android/data/JPushConfig.java +79 -0
  69. package/android/src/main/java/cn/jpush/android/data/JPushConfigEx.java +41 -0
  70. package/android/src/main/java/cn/jpush/android/data/JPushLocalNotification.java +155 -0
  71. package/android/src/main/java/cn/jpush/android/helper/JCoreHelper.java +70 -0
  72. package/android/src/main/java/cn/jpush/android/helper/Logger.java +82 -0
  73. package/android/src/main/java/cn/jpush/android/local/ActionHelper.java +1277 -0
  74. package/android/src/main/java/cn/jpush/android/local/ActivityLifeCallBack.java +47 -0
  75. package/android/src/main/java/cn/jpush/android/local/JPushAction.java +67 -0
  76. package/android/src/main/java/cn/jpush/android/local/JPushConstants.java +34 -0
  77. package/android/src/main/java/cn/jpush/android/local/JPushResponse.java +26 -0
  78. package/android/src/main/java/cn/jpush/android/local/ProxyActivityAction.java +34 -0
  79. package/android/src/main/java/cn/jpush/android/service/AlarmReceiver.java +15 -0
  80. package/android/src/main/java/cn/jpush/android/service/BActivity.java +19 -0
  81. package/android/src/main/java/cn/jpush/android/service/DActivity.java +19 -0
  82. package/android/src/main/java/cn/jpush/android/service/DaemonService.java +17 -0
  83. package/android/src/main/java/cn/jpush/android/service/DataProvider.java +136 -0
  84. package/android/src/main/java/cn/jpush/android/service/DataShare.java +129 -0
  85. package/android/src/main/java/cn/jpush/android/service/DownloadProvider.java +139 -0
  86. package/android/src/main/java/cn/jpush/android/service/InitProvider.java +48 -0
  87. package/android/src/main/java/cn/jpush/android/service/JCommonService.java +79 -0
  88. package/android/src/main/java/cn/jpush/android/service/JNotifyActivity.java +28 -0
  89. package/android/src/main/java/cn/jpush/android/service/JPushMessageReceiver.java +94 -0
  90. package/android/src/main/java/cn/jpush/android/service/PushReceiver.java +26 -0
  91. package/android/src/main/java/cn/jpush/android/service/PushService.java +28 -0
  92. package/android/src/main/java/cn/jpush/android/service/SchedulerReceiver.java +17 -0
  93. package/android/src/main/java/cn/jpush/android/service/TagAliasReceiver.java +11 -0
  94. package/android/src/main/java/cn/jpush/android/service/WakedResultReceiver.java +24 -0
  95. package/android/src/main/java/cn/jpush/android/ui/FullScreenView.java +11 -0
  96. package/android/src/main/java/cn/jpush/android/ui/PopWinActivity.java +16 -0
  97. package/android/src/main/java/cn/jpush/android/ui/PushActivity.java +27 -0
  98. package/android/src/main/java/cn/jpush/android/ui/RoundedImageView.java +63 -0
  99. package/android/src/main/java/cn/jpush/android/ui/ShadowViewCard.java +56 -0
  100. package/android/src/main/java/cn/jpush/android/ups/ICallbackResult.java +5 -0
  101. package/android/src/main/java/cn/jpush/android/ups/JPushUPSManager.java +37 -0
  102. package/android/src/main/java/cn/jpush/android/ups/TokenResult.java +47 -0
  103. package/android/src/main/java/cn/jpush/android/ups/UPSRegisterCallBack.java +4 -0
  104. package/android/src/main/java/cn/jpush/android/ups/UPSTurnCallBack.java +4 -0
  105. package/android/src/main/java/cn/jpush/android/ups/UPSUnRegisterCallBack.java +4 -0
  106. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpush.kt +455 -0
  107. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushPackage.kt +50 -0
  108. package/android/src/main/res/values/jpush_string.xml +7 -0
  109. package/android/src/main/res/values/jpush_style.xml +22 -0
  110. package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java +1016 -0
  111. package/ios/ReactNativeJpush.swift +522 -0
  112. package/ios/bridge/RCTJCoreModule.h +5 -0
  113. package/ios/bridge/RCTJCoreModule.m +31 -0
  114. package/ios/bridge/RCTJPushEventQueue.h +14 -0
  115. package/ios/bridge/RCTJPushEventQueue.m +23 -0
  116. package/ios/bridge/RCTJPushModule.h +37 -0
  117. package/ios/bridge/RCTJPushModule.m +1165 -0
  118. package/ios/include/JCOREService.h +3 -0
  119. package/ios/include/JGCOREAPI.h +14 -0
  120. package/ios/include/JGInforCollectionAuth.h +17 -0
  121. package/ios/include/JGInforCollectionAuthItems.h +2 -0
  122. package/ios/include/JGPushNotificationTrigger.h +2 -0
  123. package/ios/include/JPUSHService.h +359 -0
  124. package/ios/jcore/JCOREConfiguration.h +141 -0
  125. package/ios/jcore/JCOREConfiguration.m +703 -0
  126. package/ios/jcore/JCORECrypto.h +73 -0
  127. package/ios/jcore/JCORECrypto.m +649 -0
  128. package/ios/jcore/JCORESisResolver.h +56 -0
  129. package/ios/jcore/JCORESisResolver.m +525 -0
  130. package/ios/jcore/JCORETcpSession.h +42 -0
  131. package/ios/jcore/JCORETcpSession.m +956 -0
  132. package/ios/jcore/JCOREWireProtocol.h +274 -0
  133. package/ios/jcore/JCOREWireProtocol.m +1453 -0
  134. package/ios/jpush/JPUSHModels.m +214 -0
  135. package/ios/jpush/JPUSHNotificationManager.h +34 -0
  136. package/ios/jpush/JPUSHNotificationManager.m +360 -0
  137. package/ios/jpush/JPUSHService.m +794 -0
  138. package/ios/tests/ReactNativeJpushTests.m +148 -0
  139. package/lib/module/ReactNativeJpush.nitro.js +4 -0
  140. package/lib/module/ReactNativeJpush.nitro.js.map +1 -0
  141. package/lib/module/index.js +801 -0
  142. package/lib/module/index.js.map +1 -0
  143. package/lib/module/package.json +1 -0
  144. package/lib/typescript/package.json +1 -0
  145. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts +60 -0
  146. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts.map +1 -0
  147. package/lib/typescript/src/index.d.ts +235 -0
  148. package/lib/typescript/src/index.d.ts.map +1 -0
  149. package/nitro.json +23 -0
  150. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  151. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string.hpp +76 -0
  152. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.cpp +310 -0
  153. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.hpp +115 -0
  154. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_bool.kt +78 -0
  155. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_std__string_std__string.kt +78 -0
  156. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/HybridReactNativeJpushSpec.kt +274 -0
  157. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/reactnativejpushOnLoad.kt +35 -0
  158. package/nitrogen/generated/android/reactnativejpush+autolinking.cmake +81 -0
  159. package/nitrogen/generated/android/reactnativejpush+autolinking.gradle +27 -0
  160. package/nitrogen/generated/android/reactnativejpushOnLoad.cpp +58 -0
  161. package/nitrogen/generated/android/reactnativejpushOnLoad.hpp +34 -0
  162. package/nitrogen/generated/ios/ReactNativeJpush+autolinking.rb +62 -0
  163. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.cpp +49 -0
  164. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.hpp +147 -0
  165. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Umbrella.hpp +46 -0
  166. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.mm +33 -0
  167. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.swift +26 -0
  168. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.cpp +11 -0
  169. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.hpp +402 -0
  170. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  171. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string.swift +46 -0
  172. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec.swift +107 -0
  173. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec_cxx.swift +723 -0
  174. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.cpp +73 -0
  175. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.hpp +116 -0
  176. package/package.json +174 -4
  177. package/src/ReactNativeJpush.nitro.ts +93 -0
  178. package/src/index.tsx +1090 -0
@@ -0,0 +1,1395 @@
1
+ package cn.jiguang.core;
2
+
3
+ import android.content.Context;
4
+ import android.os.Bundle;
5
+ import cn.jiguang.plugins.push.common.JLogger;
6
+ import cn.jpush.android.api.JPushInterface;
7
+ import cn.jpush.android.local.ActionHelper;
8
+ import java.io.ByteArrayOutputStream;
9
+ import java.io.InputStream;
10
+ import java.io.OutputStream;
11
+ import java.net.DatagramPacket;
12
+ import java.net.DatagramSocket;
13
+ import java.net.HttpURLConnection;
14
+ import java.net.InetAddress;
15
+ import java.net.InetSocketAddress;
16
+ import java.net.Socket;
17
+ import java.net.URL;
18
+ import java.nio.charset.StandardCharsets;
19
+ import java.util.ArrayList;
20
+ import java.util.Collections;
21
+ import java.util.LinkedHashSet;
22
+ import java.util.List;
23
+ import java.util.Queue;
24
+ import java.util.Set;
25
+ import java.util.concurrent.ConcurrentHashMap;
26
+ import java.util.concurrent.ConcurrentLinkedQueue;
27
+ import java.util.concurrent.CopyOnWriteArrayList;
28
+ import java.util.concurrent.CountDownLatch;
29
+ import java.util.concurrent.ExecutorService;
30
+ import java.util.concurrent.Executors;
31
+ import java.util.concurrent.ScheduledExecutorService;
32
+ import java.util.concurrent.ScheduledFuture;
33
+ import java.util.concurrent.ThreadFactory;
34
+ import java.util.concurrent.TimeUnit;
35
+ import javax.net.ssl.HttpsURLConnection;
36
+ import javax.net.ssl.SSLSession;
37
+ import javax.net.ssl.SSLSocket;
38
+ import javax.net.ssl.SSLSocketFactory;
39
+ import org.json.JSONArray;
40
+ import org.json.JSONObject;
41
+
42
+ public final class JCoreTcpEngine {
43
+ public interface RequestCallback {
44
+ void onResult(JCoreWireCodec.DecodedFrame responseFrame, int statusCode);
45
+ }
46
+
47
+ private static final class QueuedRequest {
48
+ final int command;
49
+ final int version;
50
+ final long rid;
51
+ final byte[] body;
52
+ final long timeoutMs;
53
+ final int maxRetries;
54
+ final RequestCallback callback;
55
+
56
+ QueuedRequest(
57
+ int command,
58
+ int version,
59
+ long rid,
60
+ byte[] body,
61
+ long timeoutMs,
62
+ int maxRetries,
63
+ RequestCallback callback
64
+ ) {
65
+ this.command = command;
66
+ this.version = version;
67
+ this.rid = rid;
68
+ this.body = body != null ? body : new byte[0];
69
+ this.timeoutMs = timeoutMs > 0L ? timeoutMs : 5000L;
70
+ this.maxRetries = Math.max(0, maxRetries);
71
+ this.callback = callback;
72
+ }
73
+ }
74
+
75
+ private static final class ResponseSlot {
76
+ final CountDownLatch latch = new CountDownLatch(1);
77
+ volatile JCoreWireCodec.DecodedFrame response;
78
+
79
+ void complete(JCoreWireCodec.DecodedFrame frame) {
80
+ this.response = frame;
81
+ this.latch.countDown();
82
+ }
83
+ }
84
+
85
+ private static final JCoreTcpEngine INSTANCE = new JCoreTcpEngine();
86
+ private static final String DEFAULT_HOST = "im64.jpush.cn";
87
+ private static final int DEFAULT_PORT = 7000;
88
+ private static final String SIS_URL = "https://tsis.jpush.cn/v3/srv";
89
+ private static final String DEFAULT_REPORT_URL = "https://stats.jpush.cn/v3/report";
90
+ private static final String SIS_RSA_PUBLIC_KEY =
91
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCi0v4VEUhOdfIEfFCrPC72pcDsQF/"
92
+ + "luTmr4q34NY0EZYGKzfQuTrUAm916P52HCgF+342gjZ/Nvijts5543qYNyoLvgtu4NRcHJmuGI/"
93
+ + "w7qifhfsivYeoEj9wYphXOyB9HUjzwn1BtAih+1RyUrcErCi249yabUOIKQygPZ9OXXQIDAQAB";
94
+ private static final String SIS_AES_IV = "0102030405060708";
95
+
96
+ private final ExecutorService ioExecutor = Executors.newCachedThreadPool(new ThreadFactory() {
97
+ private int count = 0;
98
+
99
+ @Override
100
+ public synchronized Thread newThread(Runnable r) {
101
+ Thread t = new Thread(r, "jcore-clean-net-" + (++count));
102
+ t.setDaemon(true);
103
+ return t;
104
+ }
105
+ });
106
+
107
+ private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(
108
+ new ThreadFactory() {
109
+ @Override
110
+ public Thread newThread(Runnable r) {
111
+ Thread t = new Thread(r, "jcore-clean-heartbeat");
112
+ t.setDaemon(true);
113
+ return t;
114
+ }
115
+ }
116
+ );
117
+
118
+ private final ConcurrentHashMap<Long, ResponseSlot> pendingRequests = new ConcurrentHashMap<Long, ResponseSlot>();
119
+ private final Queue<QueuedRequest> preLoginQueue = new ConcurrentLinkedQueue<QueuedRequest>();
120
+ private final List<String> candidateEndpoints = new CopyOnWriteArrayList<String>();
121
+ private final List<JCoreWireCodec.CtrlCommand> receivedCtrlCommands =
122
+ new CopyOnWriteArrayList<JCoreWireCodec.CtrlCommand>();
123
+ private final Object socketLock = new Object();
124
+
125
+ private volatile boolean started = false;
126
+ private volatile boolean loggedIn = false;
127
+ private volatile boolean autoReconnectEnabled = true;
128
+ private volatile long reconnectBaseDelayMs = 3000L;
129
+ private volatile long heartbeatIntervalMs = 290000L;
130
+ private volatile ScheduledFuture<?> activeHeartbeatFuture;
131
+ private volatile int reconnectAttempt = 0;
132
+ private volatile long connectionGeneration = 0L;
133
+ private volatile int sessionId = 1;
134
+ private volatile String customHost = "";
135
+ private volatile int customPort = 0;
136
+ private volatile String customReportUrl = "";
137
+ private volatile String customSisUrl = "";
138
+ private volatile String defaultSisUdpHost = "s.jpush.cn";
139
+ private volatile int defaultSisUdpPort = 19000;
140
+ private volatile long defaultRequestTimeoutMs = 5000L;
141
+ private volatile boolean requireServerAckForOperations = false;
142
+ private volatile boolean testConnectionEnabled = false;
143
+ private volatile Socket activeSocket;
144
+ private volatile OutputStream activeOutput;
145
+ private volatile CountDownLatch handshakeLatch = new CountDownLatch(1);
146
+
147
+ private JCoreTcpEngine() {
148
+ }
149
+
150
+ public static JCoreTcpEngine getInstance() {
151
+ return INSTANCE;
152
+ }
153
+
154
+ public void setTestConnectionEnabled(boolean enabled) {
155
+ this.testConnectionEnabled = enabled;
156
+ }
157
+
158
+ public void setAutoReconnectEnabled(boolean enabled) {
159
+ this.autoReconnectEnabled = enabled;
160
+ }
161
+
162
+ public boolean isAutoReconnectEnabled() {
163
+ return autoReconnectEnabled;
164
+ }
165
+
166
+ public void setReconnectBaseDelayMs(long baseDelayMs) {
167
+ if (baseDelayMs > 0L) {
168
+ this.reconnectBaseDelayMs = baseDelayMs;
169
+ }
170
+ }
171
+
172
+ public long getReconnectBaseDelayMs() {
173
+ return reconnectBaseDelayMs;
174
+ }
175
+
176
+ public void setHeartbeatIntervalMs(long intervalMs) {
177
+ if (intervalMs > 0L) {
178
+ this.heartbeatIntervalMs = intervalMs;
179
+ }
180
+ }
181
+
182
+ public long getHeartbeatIntervalMs() {
183
+ return heartbeatIntervalMs;
184
+ }
185
+
186
+ private void cancelHeartbeatTimer() {
187
+ ScheduledFuture<?> future = this.activeHeartbeatFuture;
188
+ this.activeHeartbeatFuture = null;
189
+ if (future != null) {
190
+ future.cancel(false);
191
+ }
192
+ }
193
+
194
+ private void startHeartbeatTimer(final Context context, final Socket targetSocket) {
195
+ cancelHeartbeatTimer();
196
+ long interval = this.heartbeatIntervalMs > 0L ? this.heartbeatIntervalMs : 290000L;
197
+ this.activeHeartbeatFuture = heartbeatExecutor.scheduleWithFixedDelay(
198
+ new Runnable() {
199
+ @Override
200
+ public void run() {
201
+ if (!loggedIn || activeSocket != targetSocket || targetSocket == null || targetSocket.isClosed()) {
202
+ cancelHeartbeatTimer();
203
+ return;
204
+ }
205
+ try {
206
+ long currentUid = JCoreStorage.getUid(context);
207
+ byte[] hbPayload = JCoreWireCodec.buildHeartbeatPayload(JCoreStorage.isPushStopped(context));
208
+ sendFrameRaw(
209
+ JCoreWireCodec.CMD_HEARTBEAT,
210
+ JCoreWireCodec.VER_HEARTBEAT,
211
+ JCoreStorage.nextRid(),
212
+ sessionId,
213
+ currentUid,
214
+ hbPayload
215
+ );
216
+ } catch (Throwable ignored) {
217
+ }
218
+ }
219
+ },
220
+ interval,
221
+ interval,
222
+ TimeUnit.MILLISECONDS
223
+ );
224
+ }
225
+
226
+ public int getReconnectAttempt() {
227
+ return reconnectAttempt;
228
+ }
229
+
230
+ public static long computeReconnectDelayMs(int attempt) {
231
+ return computeReconnectDelayMs(attempt, 3000L);
232
+ }
233
+
234
+ public static long computeReconnectDelayMs(int attempt, long baseDelayMs) {
235
+ int cappedShift = Math.max(0, Math.min(attempt, 6));
236
+ long effectiveBase = baseDelayMs > 0L ? baseDelayMs : 3000L;
237
+ long delay = effectiveBase * (1L << cappedShift);
238
+ return Math.min(delay, 120000L);
239
+ }
240
+
241
+ public void setDefaultRequestTimeoutMs(long timeoutMs) {
242
+ if (timeoutMs > 0L) {
243
+ this.defaultRequestTimeoutMs = timeoutMs;
244
+ }
245
+ }
246
+
247
+ public long getDefaultRequestTimeoutMs() {
248
+ return defaultRequestTimeoutMs;
249
+ }
250
+
251
+ public void setRequireServerAckForOperations(boolean require) {
252
+ this.requireServerAckForOperations = require;
253
+ }
254
+
255
+ public boolean isRequireServerAckForOperations() {
256
+ return requireServerAckForOperations;
257
+ }
258
+
259
+ public void setCustomReportUrl(String url) {
260
+ this.customReportUrl = url != null ? url.trim() : "";
261
+ }
262
+
263
+ public String getCustomReportUrl() {
264
+ return customReportUrl;
265
+ }
266
+
267
+ public void setCustomSisUrl(String url) {
268
+ this.customSisUrl = url != null ? url.trim() : "";
269
+ }
270
+
271
+ public String getCustomSisUrl() {
272
+ return customSisUrl;
273
+ }
274
+
275
+ public void setDefaultSisUdpEndpoint(String host, int port) {
276
+ this.defaultSisUdpHost = (host != null && !host.trim().isEmpty()) ? host.trim() : "s.jpush.cn";
277
+ this.defaultSisUdpPort = port > 0 ? port : 19000;
278
+ }
279
+
280
+ public List<JCoreWireCodec.CtrlCommand> getReceivedCtrlCommands() {
281
+ return new ArrayList<JCoreWireCodec.CtrlCommand>(receivedCtrlCommands);
282
+ }
283
+
284
+ public void clearReceivedCtrlCommands() {
285
+ receivedCtrlCommands.clear();
286
+ }
287
+
288
+ public synchronized void setCandidateEndpoints(List<String> ipPorts) {
289
+ this.candidateEndpoints.clear();
290
+ if (ipPorts != null) {
291
+ for (String item : ipPorts) {
292
+ if (item != null && !item.trim().isEmpty()) {
293
+ this.candidateEndpoints.add(item.trim());
294
+ }
295
+ }
296
+ }
297
+ if (!this.candidateEndpoints.isEmpty()) {
298
+ this.testConnectionEnabled = true;
299
+ String first = this.candidateEndpoints.get(0);
300
+ String[] hp = splitHostPort(first);
301
+ if (hp != null) {
302
+ this.customHost = hp[0];
303
+ this.customPort = Integer.parseInt(hp[1]);
304
+ }
305
+ }
306
+ closeSocket();
307
+ this.started = false;
308
+ this.loggedIn = false;
309
+ this.handshakeLatch = new CountDownLatch(1);
310
+ }
311
+
312
+ public synchronized void setCustomEndpoint(String host, int port) {
313
+ this.customHost = host != null ? host.trim() : "";
314
+ this.customPort = port;
315
+ this.candidateEndpoints.clear();
316
+ if (!this.customHost.isEmpty() && port > 0) {
317
+ this.testConnectionEnabled = true;
318
+ this.candidateEndpoints.add(this.customHost + ":" + port);
319
+ }
320
+ closeSocket();
321
+ this.started = false;
322
+ this.loggedIn = false;
323
+ this.handshakeLatch = new CountDownLatch(1);
324
+ }
325
+
326
+ public String getCustomHost() {
327
+ return customHost;
328
+ }
329
+
330
+ public int getCustomPort() {
331
+ return customPort;
332
+ }
333
+
334
+ public int getSessionId() {
335
+ return sessionId;
336
+ }
337
+
338
+ public boolean isLoggedIn() {
339
+ return loggedIn;
340
+ }
341
+
342
+ public int getPreLoginQueueSize() {
343
+ return preLoginQueue.size();
344
+ }
345
+
346
+ public boolean awaitHandshake(long timeoutMs) {
347
+ try {
348
+ return handshakeLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
349
+ } catch (InterruptedException e) {
350
+ Thread.currentThread().interrupt();
351
+ return false;
352
+ }
353
+ }
354
+
355
+ public synchronized void closeSocket() {
356
+ this.connectionGeneration++;
357
+ cancelHeartbeatTimer();
358
+ synchronized (socketLock) {
359
+ if (activeSocket != null) {
360
+ try {
361
+ activeSocket.close();
362
+ } catch (Throwable ignored) {
363
+ }
364
+ activeSocket = null;
365
+ activeOutput = null;
366
+ }
367
+ }
368
+ for (ResponseSlot slot : pendingRequests.values()) {
369
+ slot.complete(null);
370
+ }
371
+ pendingRequests.clear();
372
+ loggedIn = false;
373
+ }
374
+
375
+ public synchronized void stop(Context context) {
376
+ closeSocket();
377
+ this.started = false;
378
+ JCoreStorage.setConnected(context, false);
379
+ }
380
+
381
+ public void start(final Context context) {
382
+ JCoreStorage.setAppContext(context);
383
+ final long startGen;
384
+ final CountDownLatch latchForStart;
385
+ synchronized (this) {
386
+ if (started) {
387
+ return;
388
+ }
389
+ started = true;
390
+ handshakeLatch = new CountDownLatch(1);
391
+ startGen = this.connectionGeneration;
392
+ latchForStart = this.handshakeLatch;
393
+ }
394
+ if (JCoreStorage.isPushStopped(JCoreStorage.getAppContext(context))) {
395
+ latchForStart.countDown();
396
+ return;
397
+ }
398
+ ioExecutor.execute(new Runnable() {
399
+ @Override
400
+ public void run() {
401
+ if (!started || startGen != connectionGeneration) {
402
+ latchForStart.countDown();
403
+ return;
404
+ }
405
+ Context app = JCoreStorage.getAppContext(context);
406
+ if (JCoreStorage.isPushStopped(app)) {
407
+ latchForStart.countDown();
408
+ return;
409
+ }
410
+ connectAndHandshake(app);
411
+ }
412
+ });
413
+ }
414
+
415
+ public void triggerReconnectNow(final Context context) {
416
+ final long reconnectGen;
417
+ final CountDownLatch latchForReconnect;
418
+ synchronized (this) {
419
+ closeSocket();
420
+ this.started = true;
421
+ this.handshakeLatch = new CountDownLatch(1);
422
+ reconnectGen = this.connectionGeneration;
423
+ latchForReconnect = this.handshakeLatch;
424
+ }
425
+ ioExecutor.execute(new Runnable() {
426
+ @Override
427
+ public void run() {
428
+ if (!started || reconnectGen != connectionGeneration) {
429
+ latchForReconnect.countDown();
430
+ return;
431
+ }
432
+ connectAndHandshake(JCoreStorage.getAppContext(context));
433
+ }
434
+ });
435
+ }
436
+
437
+ public void onNetworkConnected(Context context) {
438
+ this.reconnectAttempt = 0;
439
+ if (!this.loggedIn) {
440
+ triggerReconnectNow(context);
441
+ }
442
+ }
443
+
444
+ public void executeAsync(Runnable task) {
445
+ if (task != null) {
446
+ ioExecutor.execute(task);
447
+ }
448
+ }
449
+
450
+ private boolean isPlainSocketEndpoint(String host, int port) {
451
+ if (testConnectionEnabled) {
452
+ return true;
453
+ }
454
+ if (port != 443) {
455
+ // Jiguang TCP long-connection ports (7000-7009 / 8000-8009) speak 24B/20B AES/SM4 frames directly over TCP.
456
+ return true;
457
+ }
458
+ if (host == null) {
459
+ return false;
460
+ }
461
+ return "127.0.0.1".equals(host) || "localhost".equalsIgnoreCase(host) || "::1".equals(host);
462
+ }
463
+
464
+ private static String[] splitHostPort(String ipPort) {
465
+ if (ipPort == null) {
466
+ return null;
467
+ }
468
+ String trimmed = ipPort.trim();
469
+ int colon = trimmed.lastIndexOf(':');
470
+ if (colon <= 0 || colon + 1 >= trimmed.length()) {
471
+ return null;
472
+ }
473
+ String host = trimmed.substring(0, colon).trim();
474
+ String portStr = trimmed.substring(colon + 1).trim();
475
+ try {
476
+ int port = Integer.parseInt(portStr);
477
+ if (port > 0 && port <= 65535) {
478
+ return new String[] { host, String.valueOf(port) };
479
+ }
480
+ } catch (NumberFormatException ignored) {
481
+ }
482
+ return null;
483
+ }
484
+
485
+ private List<String> buildOrderedCandidateList(Context context) {
486
+ Set<String> ordered = new LinkedHashSet<String>();
487
+ for (String ep : candidateEndpoints) {
488
+ if (ep != null && !ep.isEmpty()) {
489
+ ordered.add(ep);
490
+ }
491
+ }
492
+ if (customHost != null && !customHost.isEmpty() && customPort > 0) {
493
+ ordered.add(customHost + ":" + customPort);
494
+ }
495
+ String lastGood = JCoreStorage.getLastGoodIp(context);
496
+ if (lastGood != null && !lastGood.isEmpty() && ordered.isEmpty()) {
497
+ ordered.add(lastGood);
498
+ }
499
+ for (String s : JCoreStorage.getSisIps(context)) {
500
+ ordered.add(s);
501
+ }
502
+ for (String s : JCoreStorage.getSisSslIps(context)) {
503
+ ordered.add(s);
504
+ }
505
+ List<String> failedIps = JCoreStorage.getFailedIps(context);
506
+ if (failedIps == null || failedIps.isEmpty()) {
507
+ return new ArrayList<String>(ordered);
508
+ }
509
+ List<String> healthy = new ArrayList<String>();
510
+ List<String> failed = new ArrayList<String>();
511
+ for (String ep : ordered) {
512
+ if (failedIps.contains(ep)) {
513
+ failed.add(ep);
514
+ } else {
515
+ healthy.add(ep);
516
+ }
517
+ }
518
+ healthy.addAll(failed);
519
+ return healthy;
520
+ }
521
+
522
+ private boolean hasHealthyCandidate(Context context, List<String> candidates) {
523
+ if (candidates == null || candidates.isEmpty()) {
524
+ return false;
525
+ }
526
+ List<String> failedIps = JCoreStorage.getFailedIps(context);
527
+ if (failedIps == null || failedIps.isEmpty()) {
528
+ return true;
529
+ }
530
+ for (String ep : candidates) {
531
+ if (ep != null && !ep.isEmpty() && !failedIps.contains(ep)) {
532
+ return true;
533
+ }
534
+ }
535
+ return false;
536
+ }
537
+
538
+ public void connectAndHandshake(final Context context) {
539
+ try {
540
+ List<String> candidates = buildOrderedCandidateList(context);
541
+ if (customPort <= 0 && candidateEndpoints.isEmpty()) {
542
+ if (!hasHealthyCandidate(context, candidates)) {
543
+ String[] sisResolved = resolveSisEndpoint(context);
544
+ candidates = buildOrderedCandidateList(context);
545
+ if (sisResolved != null && sisResolved.length == 2) {
546
+ String resolvedEp = (sisResolved[0] + ":" + sisResolved[1]).trim();
547
+ if (!resolvedEp.isEmpty() && splitHostPort(resolvedEp) != null && !candidates.contains(resolvedEp)) {
548
+ candidates.add(0, resolvedEp);
549
+ }
550
+ }
551
+ }
552
+ if (!hasHealthyCandidate(context, candidates)) {
553
+ String defaultEndpoint = DEFAULT_HOST + ":" + DEFAULT_PORT;
554
+ if (!candidates.contains(defaultEndpoint)) {
555
+ candidates.add(defaultEndpoint);
556
+ }
557
+ }
558
+ }
559
+ if (candidates.isEmpty()) {
560
+ JCoreStorage.setConnected(context, false);
561
+ return;
562
+ }
563
+
564
+ boolean connectedSuccess = false;
565
+ for (String endpoint : candidates) {
566
+ String[] hp = splitHostPort(endpoint);
567
+ if (hp == null) {
568
+ continue;
569
+ }
570
+ String host = hp[0];
571
+ int port = Integer.parseInt(hp[1]);
572
+ if (tryConnectAndHandshakeEndpoint(context, host, port)) {
573
+ JCoreStorage.setLastGoodIp(context, host + ":" + port);
574
+ connectedSuccess = true;
575
+ break;
576
+ } else {
577
+ JCoreStorage.recordFailedIp(context, host + ":" + port);
578
+ }
579
+ }
580
+
581
+ if (!connectedSuccess) {
582
+ JCoreStorage.setConnected(context, false);
583
+ if (this.started && this.autoReconnectEnabled && !JCoreStorage.isPushStopped(context)) {
584
+ final long delayMs = computeReconnectDelayMs(reconnectAttempt++, reconnectBaseDelayMs);
585
+ final long scheduledGen = this.connectionGeneration;
586
+ heartbeatExecutor.schedule(new Runnable() {
587
+ @Override
588
+ public void run() {
589
+ if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
590
+ return;
591
+ }
592
+ ioExecutor.execute(new Runnable() {
593
+ @Override
594
+ public void run() {
595
+ if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
596
+ return;
597
+ }
598
+ connectAndHandshake(context);
599
+ }
600
+ });
601
+ }
602
+ }, delayMs, TimeUnit.MILLISECONDS);
603
+ } else {
604
+ this.reconnectAttempt++;
605
+ }
606
+ }
607
+ } finally {
608
+ handshakeLatch.countDown();
609
+ }
610
+ }
611
+
612
+ private boolean tryConnectAndHandshakeEndpoint(final Context context, String host, int port) {
613
+ Socket socket = null;
614
+ try {
615
+ if (isPlainSocketEndpoint(host, port)) {
616
+ socket = new Socket();
617
+ socket.connect(new InetSocketAddress(host, port), 3000);
618
+ socket.setSoTimeout(10000);
619
+ } else {
620
+ socket = createVerifiedTlsSocket(host, port);
621
+ }
622
+
623
+ final Socket connectedSocket = socket;
624
+ final InputStream in = connectedSocket.getInputStream();
625
+ final OutputStream out = connectedSocket.getOutputStream();
626
+ synchronized (socketLock) {
627
+ this.activeSocket = connectedSocket;
628
+ this.activeOutput = out;
629
+ }
630
+
631
+ Thread readerThread = new Thread(new Runnable() {
632
+ @Override
633
+ public void run() {
634
+ readLoop(context, connectedSocket, in);
635
+ }
636
+ }, "jcore-tcp-reader");
637
+ readerThread.setDaemon(true);
638
+ readerThread.start();
639
+
640
+ String appKey = JCoreStorage.getAppKey(context);
641
+ if (appKey.isEmpty()) {
642
+ appKey = "onekey_default_appkey";
643
+ }
644
+ String channel = JCoreStorage.getChannel(context);
645
+ String pkgName = "so.onekey.app.wallet";
646
+ if (context != null) {
647
+ try {
648
+ String p = context.getPackageName();
649
+ if (p != null && !p.isEmpty()) {
650
+ pkgName = p;
651
+ }
652
+ } catch (Throwable ignored) {
653
+ }
654
+ }
655
+ String deviceId = JCoreStorage.getDeviceId(context);
656
+ String countryCode = JCoreStorage.getCountryCode(context);
657
+
658
+ // Registered Device Fast-Login check: skip Register (cmd=0) if already server-registered
659
+ boolean fastLogin = JCoreStorage.isServerRegistered(context)
660
+ && JCoreStorage.getUid(context) > 0L
661
+ && !JCoreStorage.getPassword(context).isEmpty()
662
+ && !JCoreStorage.getRegistrationId(context).isEmpty();
663
+
664
+ if (!fastLogin) {
665
+ if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId)) {
666
+ closeSocket();
667
+ return false;
668
+ }
669
+ }
670
+
671
+ long uid = JCoreStorage.getUid(context);
672
+ String password = JCoreStorage.getPassword(context);
673
+ byte[] loginBody = JCoreWireCodec.buildLoginPayload(password, appKey, deviceId, countryCode);
674
+ JCoreWireCodec.DecodedFrame loginFrame = sendRequestOnSocket(
675
+ JCoreWireCodec.CMD_LOGIN,
676
+ JCoreWireCodec.VER_LOGIN,
677
+ 0,
678
+ uid,
679
+ loginBody,
680
+ defaultRequestTimeoutMs
681
+ );
682
+ if (loginFrame == null) {
683
+ closeSocket();
684
+ return false;
685
+ }
686
+ JCoreWireCodec.LoginResponse loginResp = JCoreWireCodec.parseLoginResponse(loginFrame);
687
+ if (loginResp == null) {
688
+ closeSocket();
689
+ return false;
690
+ }
691
+
692
+ // Handle Login statusCode == 108 (clear registration and immediately re-register + login)
693
+ if (loginResp.statusCode == 108) {
694
+ JCoreStorage.clearRegistration(context);
695
+ if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId)) {
696
+ closeSocket();
697
+ return false;
698
+ }
699
+ uid = JCoreStorage.getUid(context);
700
+ password = JCoreStorage.getPassword(context);
701
+ loginBody = JCoreWireCodec.buildLoginPayload(password, appKey, deviceId, countryCode);
702
+ loginFrame = sendRequestOnSocket(
703
+ JCoreWireCodec.CMD_LOGIN,
704
+ JCoreWireCodec.VER_LOGIN,
705
+ 0,
706
+ uid,
707
+ loginBody,
708
+ defaultRequestTimeoutMs
709
+ );
710
+ if (loginFrame == null) {
711
+ closeSocket();
712
+ return false;
713
+ }
714
+ loginResp = JCoreWireCodec.parseLoginResponse(loginFrame);
715
+ } else if (loginResp.statusCode == 1012) {
716
+ JCoreStorage.resetSisCache(context);
717
+ closeSocket();
718
+ return false;
719
+ }
720
+
721
+ if (loginResp == null || loginResp.statusCode != 0) {
722
+ closeSocket();
723
+ return false;
724
+ }
725
+
726
+ if (loginResp.sessionId != 0) {
727
+ this.sessionId = loginResp.sessionId;
728
+ } else if (loginFrame.sessionId != 0) {
729
+ this.sessionId = loginFrame.sessionId;
730
+ }
731
+
732
+ this.loggedIn = true;
733
+ this.reconnectAttempt = 0;
734
+ int idleReadTimeoutMs = (int) Math.min(
735
+ (long) Integer.MAX_VALUE,
736
+ Math.max(320000L, this.heartbeatIntervalMs + 30000L)
737
+ );
738
+ try {
739
+ if (!connectedSocket.isClosed()) {
740
+ connectedSocket.setSoTimeout(idleReadTimeoutMs);
741
+ }
742
+ } catch (Throwable ignored) {
743
+ }
744
+ JLogger.i(
745
+ "TCP login success uid="
746
+ + uid
747
+ + " sid="
748
+ + this.sessionId
749
+ + " registrationId="
750
+ + JCoreStorage.getRegistrationId(context)
751
+ );
752
+ JCoreStorage.setConnected(context, true);
753
+ ActionHelper.getInstance().dispatchConnectedEvent(context, true);
754
+
755
+ byte[] hbBody = JCoreWireCodec.buildHeartbeatPayload(JCoreStorage.isPushStopped(context));
756
+ sendRequestOnSocket(
757
+ JCoreWireCodec.CMD_HEARTBEAT,
758
+ JCoreWireCodec.VER_HEARTBEAT,
759
+ this.sessionId,
760
+ uid,
761
+ hbBody,
762
+ defaultRequestTimeoutMs
763
+ );
764
+
765
+ startHeartbeatTimer(context, connectedSocket);
766
+ flushPreLoginQueue(context);
767
+ return true;
768
+ } catch (Throwable ignored) {
769
+ closeSocket();
770
+ return false;
771
+ }
772
+ }
773
+
774
+ private boolean performRegisterHandshake(
775
+ Context context,
776
+ String appKey,
777
+ String channel,
778
+ String pkgName,
779
+ String deviceId
780
+ ) {
781
+ int registerSeed = JCoreCrypto.nextRegisterSeed();
782
+ JCoreWireCodec.setLastRegisterSeed(registerSeed);
783
+ byte[] regBody = JCoreWireCodec.buildRegisterPayload(appKey, channel, pkgName, deviceId);
784
+ JCoreWireCodec.DecodedFrame regFrame = sendRequestOnSocket(
785
+ JCoreWireCodec.CMD_REGISTER,
786
+ JCoreWireCodec.VER_REGISTER,
787
+ registerSeed,
788
+ 0L,
789
+ regBody,
790
+ defaultRequestTimeoutMs
791
+ );
792
+ if (regFrame == null) {
793
+ return false;
794
+ }
795
+ JCoreWireCodec.RegisterResponse regResp = JCoreWireCodec.parseRegisterResponse(regFrame);
796
+ if (regResp == null || regResp.statusCode != 0 || regResp.uid <= 0L) {
797
+ return false;
798
+ }
799
+ JCoreStorage.updateRegistration(
800
+ context,
801
+ regResp.uid,
802
+ regResp.password,
803
+ regResp.registrationId,
804
+ regResp.deviceId
805
+ );
806
+ JCoreStorage.setServerRegistered(context, true);
807
+ JLogger.i("TCP register success uid=" + regResp.uid + " registrationId=" + regResp.registrationId);
808
+ ActionHelper.getInstance().dispatchRegisterEvent(context, regResp.registrationId);
809
+ return true;
810
+ }
811
+
812
+ private void flushPreLoginQueue(final Context context) {
813
+ QueuedRequest item;
814
+ while ((item = preLoginQueue.poll()) != null) {
815
+ final QueuedRequest req = item;
816
+ ioExecutor.execute(new Runnable() {
817
+ @Override
818
+ public void run() {
819
+ executeQueuedRequestWithRetry(context, req);
820
+ }
821
+ });
822
+ }
823
+ }
824
+
825
+ public void enqueueOrSendRequest(
826
+ final Context context,
827
+ int command,
828
+ int version,
829
+ long rid,
830
+ byte[] body,
831
+ long timeoutMs,
832
+ int maxRetries,
833
+ RequestCallback callback
834
+ ) {
835
+ final long effectiveRid = rid > 0L ? rid : JCoreStorage.nextRid();
836
+ final QueuedRequest req = new QueuedRequest(
837
+ command,
838
+ version,
839
+ effectiveRid,
840
+ body,
841
+ timeoutMs > 0L ? timeoutMs : defaultRequestTimeoutMs,
842
+ maxRetries,
843
+ callback
844
+ );
845
+ if (!this.loggedIn || this.activeOutput == null) {
846
+ preLoginQueue.offer(req);
847
+ return;
848
+ }
849
+ ioExecutor.execute(new Runnable() {
850
+ @Override
851
+ public void run() {
852
+ executeQueuedRequestWithRetry(context, req);
853
+ }
854
+ });
855
+ }
856
+
857
+ private void executeQueuedRequestWithRetry(Context context, QueuedRequest req) {
858
+ long uid = JCoreStorage.getUid(context);
859
+ int attempts = 0;
860
+ while (attempts <= req.maxRetries) {
861
+ attempts++;
862
+ long ridToUse = (attempts == 1) ? req.rid : JCoreStorage.nextRid();
863
+ JCoreWireCodec.DecodedFrame resp = sendRequestWithRidOnSocket(
864
+ req.command,
865
+ req.version,
866
+ ridToUse,
867
+ this.sessionId,
868
+ uid,
869
+ req.body,
870
+ req.timeoutMs
871
+ );
872
+ if (resp != null) {
873
+ int code = resp.statusCode;
874
+ if ((req.command == JCoreWireCodec.CMD_TAG_OP
875
+ || req.command == JCoreWireCodec.CMD_ALIAS_OP
876
+ || req.command == JCoreWireCodec.CMD_PROPERTY_OP)
877
+ && resp.body != null && resp.body.length > 0) {
878
+ String json = JCoreWireCodec.parseJsonCommandPayload(resp.body);
879
+ code = JCoreWireCodec.extractJsonInt(json, "code", code);
880
+ }
881
+ if (req.callback != null) {
882
+ req.callback.onResult(resp, code);
883
+ }
884
+ return;
885
+ }
886
+ }
887
+ if (req.callback != null) {
888
+ req.callback.onResult(null, JPushInterface.ErrorCode.TIMEOUT);
889
+ }
890
+ }
891
+
892
+ private void readLoop(final Context context, Socket socket, InputStream in) {
893
+ try {
894
+ while (!socket.isClosed()) {
895
+ long defaultUid = JCoreStorage.getUid(context);
896
+ JCoreWireCodec.DecodedFrame frame = JCoreWireCodec.readFrameFromStream(in, defaultUid);
897
+ if (frame == null) {
898
+ break;
899
+ }
900
+ handleIncomingFrame(context, frame);
901
+ }
902
+ } catch (Throwable ignored) {
903
+ } finally {
904
+ boolean wasCurrentSocket = false;
905
+ boolean wasLoggedIn = false;
906
+ boolean droppedUnexpectedly = false;
907
+ synchronized (socketLock) {
908
+ if (this.activeSocket == socket) {
909
+ wasCurrentSocket = true;
910
+ wasLoggedIn = this.loggedIn;
911
+ this.loggedIn = false;
912
+ droppedUnexpectedly = !socket.isClosed();
913
+ try {
914
+ socket.close();
915
+ } catch (Throwable ignored) {
916
+ }
917
+ this.activeSocket = null;
918
+ this.activeOutput = null;
919
+ }
920
+ }
921
+ if (wasCurrentSocket) {
922
+ cancelHeartbeatTimer();
923
+ if (wasLoggedIn) {
924
+ JCoreStorage.setConnected(context, false);
925
+ }
926
+ for (ResponseSlot slot : pendingRequests.values()) {
927
+ slot.complete(null);
928
+ }
929
+ pendingRequests.clear();
930
+ if (wasLoggedIn && autoReconnectEnabled && droppedUnexpectedly && !JCoreStorage.isPushStopped(context)) {
931
+ final long delayMs = computeReconnectDelayMs(reconnectAttempt++, reconnectBaseDelayMs);
932
+ final long scheduledGen = this.connectionGeneration;
933
+ heartbeatExecutor.schedule(new Runnable() {
934
+ @Override
935
+ public void run() {
936
+ if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
937
+ return;
938
+ }
939
+ ioExecutor.execute(new Runnable() {
940
+ @Override
941
+ public void run() {
942
+ if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
943
+ return;
944
+ }
945
+ connectAndHandshake(context);
946
+ }
947
+ });
948
+ }
949
+ }, delayMs, TimeUnit.MILLISECONDS);
950
+ }
951
+ }
952
+ }
953
+ }
954
+
955
+ private void handleIncomingFrame(Context context, JCoreWireCodec.DecodedFrame frame) {
956
+ if (frame.command == JCoreWireCodec.CMD_PUSH_MESSAGE) {
957
+ JCoreWireCodec.PushEnvelope envelope = JCoreWireCodec.parsePushMessage(frame);
958
+ if (envelope != null) {
959
+ JLogger.i(
960
+ "TCP push message received msgType="
961
+ + envelope.messageType
962
+ + " msgId="
963
+ + envelope.messageId
964
+ );
965
+ long ackRid = frame.requestId != 0L ? frame.requestId : JCoreStorage.nextRid();
966
+ long uid = frame.uid != 0L ? frame.uid : JCoreStorage.getUid(context);
967
+ byte[] ackBody = JCoreWireCodec.buildMessageAckPayload(envelope.messageType, envelope.messageId);
968
+ sendFrameRaw(
969
+ JCoreWireCodec.CMD_MESSAGE_ACK,
970
+ JCoreWireCodec.VER_MESSAGE_ACK,
971
+ ackRid,
972
+ this.sessionId,
973
+ uid,
974
+ ackBody
975
+ );
976
+ ActionHelper.getInstance().handleDownlinkPushMessage(
977
+ context,
978
+ envelope.messageType,
979
+ envelope.messageId,
980
+ envelope.content
981
+ );
982
+ }
983
+ return;
984
+ }
985
+
986
+ if (frame.command == JCoreWireCodec.CMD_CTRL) {
987
+ JCoreWireCodec.CtrlCommand ctrl = JCoreWireCodec.parseCtrlCommand(frame.body);
988
+ if (ctrl != null) {
989
+ receivedCtrlCommands.add(ctrl);
990
+ long ackRid = frame.requestId != 0L ? frame.requestId : JCoreStorage.nextRid();
991
+ long uid = frame.uid != 0L ? frame.uid : JCoreStorage.getUid(context);
992
+ byte[] ackBody = JCoreWireCodec.buildCtrlAckPayload(0, ctrl.requestId);
993
+ sendFrameRaw(
994
+ JCoreWireCodec.CMD_CTRL,
995
+ JCoreWireCodec.VER_CTRL,
996
+ ackRid,
997
+ this.sessionId,
998
+ uid,
999
+ ackBody
1000
+ );
1001
+ }
1002
+ return;
1003
+ }
1004
+
1005
+ ResponseSlot slot = pendingRequests.remove(frame.requestId);
1006
+ if (slot != null) {
1007
+ slot.complete(frame);
1008
+ }
1009
+ }
1010
+
1011
+ public boolean sendFrameRaw(int command, int version, long requestId, int sid, long uid, byte[] body) {
1012
+ byte[] packet = JCoreWireCodec.encodeFrame(command, version, requestId, sid, uid, body);
1013
+ if (packet.length == 0) {
1014
+ return false;
1015
+ }
1016
+ synchronized (socketLock) {
1017
+ if (activeOutput == null) {
1018
+ return false;
1019
+ }
1020
+ try {
1021
+ activeOutput.write(packet);
1022
+ activeOutput.flush();
1023
+ return true;
1024
+ } catch (Throwable ignored) {
1025
+ return false;
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ public JCoreWireCodec.DecodedFrame sendRequest(int command, int version, byte[] body, long timeoutMs) {
1031
+ long uid = JCoreStorage.getUid(null);
1032
+ return sendRequestOnSocket(command, version, this.sessionId, uid, body, timeoutMs);
1033
+ }
1034
+
1035
+ private JCoreWireCodec.DecodedFrame sendRequestOnSocket(
1036
+ int command,
1037
+ int version,
1038
+ int sid,
1039
+ long uid,
1040
+ byte[] body,
1041
+ long timeoutMs
1042
+ ) {
1043
+ long rid = JCoreStorage.nextRid();
1044
+ return sendRequestWithRidOnSocket(command, version, rid, sid, uid, body, timeoutMs);
1045
+ }
1046
+
1047
+ private JCoreWireCodec.DecodedFrame sendRequestWithRidOnSocket(
1048
+ int command,
1049
+ int version,
1050
+ long rid,
1051
+ int sid,
1052
+ long uid,
1053
+ byte[] body,
1054
+ long timeoutMs
1055
+ ) {
1056
+ ResponseSlot slot = new ResponseSlot();
1057
+ pendingRequests.put(rid, slot);
1058
+ if (!sendFrameRaw(command, version, rid, sid, uid, body)) {
1059
+ pendingRequests.remove(rid);
1060
+ return null;
1061
+ }
1062
+ try {
1063
+ slot.latch.await(timeoutMs, TimeUnit.MILLISECONDS);
1064
+ } catch (InterruptedException e) {
1065
+ Thread.currentThread().interrupt();
1066
+ } finally {
1067
+ pendingRequests.remove(rid);
1068
+ }
1069
+ return slot.response;
1070
+ }
1071
+
1072
+ public String[] resolveUdpSisEndpoint(Context context, String sisHost, int sisPort) {
1073
+ DatagramSocket udpSocket = null;
1074
+ try {
1075
+ String requestJson = JCoreWireCodec.buildSisRequestJson(
1076
+ 1,
1077
+ JCoreStorage.getAppKey(context),
1078
+ JCoreStorage.getUid(context),
1079
+ "",
1080
+ JCoreStorage.getFailedIps(context),
1081
+ JCoreStorage.getSisCertVer(context)
1082
+ );
1083
+ int seed24 = JCoreCrypto.nextPositiveSeed24();
1084
+ byte[] packetBytes = JCoreWireCodec.encodeUdpSisPacket(requestJson, seed24);
1085
+
1086
+ udpSocket = new DatagramSocket();
1087
+ udpSocket.setSoTimeout(3000);
1088
+ InetAddress address = InetAddress.getByName(sisHost);
1089
+ DatagramPacket reqPacket = new DatagramPacket(packetBytes, packetBytes.length, address, sisPort);
1090
+ udpSocket.send(reqPacket);
1091
+
1092
+ byte[] recvBuf = new byte[4096];
1093
+ DatagramPacket respPacket = new DatagramPacket(recvBuf, recvBuf.length);
1094
+ udpSocket.receive(respPacket);
1095
+
1096
+ byte[] respBytes = new byte[respPacket.getLength()];
1097
+ System.arraycopy(recvBuf, 0, respBytes, 0, respBytes.length);
1098
+ String decodedJson = JCoreWireCodec.decodeUdpSisPacket(respBytes);
1099
+ if (!decodedJson.isEmpty()) {
1100
+ JCoreStorage.saveSisConfig(context, decodedJson);
1101
+ }
1102
+ Set<String> ips = JCoreWireCodec.extractJsonStringArray(decodedJson, "ips");
1103
+ if (ips.isEmpty()) {
1104
+ ips = JCoreWireCodec.extractJsonStringArray(decodedJson, "ssl_ips");
1105
+ }
1106
+ if (!ips.isEmpty()) {
1107
+ String first = ips.iterator().next();
1108
+ String[] hp = splitHostPort(first);
1109
+ if (hp != null) {
1110
+ return hp;
1111
+ }
1112
+ }
1113
+ } catch (Throwable ignored) {
1114
+ } finally {
1115
+ if (udpSocket != null) {
1116
+ udpSocket.close();
1117
+ }
1118
+ }
1119
+ return new String[] { DEFAULT_HOST, String.valueOf(DEFAULT_PORT) };
1120
+ }
1121
+
1122
+ public String[] resolveSisEndpoint(Context context) {
1123
+ if (customHost != null && !customHost.isEmpty() && customPort > 0) {
1124
+ return new String[] { customHost, String.valueOf(customPort) };
1125
+ }
1126
+ if (!"udp-only".equalsIgnoreCase(customSisUrl)) {
1127
+ OutputStream out = null;
1128
+ InputStream in = null;
1129
+ try {
1130
+ String aesKey = JCoreCrypto.generateHttpSeedKey();
1131
+ JSONObject body = new JSONObject();
1132
+ body.put("type", 1);
1133
+ body.put("opera", "");
1134
+ body.put("uid", JCoreStorage.getUid(context));
1135
+ body.put("appkey", JCoreStorage.getAppKey(context));
1136
+ body.put("sdkver", "5.1.1");
1137
+ body.put("platform", 0);
1138
+
1139
+ byte[] compressed = JCoreCrypto.gzipCompress(body.toString().getBytes(StandardCharsets.UTF_8));
1140
+ byte[] encrypted = JCoreCrypto.aesCbcEncrypt(compressed, aesKey, SIS_AES_IV, 32);
1141
+ String auth = JCoreCrypto.rsaEncryptBase64(aesKey, SIS_RSA_PUBLIC_KEY);
1142
+
1143
+ String targetSisUrl = (customSisUrl != null && !customSisUrl.isEmpty()) ? customSisUrl : SIS_URL;
1144
+ URL url = new URL(targetSisUrl);
1145
+ HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
1146
+ conn.setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
1147
+ conn.setConnectTimeout(5000);
1148
+ conn.setReadTimeout(5000);
1149
+ conn.setRequestMethod("POST");
1150
+ conn.setDoOutput(true);
1151
+ conn.setRequestProperty("Content-Type", "application/json");
1152
+ conn.setRequestProperty("Authorization", auth);
1153
+
1154
+ out = conn.getOutputStream();
1155
+ out.write(encrypted);
1156
+ out.flush();
1157
+
1158
+ if (conn.getResponseCode() == 200) {
1159
+ in = conn.getInputStream();
1160
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1161
+ byte[] chunk = new byte[2048];
1162
+ int read;
1163
+ while ((read = in.read(chunk)) != -1) {
1164
+ buffer.write(chunk, 0, read);
1165
+ }
1166
+ byte[] decrypted = JCoreCrypto.aesCbcDecrypt(buffer.toByteArray(), aesKey, SIS_AES_IV, 32);
1167
+ byte[] plain = JCoreCrypto.gzipDecompress(decrypted);
1168
+ String plainStr = new String(plain, StandardCharsets.UTF_8);
1169
+ JCoreStorage.saveSisConfig(context, plainStr);
1170
+ Set<String> ips = JCoreWireCodec.extractJsonStringArray(plainStr, "ips");
1171
+ if (ips.isEmpty()) {
1172
+ ips = JCoreWireCodec.extractJsonStringArray(plainStr, "ssl_ips");
1173
+ }
1174
+ if (!ips.isEmpty()) {
1175
+ String first = ips.iterator().next();
1176
+ String[] hp = splitHostPort(first);
1177
+ if (hp != null) {
1178
+ return hp;
1179
+ }
1180
+ }
1181
+ JSONObject json = new JSONObject(plainStr);
1182
+ JSONArray sslIps = json.optJSONArray("ssl_ips");
1183
+ if (sslIps != null && sslIps.length() > 0) {
1184
+ String first = sslIps.optString(0, "");
1185
+ String[] hp = splitHostPort(first);
1186
+ if (hp != null) {
1187
+ return hp;
1188
+ }
1189
+ }
1190
+ }
1191
+ } catch (Throwable ignored) {
1192
+ } finally {
1193
+ try {
1194
+ if (out != null) {
1195
+ out.close();
1196
+ }
1197
+ if (in != null) {
1198
+ in.close();
1199
+ }
1200
+ } catch (Throwable ignored) {
1201
+ }
1202
+ }
1203
+ }
1204
+ return resolveUdpSisEndpoint(context, defaultSisUdpHost, defaultSisUdpPort);
1205
+ }
1206
+
1207
+ /**
1208
+ * HTTP Report Channel (/v3/report):
1209
+ * Builds envelope {"platform":"a","uid":uid,"app_key":appKey,"channel":channel,"app_version":"1.0.0",
1210
+ * "sdk_ver":{"push":"5.9.0","core":"5.1.1"},"content":[reportItem]}, GZIP-compresses, encrypts with
1211
+ * aes256CbcHttpEncrypt (IV = "iop203040506aPk!"), sets Authorization: Basic base64(uid:sha1(uid + md5(password) + sha1(body)):aesKey)
1212
+ * and X-App-Key: appKey.
1213
+ */
1214
+ public boolean sendHttpReport(Context context, String reportItemJson) {
1215
+ OutputStream out = null;
1216
+ InputStream in = null;
1217
+ try {
1218
+ long uid = JCoreStorage.getUid(context);
1219
+ String appKey = JCoreStorage.getAppKey(context);
1220
+ String channel = JCoreStorage.getChannel(context);
1221
+ String password = JCoreStorage.getPassword(context);
1222
+
1223
+ String item = (reportItemJson != null && !reportItemJson.isEmpty()) ? reportItemJson : "{}";
1224
+ String envelopeJson = "{\"platform\":\"a\",\"uid\":" + uid
1225
+ + ",\"app_key\":\"" + JCoreWireCodec.escapeJson(appKey) + "\""
1226
+ + ",\"channel\":\"" + JCoreWireCodec.escapeJson(channel) + "\""
1227
+ + ",\"app_version\":\"1.0.0\""
1228
+ + ",\"sdk_ver\":{\"push\":\"5.9.0\",\"core\":\"5.1.1\"}"
1229
+ + ",\"content\":[" + item + "]}";
1230
+
1231
+ byte[] compressed = JCoreCrypto.gzipCompress(envelopeJson.getBytes(StandardCharsets.UTF_8));
1232
+ String aesKey = JCoreCrypto.generateHttpSeedKey();
1233
+ byte[] encryptedBody = JCoreCrypto.aes256CbcHttpEncrypt(compressed, aesKey);
1234
+ if (encryptedBody.length == 0) {
1235
+ return false;
1236
+ }
1237
+
1238
+ String bodySha1 = JCoreCrypto.sha1Hex(encryptedBody);
1239
+ String pwdMd5 = JCoreCrypto.md5Hex(password);
1240
+ String signSha1 = JCoreCrypto.sha1Hex(uid + pwdMd5 + bodySha1);
1241
+ String authRaw = uid + ":" + signSha1 + ":" + aesKey;
1242
+ String authHeader = "Basic " + JCoreCrypto.base64Encode(authRaw.getBytes(StandardCharsets.UTF_8));
1243
+
1244
+ String targetUrl = customReportUrl;
1245
+ if (targetUrl == null || targetUrl.isEmpty()) {
1246
+ List<String> httpsReports = JCoreStorage.getSisHttpsReportIps(context);
1247
+ if (!httpsReports.isEmpty()) {
1248
+ String hostPort = httpsReports.get(0);
1249
+ targetUrl = hostPort.startsWith("http") ? hostPort : ("https://" + hostPort + "/v3/report");
1250
+ } else {
1251
+ List<String> httpReports = JCoreStorage.getSisHttpReportIps(context);
1252
+ if (!httpReports.isEmpty()) {
1253
+ String hostPort = httpReports.get(0);
1254
+ targetUrl = hostPort.startsWith("http") ? hostPort : ("http://" + hostPort + "/v3/report");
1255
+ } else {
1256
+ targetUrl = DEFAULT_REPORT_URL;
1257
+ }
1258
+ }
1259
+ }
1260
+
1261
+ URL url = new URL(targetUrl);
1262
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1263
+ if (conn instanceof HttpsURLConnection) {
1264
+ ((HttpsURLConnection) conn).setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
1265
+ }
1266
+ conn.setConnectTimeout(5000);
1267
+ conn.setReadTimeout(5000);
1268
+ conn.setRequestMethod("POST");
1269
+ conn.setDoOutput(true);
1270
+ conn.setRequestProperty("Content-Type", "application/octet-stream");
1271
+ conn.setRequestProperty("Accept", "application/json");
1272
+ conn.setRequestProperty("X-App-Key", appKey);
1273
+ conn.setRequestProperty("Authorization", authHeader);
1274
+
1275
+ out = conn.getOutputStream();
1276
+ out.write(encryptedBody);
1277
+ out.flush();
1278
+
1279
+ int code = conn.getResponseCode();
1280
+ if (code >= 200 && code < 300) {
1281
+ in = conn.getInputStream();
1282
+ while (in.read() != -1) {
1283
+ // Drain response
1284
+ }
1285
+ return true;
1286
+ }
1287
+ } catch (Throwable ignored) {
1288
+ } finally {
1289
+ try {
1290
+ if (out != null) {
1291
+ out.close();
1292
+ }
1293
+ if (in != null) {
1294
+ in.close();
1295
+ }
1296
+ } catch (Throwable ignored) {
1297
+ }
1298
+ }
1299
+ return false;
1300
+ }
1301
+
1302
+ public boolean verifyTlsSession(SSLSocket socket, String host) {
1303
+ if (socket == null || host == null) {
1304
+ return false;
1305
+ }
1306
+ SSLSession session = socket.getSession();
1307
+ return session != null && HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
1308
+ }
1309
+
1310
+ public SSLSocket createVerifiedTlsSocket(String host, int port) throws Exception {
1311
+ SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
1312
+ SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
1313
+ socket.setSoTimeout(8000);
1314
+ socket.startHandshake();
1315
+ if (!verifyTlsSession(socket, host)) {
1316
+ socket.close();
1317
+ throw new SecurityException("TLS hostname verification failed for " + host);
1318
+ }
1319
+ return socket;
1320
+ }
1321
+
1322
+ private void broadcastConnectAndRegister(Context context) {
1323
+ String regId = JCoreStorage.getRegistrationId(context);
1324
+ ActionHelper.getInstance().dispatchRegisterEvent(context, regId);
1325
+ ActionHelper.getInstance().dispatchConnectedEvent(context, true);
1326
+ cn.jpush.android.api.JThirdPlatFormInterface.initializeFcm(context);
1327
+ }
1328
+
1329
+ public Bundle handleEvent(Context context, String sdkType, int cmd, String action, Bundle bundle, Object... extras) {
1330
+ Context app = JCoreStorage.getAppContext(context);
1331
+ switch (cmd) {
1332
+ case 4: // getRegistrationID
1333
+ return stringResult(JCoreStorage.getRegistrationId(app));
1334
+ case 6: // getChannel
1335
+ return stringResult(JCoreStorage.getChannel(app));
1336
+ case 7: // getAppKey
1337
+ return stringResult(JCoreStorage.getAppKey(app));
1338
+ case 8: // getDeviceId
1339
+ return stringResult(JCoreStorage.getDeviceId(app));
1340
+ case 11:
1341
+ case 12:
1342
+ if (extras != null) {
1343
+ for (Object extra : extras) {
1344
+ if (extra instanceof Runnable) {
1345
+ executeAsync((Runnable) extra);
1346
+ }
1347
+ }
1348
+ }
1349
+ return null;
1350
+ case 17:
1351
+ case 50:
1352
+ if (extras != null && extras.length > 0 && extras[0] instanceof byte[]) {
1353
+ byte[] rawData = (byte[]) extras[0];
1354
+ sendFrameRaw(
1355
+ JCoreWireCodec.CMD_PUSH_CTRL,
1356
+ 1,
1357
+ JCoreStorage.nextRid(),
1358
+ this.sessionId,
1359
+ JCoreStorage.getUid(app),
1360
+ rawData
1361
+ );
1362
+ }
1363
+ return null;
1364
+ case 19: // getReportTime
1365
+ Bundle timeBundle = new Bundle();
1366
+ timeBundle.putLong("result", System.currentTimeMillis() / 1000L);
1367
+ return timeBundle;
1368
+ case 20: // getUid
1369
+ Bundle uidBundle = new Bundle();
1370
+ uidBundle.putLong("result", JCoreStorage.getUid(app));
1371
+ return uidBundle;
1372
+ case 21: // isValidRegistered
1373
+ Bundle regBundle = new Bundle();
1374
+ regBundle.putBoolean("result", !JCoreStorage.getRegistrationId(app).isEmpty());
1375
+ return regBundle;
1376
+ case 67: // getConnectionState
1377
+ Bundle connBundle = new Bundle();
1378
+ connBundle.putBoolean("result", JCoreStorage.isConnected(app));
1379
+ return connBundle;
1380
+ case 96: // setAuth
1381
+ if (extras != null && extras.length > 0 && extras[0] instanceof Boolean) {
1382
+ JCoreStorage.setCollectionAuth(app, (Boolean) extras[0]);
1383
+ }
1384
+ return null;
1385
+ default:
1386
+ return null;
1387
+ }
1388
+ }
1389
+
1390
+ private static Bundle stringResult(String value) {
1391
+ Bundle b = new Bundle();
1392
+ b.putString("result", value != null ? value : "");
1393
+ return b;
1394
+ }
1395
+ }