fix init app not found id,change ffi from MainActivity to MainService,add boot service but not open

This commit is contained in:
csf 2022-02-09 17:04:13 +08:00
parent 2137f4b3f2
commit 9c3b10d6a9
9 changed files with 191 additions and 95 deletions

View File

@ -9,11 +9,23 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />-->
<application
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:label="RustDesk">
android:label="RustDesk"
android:requestLegacyExternalStorage="true">
<!-- 暂时不开启接收开机广播的功能 enabled设置为false-->
<receiver
android:name=".BootReceiver"
android:enabled="false"
android:exported="false">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".InputService"
android:enabled="true"
@ -23,6 +35,7 @@
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
@ -31,11 +44,11 @@
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
android:exported="true">
android:windowSoftInputMode="adjustResize">
<!--
Specifies an Android theme to apply to this Activity as soon as

View File

@ -0,0 +1,24 @@
package com.carriez.flutter_hbb
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.Toast
// 开机自启动 此功能暂不开启
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if ("android.intent.action.BOOT_COMPLETED" == intent.action){
val it = Intent(context,MainService::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(it)
}else{
context.startService(it)
}
}
}
}

View File

@ -50,7 +50,6 @@ class MainActivity : FlutterActivity() {
result.success(true)
}
"start_capture" -> {
// return bool
mainService?.let {
result.success(it.startCapture())
} ?: let {

View File

@ -95,6 +95,7 @@ class MainService : Service() {
fun rustSetByName(name: String, arg1: String, arg2: String) {
when (name) {
"try_start_without_auth" -> {
// TODO 改成 json 三个参数 类型 name id
// to UI
Log.d(logTag, "from rust:got try_start_without_auth")
Handler(Looper.getMainLooper()).post {
@ -105,7 +106,6 @@ class MainService : Service() {
Log.d(logTag, "activity.runOnUiThread invokeMethod try_start_without_auth,done")
}
// TODO notify
Log.d(logTag, "kotlin invokeMethod try_start_without_auth,done")
}
"start_capture" -> {
Log.d(logTag, "from rust:start_capture")
@ -114,7 +114,7 @@ class MainService : Service() {
name,
mapOf("peerID" to arg1, "name" to arg2)
)
Log.d(logTag, "activity.runOnUiThread invokeMethod try_start_without_auth,done")
Log.d(logTag, "activity.runOnUiThread invokeMethod start_capture,done")
}
if (isStart) {
Log.d(logTag, "正在录制")
@ -129,7 +129,7 @@ class MainService : Service() {
stopCapture()
Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod(name, null)
Log.d(logTag, "activity.runOnUiThread invokeMethod try_start_without_auth,done")
Log.d(logTag, "activity.runOnUiThread invokeMethod stop_capture,done")
}
}
else -> {}
@ -139,13 +139,14 @@ class MainService : Service() {
// jvm call rust
private external fun init(ctx: Context)
private external fun startServer()
private external fun ready()
private external fun sendVp9(data: ByteArray)
private val logTag = "LOG_SERVICE"
private val useVP9 = false
private val binder = LocalBinder()
private var _isReady = false
private var _isStart = false
private var _isReady = false // 是否获取了录屏权限
private var _isStart = false // 是否正在进行录制
val isReady: Boolean
get() = _isReady
val isStart: Boolean
@ -177,6 +178,7 @@ class MainService : Service() {
override fun onCreate() {
super.onCreate()
initNotification()
startServer() // 开启了rust服务但是没有设置可以接收连接 如果不开启 首次启动没法获得服务ID
}
override fun onBind(intent: Intent): IBinder {
@ -194,11 +196,9 @@ class MainService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("whichService", "this service:${Thread.currentThread()}")
// initService是关键的逻辑 在用户点击开始监听或者获取到视频捕捉权限的时候执行initService
// 只有init的时候通过onStartCommand 且开启前台服务
if (intent?.action == INIT_SERVICE) {
Log.d(logTag, "service starting:${startId}:${Thread.currentThread()}")
// createForegroundNotification(this)
createForegroundNotification()
val mMediaProjectionManager =
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
@ -209,12 +209,13 @@ class MainService : Service() {
checkMediaPermission()
surface = createSurface()
init(this)
startServer()
ready()
_isReady = true
} ?: let {
Log.d(logTag, "获取mMediaProjection失败")
}
} else if (intent?.action == ACTION_LOGIN_REQ_NOTIFY) {
// TODO notify 重新适配多连接的情况
val notifyLoginRes = intent.getBooleanExtra(EXTRA_LOGIN_REQ_NOTIFY, false)
Log.d(logTag, "从通知栏点击了:$notifyLoginRes")
}
@ -264,6 +265,9 @@ class MainService : Service() {
}
fun startCapture(): Boolean {
if (isStart){
return true
}
if (mediaProjection == null) {
Log.w(logTag, "startCapture fail,mediaProjection is null")
return false
@ -288,6 +292,7 @@ class MainService : Service() {
fun stopCapture() {
Log.d(logTag, "Stop Capture")
_isStart = false
audioRecordStat = false
virtualDisplay?.release()
videoEncoder?.let {
it.signalEndOfInputStream()
@ -295,15 +300,12 @@ class MainService : Service() {
it.release()
}
audioRecorder?.startRecording()
audioRecordStat = false
// audioRecorder 如果无法重新创建 保留服务的情况不要释放
// audioRecorder?.stop()
// mediaProjection?.stop()
virtualDisplay = null
videoEncoder = null
videoData = null
// audioRecorder 如果无法重新创建 保留服务的情况不要释放
// audioRecorder?.stop()
// audioRecorder = null
// audioData = null
}
@ -318,6 +320,7 @@ class MainService : Service() {
mediaProjection = null
checkMediaPermission()
stopService(Intent(this,InputService::class.java)) // close input service maybe not work
stopForeground(true)
stopSelf()
}

View File

@ -35,34 +35,6 @@ fun testVP9Support(): Boolean {
return res != null
}
fun createForegroundNotification(ctx: Service) {
// 设置通知渠道 android8开始引入 老版本会被忽略 这个东西的作用相当于为通知分类 给用户选择通知消息的种类
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "RustDeskForeground"
val channelName = "RustDesk屏幕分享服务状态"
val channel = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Share your Android Screen with RustDeskService"
}
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
channelId
} else {
""
}
val notification: Notification = NotificationCompat.Builder(ctx, channelId)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
ctx.startForeground(11, notification)
}
fun createNormalNotification(
ctx: Context,
title: String,

View File

@ -291,6 +291,50 @@ https://developer.android.com/about/versions/oreo/background?hl=zh-cn#services
<hr>
### 开机自启动
利用接收RECEIVE_BOOT_COMPLETED的系统广播
- 权限:
- 清单文件
```xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
<application...>
...
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
...
```
- 创建一个class文件类继承自BroadcastReceiver()
```kotlin
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null) {
if ("android.intent.action.BOOT_COMPLETED" == intent.action){
val it = Intent(context,MainService::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context?.startForegroundService(it)
}else{
context?.startService(it)
}
Log.d(LOG_TAG,"onReceive done")
}
}
}
}
```
<hr>
### 其他
- Kotlin 与 compose 版本设置问题
- https://stackoverflow.com/questions/67600344/jetpack-compose-on-kotlin-1-5-0

View File

@ -228,9 +228,8 @@ toAndroidChannelInit() {
}
case "start_capture":
{
var peerID = call.arguments["peerID"] as String;
var name = call.arguments["name"] as String;
ServerPage.serverModel.setPeer(true, name: name, id: peerID);
clearLoginReqAlert();
ServerPage.serverModel.updateClientState();
break;
}
case "stop_capture":

View File

@ -547,8 +547,8 @@ class ClientState {
class ServerModel with ChangeNotifier {
bool _mediaOk;
bool _inputOk;
bool _isStart;
// bool _needServerOpen;
bool _isPeerStart;
bool _isFileTransfer;
String _peerName;
String _peerID;
@ -557,7 +557,9 @@ class ServerModel with ChangeNotifier {
bool get inputOk => _inputOk;
bool get isStart => _isStart;
// bool get needServerOpen => _needServerOpen;
bool get isPeerStart => _isPeerStart;
bool get isFileTransfer => _isFileTransfer;
@ -568,11 +570,16 @@ class ServerModel with ChangeNotifier {
ServerModel() {
_mediaOk = false;
_inputOk = false;
_isStart = false;
_isPeerStart = false;
_peerName = "";
_peerID = "";
}
// setNeedServerOpen(bool v){
// _needServerOpen = v;
// notifyListeners();
// }
changeStatue(String name, bool value) {
switch (name) {
case "media":
@ -588,7 +595,7 @@ class ServerModel with ChangeNotifier {
}
setPeer(bool enabled, {String name = "", String id = ""}) {
_isStart = enabled;
_isPeerStart = enabled;
if (name != "") _peerName = name;
if (id != "") _peerID = id;
notifyListeners();
@ -599,7 +606,7 @@ class ServerModel with ChangeNotifier {
debugPrint("getByName client_state string:$res");
try {
var clientState = ClientState.fromJson(jsonDecode(res));
_isStart = clientState.isStart;
_isPeerStart = clientState.isStart;
_isFileTransfer = clientState.isFileTransfer;
_peerName = clientState.name;
_peerID = clientState.peerId;
@ -609,7 +616,7 @@ class ServerModel with ChangeNotifier {
}
clearPeer() {
_isStart = false;
_isPeerStart = false;
_peerName = "";
_peerID = "";
notifyListeners();

View File

@ -57,13 +57,8 @@ class ServerPage extends StatelessWidget {
void checkService() {
//
toAndroidChannel.invokeMethod("check_service"); // jvm
toAndroidChannel.invokeMethod("check_service"); // jvm
ServerPage.serverModel.updateClientState();
// var state = FFI.getByName("client_state").split(":"); // rust
// var isStart = FFI.getByName("client_is_start") !="";// 使JSON
// if(state.length == 2){
// ServerPage.serverModel.setPeer(isStart,name:state[0],id:state[1]);
// }
}
class ServerInfo extends StatefulWidget {
@ -75,14 +70,20 @@ class _ServerInfoState extends State<ServerInfo> {
var _passwdShow = false;
// TODO set ID / PASSWORD
var _serverId = "";
var _serverPasswd = "";
var _serverId = TextEditingController(text: "");
var _serverPasswd = TextEditingController(text: "");
static const _emptyIdShow = "正在获取ID...";
@override
void initState() {
super.initState();
_serverId = FFI.getByName("server_id");
_serverPasswd = FFI.getByName("server_password");
var id = FFI.getByName("server_id");
_serverId.text = id==""?_emptyIdShow:id;
_serverPasswd.text = FFI.getByName("server_password");
if(_serverId.text == _emptyIdShow || _serverPasswd.text == ""){
fetchConfigAgain();
}
}
@override
@ -96,7 +97,7 @@ class _ServerInfoState extends State<ServerInfo> {
fontSize: 25.0,
fontWeight: FontWeight.bold,
color: MyTheme.accent),
initialValue: _serverId,
controller: _serverId,
decoration: InputDecoration(
icon: const Icon(Icons.perm_identity),
labelText: '服务ID',
@ -112,7 +113,7 @@ class _ServerInfoState extends State<ServerInfo> {
fontSize: 25.0,
fontWeight: FontWeight.bold,
color: MyTheme.accent),
initialValue: _serverPasswd,
controller: _serverPasswd,
decoration: InputDecoration(
icon: const Icon(Icons.lock),
labelText: '密码',
@ -131,6 +132,23 @@ class _ServerInfoState extends State<ServerInfo> {
],
));
}
fetchConfigAgain()async{
FFI.setByName("start_service");
var count = 0;
const maxCount = 10;
while(count<maxCount){
if(_serverId.text!=_emptyIdShow && _serverPasswd.text!=""){
break;
}
await Future.delayed(Duration(seconds: 2));
var id = FFI.getByName("server_id");
_serverId.text = id==""?_emptyIdShow:id;
_serverPasswd.text = FFI.getByName("server_password");
debugPrint("fetch id & passwd again at $count:id:${_serverId.text},passwd:${_serverPasswd.text}");
count++;
}
FFI.setByName("stop_service");
}
}
class PermissionChecker extends StatefulWidget {
@ -171,32 +189,46 @@ class _PermissionCheckerState extends State<PermissionChecker> {
}
}
void showLoginReqAlert(BuildContext context, String peerID, String name) {
BuildContext loginReqAlertCtx;
void showLoginReqAlert(BuildContext context, String peerID, String name)async {
debugPrint("got try_start_without_auth");
showDialog(
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("收到连接请求"),
content: Text("是否同意来自$name:$peerID的控制"),
actions: [
TextButton(
child: Text("接受"),
onPressed: () {
FFI.setByName("login_res", "true");
if(!ServerPage.serverModel.isFileTransfer){
_toAndroidStartCapture();
}
ServerPage.serverModel.setPeer(true);
Navigator.of(context).pop();
}),
TextButton(
child: Text("不接受"),
onPressed: () {
FFI.setByName("login_res", "false");
Navigator.of(context).pop();
})
],
));
builder: (alertContext) {
loginReqAlertCtx = alertContext;
return AlertDialog(
title: Text("收到连接请求"),
content: Text("是否同意来自$name:$peerID的控制"),
actions: [
TextButton(
child: Text("接受"),
onPressed: () {
FFI.setByName("login_res", "true");
if (!ServerPage.serverModel.isFileTransfer) {
_toAndroidStartCapture();
}
ServerPage.serverModel.setPeer(true);
Navigator.of(alertContext).pop();
}),
TextButton(
child: Text("不接受"),
onPressed: () {
FFI.setByName("login_res", "false");
Navigator.of(alertContext).pop();
})
],
);
});
debugPrint("alert done");
loginReqAlertCtx = null;
}
clearLoginReqAlert(){
if (loginReqAlertCtx!=null){
Navigator.of(loginReqAlertCtx).pop();
ServerPage.serverModel.updateClientState();
}
}
class PermissionRow extends StatelessWidget {
@ -213,7 +245,7 @@ class PermissionRow extends StatelessWidget {
children: [
Text.rich(TextSpan(children: [
TextSpan(
text: name + ":",
text: name + ": ",
style: TextStyle(fontSize: 16.0, color: MyTheme.accent50)),
TextSpan(
text: isOk ? "已开启" : "未开启",
@ -237,7 +269,7 @@ class ConnectionManager extends StatelessWidget {
final serverModel = Provider.of<ServerModel>(context);
var info =
"${serverModel.peerName != "" ? serverModel.peerName : "NA"}-${serverModel.peerID != "" ? serverModel.peerID : "NA"}";
return serverModel.isStart
return serverModel.isPeerStart
? myCard(Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -305,8 +337,11 @@ Future<Null> _toAndroidStartCapture() async {
// }
Future<Null> _toAndroidStopService() async {
FFI.setByName("stop_service");
FFI.setByName("close_conn");
ServerPage.serverModel.setPeer(false);
bool res = await toAndroidChannel.invokeMethod("stop_service");
FFI.setByName("stop_service");
debugPrint("_toAndroidStopSer:$res");
}