|
@@ -1,53 +1,123 @@
|
1
|
1
|
package io.flutter.plugins;
|
2
|
2
|
|
3
|
3
|
import android.Manifest;
|
|
4
|
+import android.annotation.SuppressLint;
|
4
|
5
|
import android.bluetooth.BluetoothAdapter;
|
|
6
|
+import android.bluetooth.BluetoothDevice;
|
|
7
|
+import android.content.BroadcastReceiver;
|
5
|
8
|
import android.content.Context;
|
|
9
|
+import android.content.Intent;
|
|
10
|
+import android.content.IntentFilter;
|
|
11
|
+import android.content.SharedPreferences;
|
|
12
|
+import android.content.pm.PackageManager;
|
|
13
|
+import android.location.LocationManager;
|
6
|
14
|
import android.os.Build;
|
|
15
|
+import android.text.TextUtils;
|
7
|
16
|
import android.util.Log;
|
8
|
17
|
import android.view.View;
|
9
|
18
|
import android.widget.Toast;
|
10
|
19
|
|
11
|
20
|
import androidx.annotation.NonNull;
|
|
21
|
+import androidx.core.app.ActivityCompat;
|
|
22
|
+import androidx.fragment.app.FragmentActivity;
|
12
|
23
|
|
13
|
24
|
import com.permissionx.guolindev.PermissionX;
|
|
25
|
+import com.szls.lszlgl.MainActivity;
|
14
|
26
|
|
15
|
27
|
import java.security.Permission;
|
16
|
28
|
import java.util.List;
|
|
29
|
+import java.util.concurrent.ExecutorService;
|
|
30
|
+import java.util.concurrent.LinkedBlockingDeque;
|
|
31
|
+import java.util.concurrent.ThreadFactory;
|
|
32
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
33
|
+import java.util.concurrent.TimeUnit;
|
17
|
34
|
|
18
|
35
|
import dev.fluttercommunity.plus.androidintent.IntentSender;
|
19
|
36
|
import dev.fluttercommunity.plus.androidintent.MethodCallHandlerImpl;
|
20
|
37
|
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
|
38
|
+import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
|
39
|
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
21
|
40
|
import io.flutter.plugin.common.BinaryMessenger;
|
|
41
|
+import io.flutter.plugin.common.EventChannel;
|
22
|
42
|
import io.flutter.plugin.common.MethodCall;
|
23
|
43
|
import io.flutter.plugin.common.MethodChannel;
|
24
|
44
|
|
|
45
|
+import io.flutter.plugin.common.EventChannel.EventSink;
|
|
46
|
+import io.flutter.plugin.common.EventChannel.StreamHandler;
|
|
47
|
+import io.flutter.plugins.bean.BlueDeviceInfo;
|
|
48
|
+import io.flutter.plugins.utils.BluetoothUtils;
|
|
49
|
+import io.flutter.plugins.utils.PrintUtil;
|
|
50
|
+
|
25
|
51
|
public class BluetoothPlugin implements FlutterPlugin, MethodChannel.MethodCallHandler {
|
26
|
52
|
|
27
|
53
|
private String methodChannelName = "io.flutter.plugins/bluetooth";
|
28
|
54
|
private MethodChannel methodChannel;
|
29
|
55
|
|
|
56
|
+ private ExecutorService executorService;
|
|
57
|
+
|
|
58
|
+ private static final String USER_DEFINED = "自定义";
|
|
59
|
+ private static final String delimiterStr = "_NIMBOT_";
|
|
60
|
+
|
|
61
|
+ private String printNameStart = "";
|
|
62
|
+
|
|
63
|
+ OnDiscoveryDeviceStreamHandler onDiscoveryDeviceStreamHandler;
|
|
64
|
+ private EventChannel onReceiveDeviceChannel;
|
|
65
|
+ public EventChannel.EventSink onReceiveSink;
|
|
66
|
+
|
|
67
|
+ private BlueDeviceInfo itemPosition;
|
|
68
|
+
|
|
69
|
+// private MainActivity _mainActivity;
|
30
|
70
|
|
|
71
|
+
|
|
72
|
+ Context _applicationContext;
|
31
|
73
|
private BluetoothAdapter mBluetoothAdapter;
|
32
|
74
|
|
33
|
|
- public BluetoothPlugin() {
|
|
75
|
+
|
|
76
|
+ public BluetoothPlugin(MainActivity activity) {
|
34
|
77
|
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
78
|
+// _mainActivity = activity;
|
|
79
|
+
|
|
80
|
+ //注册广播
|
|
81
|
+ IntentFilter intentFilter = new IntentFilter();
|
|
82
|
+ intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
|
|
83
|
+ intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
|
|
84
|
+ intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
|
|
85
|
+ intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
|
86
|
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
|
87
|
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
|
88
|
+ intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
|
|
89
|
+ Log.d("ble", "初始化:注册广播 ");
|
|
90
|
+ activity.registerReceiver(receiver, intentFilter);
|
|
91
|
+ Log.d("ble", "初始化: 注册完成");
|
|
92
|
+
|
|
93
|
+ //注册线程池
|
|
94
|
+ ThreadFactory threadFactory = runnable -> {
|
|
95
|
+ Thread thread = new Thread(runnable);
|
|
96
|
+ thread.setName("connect_activity_pool_%d");
|
|
97
|
+ return thread;
|
|
98
|
+ };
|
|
99
|
+ executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
|
|
100
|
+
|
35
|
101
|
|
36
|
102
|
}
|
37
|
103
|
|
38
|
104
|
@Override
|
39
|
105
|
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
40
|
106
|
setupChannels(flutterPluginBinding.getBinaryMessenger(), flutterPluginBinding.getApplicationContext());
|
|
107
|
+
|
|
108
|
+
|
41
|
109
|
}
|
42
|
110
|
|
43
|
111
|
private void setupChannels(BinaryMessenger binaryMessenger, Context applicationContext) {
|
44
|
112
|
|
45
|
|
- methodChannel = new MethodChannel(binaryMessenger, methodChannelName);
|
46
|
|
- applicationContext = applicationContext.getApplicationContext();
|
47
|
|
- applicationContext.
|
|
113
|
+ _applicationContext = applicationContext.getApplicationContext();
|
48
|
114
|
|
|
115
|
+ methodChannel = new MethodChannel(binaryMessenger, methodChannelName + "/methods");
|
49
|
116
|
methodChannel.setMethodCallHandler(this);
|
50
|
117
|
|
|
118
|
+ onReceiveDeviceChannel = new EventChannel(binaryMessenger, methodChannelName + "/onDiscoveryDevice");
|
|
119
|
+ onDiscoveryDeviceStreamHandler = new OnDiscoveryDeviceStreamHandler();
|
|
120
|
+ onReceiveDeviceChannel.setStreamHandler(onDiscoveryDeviceStreamHandler);
|
51
|
121
|
}
|
52
|
122
|
|
53
|
123
|
@Override
|
|
@@ -59,14 +129,28 @@ public class BluetoothPlugin implements FlutterPlugin, MethodChannel.MethodCallH
|
59
|
129
|
public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
|
60
|
130
|
switch (methodCall.method) {
|
61
|
131
|
|
62
|
|
- case "isSupportBle":
|
|
132
|
+ case "isBleOpen":
|
63
|
133
|
Log.d("ble", "isSupportBle");
|
64
|
|
- result.success(true);
|
|
134
|
+ result.success(mBluetoothAdapter.isEnabled());
|
|
135
|
+ break;
|
|
136
|
+ case "isSDKIntGreaterOrEqual":
|
|
137
|
+ Log.d("ble", "isSDKIntGreaterOrEqual");
|
|
138
|
+ result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
|
65
|
139
|
break;
|
66
|
|
- case "startScan":
|
67
|
|
- Log.d("ble", "startScan");
|
68
|
|
- startScan(result);
|
|
140
|
+ case "startBluetoothDiscovery":
|
|
141
|
+ Log.d("ble", "startBluetoothDiscovery");
|
|
142
|
+ startBluetoothDiscovery(result);
|
69
|
143
|
break;
|
|
144
|
+ case "startBluetoothPair":
|
|
145
|
+ Log.d("ble", "startBluetoothPair");
|
|
146
|
+ startBluetoothPair(methodCall, result);
|
|
147
|
+ break;
|
|
148
|
+ case "startBluetoothConnect":
|
|
149
|
+ Log.d("ble", "startBluetoothConnect :"+methodCall.arguments);
|
|
150
|
+ startBluetoothConnect(methodCall, result);
|
|
151
|
+ break;
|
|
152
|
+
|
|
153
|
+
|
70
|
154
|
case "autoLogin":
|
71
|
155
|
|
72
|
156
|
break;
|
|
@@ -74,58 +158,211 @@ public class BluetoothPlugin implements FlutterPlugin, MethodChannel.MethodCallH
|
74
|
158
|
|
75
|
159
|
break;
|
76
|
160
|
case "currentAccount":
|
77
|
|
- break;
|
|
161
|
+ break;
|
78
|
162
|
case "logout":
|
79
|
|
- break;
|
|
163
|
+ break;
|
80
|
164
|
case "markAllMessagesRead":
|
81
|
|
- break;
|
82
|
|
- default:
|
83
|
|
- {
|
|
165
|
+ break;
|
|
166
|
+ default: {
|
84
|
167
|
result.notImplemented();
|
85
|
168
|
break;
|
86
|
169
|
}
|
87
|
170
|
}
|
88
|
171
|
}
|
89
|
172
|
|
90
|
|
-
|
91
|
173
|
private void teardownChannels() {
|
92
|
174
|
methodChannel.setMethodCallHandler(null);
|
93
|
175
|
methodChannel = null;
|
|
176
|
+// // 注销广播接收器
|
|
177
|
+// if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering()) {
|
|
178
|
+// mBluetoothAdapter.cancelDiscovery();
|
|
179
|
+// }
|
|
180
|
+// requireActivity().unregisterReceiver(receiver);
|
94
|
181
|
}
|
95
|
182
|
|
|
183
|
+ void startBluetoothDiscovery(@NonNull MethodChannel.Result result) {
|
|
184
|
+
|
|
185
|
+ if (ActivityCompat.checkSelfPermission(_applicationContext, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
|
|
186
|
+
|
|
187
|
+ Log.d("ble", "checkSelfPermission false");
|
|
188
|
+ return;
|
|
189
|
+ }
|
|
190
|
+
|
|
191
|
+ if (mBluetoothAdapter.isDiscovering()) {
|
96
|
192
|
|
97
|
|
- void startScan(@NonNull MethodChannel.Result result) {
|
|
193
|
+ if (mBluetoothAdapter.cancelDiscovery()) {
|
|
194
|
+ executorService.execute(() -> {
|
|
195
|
+ try {
|
|
196
|
+ //取消后等待1s后再次搜索
|
|
197
|
+ Thread.sleep(1000);
|
|
198
|
+ mBluetoothAdapter.startDiscovery();
|
|
199
|
+ Log.d("ble", "测试:开始搜索7");
|
|
200
|
+ } catch (InterruptedException e) {
|
|
201
|
+ Log.d("ble", "测试:开始搜索8");
|
|
202
|
+ e.printStackTrace();
|
|
203
|
+ }
|
|
204
|
+ });
|
98
|
205
|
|
99
|
|
- Log.d("ble", "开始搜索 ");
|
100
|
|
- if (!mBluetoothAdapter.isEnabled()) {
|
101
|
|
- result.success(1); //
|
|
206
|
+
|
|
207
|
+ }
|
102
|
208
|
} else {
|
103
|
|
- permissionRequest();
|
|
209
|
+ Log.d("ble", "测试:开始搜索9");
|
|
210
|
+ mBluetoothAdapter.startDiscovery();
|
|
211
|
+ Log.d("ble", "测试:开始搜索10");
|
104
|
212
|
}
|
105
|
213
|
}
|
106
|
214
|
|
107
|
|
- private void permissionRequest() {
|
108
|
|
- String[] permissions;
|
109
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
110
|
|
- permissions = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};
|
111
|
|
- } else {
|
112
|
|
- permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
|
|
215
|
+ void startBluetoothPair(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
|
|
216
|
+
|
|
217
|
+ if (ActivityCompat.checkSelfPermission(_applicationContext, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
|
|
218
|
+ return;
|
|
219
|
+ }
|
|
220
|
+ if (mBluetoothAdapter.isDiscovering()) {
|
|
221
|
+ mBluetoothAdapter.cancelDiscovery();
|
113
|
222
|
}
|
|
223
|
+ Log.d("ble", "startBluetoothPair");
|
|
224
|
+ String deviceMac = methodCall.arguments.toString();
|
|
225
|
+ String[] deviceInfoList = deviceMac.split(delimiterStr);
|
|
226
|
+ itemPosition = new BlueDeviceInfo(deviceInfoList[0], deviceInfoList[1], Integer.parseInt(deviceInfoList[2]));
|
|
227
|
+ BluetoothDevice bluetoothDevice = mBluetoothAdapter.getRemoteDevice(itemPosition.getDeviceHardwareAddress());
|
|
228
|
+
|
|
229
|
+ executorService.submit(() -> {
|
|
230
|
+// requireActivity().runOnUiThread(() -> {
|
|
231
|
+// bind.spinKit.setVisibility(View.GONE);
|
|
232
|
+// fragment = new MyDialogLoadingFragment("配对中");
|
|
233
|
+// fragment.show(requireActivity().getSupportFragmentManager(), "pairing");
|
|
234
|
+// });
|
|
235
|
+ Log.d("ble", "配对: 开始");
|
|
236
|
+ boolean returnValue = false;
|
|
237
|
+ try {
|
|
238
|
+ returnValue = BluetoothUtils.createBond(bluetoothDevice);
|
|
239
|
+ } catch (Exception e) {
|
|
240
|
+ Log.d("ble", "闪退日志" + e.getMessage());
|
|
241
|
+ }
|
|
242
|
+ Log.d("ble", "配对: 进行中:" + returnValue);
|
|
243
|
+ });
|
|
244
|
+ }
|
114
|
245
|
|
|
246
|
+ void startBluetoothConnect(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {
|
115
|
247
|
|
|
248
|
+ if (ActivityCompat.checkSelfPermission(_applicationContext, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
|
|
249
|
+ return;
|
|
250
|
+ }
|
|
251
|
+ if (mBluetoothAdapter.isDiscovering()) {
|
|
252
|
+ mBluetoothAdapter.cancelDiscovery();
|
|
253
|
+ }
|
116
|
254
|
|
117
|
|
- PermissionX.init(requireActivity())
|
118
|
|
- .permissions(permissions)
|
119
|
|
- .request(this::handlePermissionResult);
|
|
255
|
+ String deviceInfo = methodCall.arguments.toString();
|
|
256
|
+ String[] deviceInfoList = deviceInfo.split(delimiterStr);
|
|
257
|
+ for(int i =0; i < deviceInfoList.length ; i++){
|
|
258
|
+ Log.d("ble", i+":"+deviceInfoList[i]);
|
|
259
|
+ }
|
|
260
|
+ Log.d("ble", "startBluetoothConnect : ---"+deviceInfo + "----0:"+deviceInfoList[0]);
|
|
261
|
+ itemPosition = new BlueDeviceInfo(deviceInfoList[0], deviceInfoList[1], Integer.parseInt(deviceInfoList[2]));
|
|
262
|
+ BluetoothDevice bluetoothDevice = mBluetoothAdapter.getRemoteDevice(itemPosition.getDeviceHardwareAddress());
|
|
263
|
+
|
|
264
|
+ executorService.submit(() -> {
|
|
265
|
+// requireActivity().runOnUiThread(() -> {
|
|
266
|
+// bind.spinKit.setVisibility(View.GONE);
|
|
267
|
+// fragment = new MyDialogLoadingFragment("连接中");
|
|
268
|
+// fragment.show(requireActivity().getSupportFragmentManager(), "CONNECT");
|
|
269
|
+// });
|
|
270
|
+
|
|
271
|
+ BlueDeviceInfo blueDeviceInfo = new BlueDeviceInfo(bluetoothDevice.getName(), bluetoothDevice.getAddress(), itemPosition.getConnectState());
|
|
272
|
+ PrintUtil.setConnectedType(-1);
|
|
273
|
+ int connectResult = PrintUtil.connectBluetoothPrinter(blueDeviceInfo.getDeviceHardwareAddress());
|
|
274
|
+ Log.d("ble", "测试:连接结果 " + connectResult);
|
|
275
|
+ result.success(connectResult);
|
|
276
|
+
|
|
277
|
+ });
|
120
|
278
|
}
|
121
|
279
|
|
122
|
|
- private void handlePermissionResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
|
123
|
|
- if (allGranted) {
|
124
|
|
- Log.d("ble", "handleAllPermissionsGranted ");
|
125
|
|
-// handleAllPermissionsGranted();
|
126
|
|
- } else {
|
127
|
|
- Log.d("ble", "权限打开失败 ");
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+ private final BroadcastReceiver receiver = new BroadcastReceiver() {
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+ @SuppressLint("MissingPermission")
|
|
286
|
+ @Override
|
|
287
|
+ public void onReceive(Context context, Intent intent) {
|
|
288
|
+ String action = intent.getAction();
|
|
289
|
+ //蓝牙发现
|
|
290
|
+ if (BluetoothDevice.ACTION_FOUND.equals(action)) {
|
|
291
|
+ Log.v("ble", "测试:搜索中");
|
|
292
|
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
|
293
|
+
|
|
294
|
+ if (device != null) {
|
|
295
|
+ @SuppressLint("MissingPermission") String deviceName = device.getName();
|
|
296
|
+ String deviceHardwareAddress = device.getAddress();
|
|
297
|
+ @SuppressLint("MissingPermission") int deviceStatus = device.getBondState();
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+ @SuppressLint("MissingPermission") boolean supportBluetoothType = device.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC || device.getType() == BluetoothDevice.DEVICE_TYPE_DUAL;
|
|
301
|
+ boolean supportPrintName;
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+ if (USER_DEFINED.equals(printNameStart)) {
|
|
305
|
+ printNameStart = "";
|
|
306
|
+ }
|
|
307
|
+
|
|
308
|
+ if (TextUtils.isEmpty(printNameStart)) {
|
|
309
|
+ supportPrintName = deviceName != null;
|
|
310
|
+ } else {
|
|
311
|
+ supportPrintName = deviceName != null && deviceName.startsWith(printNameStart);
|
|
312
|
+ }
|
|
313
|
+
|
|
314
|
+ if (supportBluetoothType && supportPrintName) {
|
|
315
|
+
|
|
316
|
+ Log.d("ble", "测试:打印机名称- " + deviceName + ",设备地址:" + deviceHardwareAddress + ",设备类型:" + device.getType() + ", 设备状态:"+deviceStatus);
|
|
317
|
+ Log.d("ble", "sink:" + onReceiveSink + ":" + "onDiscoveryDeviceStreamHandler" + (onDiscoveryDeviceStreamHandler != null) + "onDiscoveryDeviceStreamHandler.sink != null:" + (onDiscoveryDeviceStreamHandler.sink));
|
|
318
|
+ if (onDiscoveryDeviceStreamHandler != null && onDiscoveryDeviceStreamHandler.sink != null) {
|
|
319
|
+ onDiscoveryDeviceStreamHandler.sink.success(deviceName+delimiterStr+deviceHardwareAddress+delimiterStr+deviceStatus);
|
|
320
|
+ }
|
|
321
|
+ }
|
|
322
|
+
|
|
323
|
+ }
|
|
324
|
+
|
|
325
|
+ } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
|
|
326
|
+ Log.v("ble", "测试:开始搜索");
|
|
327
|
+// bind.spinKit.setVisibility(View.VISIBLE);
|
|
328
|
+ } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
|
|
329
|
+ Log.v("ble", "测试:搜索结束");
|
|
330
|
+// bind.spinKit.setVisibility(View.GONE);
|
|
331
|
+ } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
|
|
332
|
+ int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
|
|
333
|
+ Log.d("ble", "测试:配对状态改变:0 " + state);
|
|
334
|
+ if(itemPosition != null) {
|
|
335
|
+ itemPosition.setConnectState(state);
|
|
336
|
+ if (onDiscoveryDeviceStreamHandler != null && onDiscoveryDeviceStreamHandler.sink != null) {
|
|
337
|
+ onDiscoveryDeviceStreamHandler.sink.success(itemPosition.getDeviceName()+delimiterStr+itemPosition.getDeviceHardwareAddress()+delimiterStr+itemPosition.getConnectState());
|
|
338
|
+ }
|
|
339
|
+ }
|
|
340
|
+ } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
|
|
341
|
+// if (fragment != null) {
|
|
342
|
+// fragment.dismiss();
|
|
343
|
+// }
|
|
344
|
+ }
|
128
|
345
|
}
|
|
346
|
+ };
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+class OnDiscoveryDeviceStreamHandler implements EventChannel.StreamHandler {
|
|
353
|
+
|
|
354
|
+ public EventChannel.EventSink sink;
|
|
355
|
+
|
|
356
|
+ @Override
|
|
357
|
+ public void onListen(Object o, EventChannel.EventSink eventSink) {
|
|
358
|
+
|
|
359
|
+ Log.d("ble", "OnDiscoveryDeviceStreamHandler: onListen");
|
|
360
|
+ sink = eventSink;
|
129
|
361
|
}
|
|
362
|
+ @Override
|
|
363
|
+ public void onCancel(Object o) {
|
130
|
364
|
|
|
365
|
+ Log.e("zhousuhua","OnReceiveDataStreamHandler=== onCancel");
|
|
366
|
+ sink = null;
|
|
367
|
+ }
|
131
|
368
|
}
|