diff --git a/app/build.gradle b/app/build.gradle index f0957b0..54cddc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,11 +9,11 @@ android { defaultConfig { applicationId "com.sealdice.dice" - minSdk 26 + minSdk 23 //noinspection ExpiredTargetSdkVersion targetSdk 28 - versionCode 27 - versionName "v0.4.7-rc2" + versionCode 28 + versionName "v0.4.8-rc" buildConfigField "String", "DOCUMENTS_AUTHORITY", "\"com.sealdice.dice.authorities\"" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -48,10 +48,10 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0-alpha08' - implementation 'androidx.navigation:navigation-ui-ktx:2.6.0-alpha08' + implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0-alpha09' + implementation 'androidx.navigation:navigation-ui-ktx:2.6.0-alpha09' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" - implementation 'androidx.core:core:1.9.0' + implementation 'androidx.core:core:1.10.0' implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.lifecycle:lifecycle-service:2.6.1' implementation 'com.squareup.okhttp3:okhttp:4.9.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 72761c1..28064e6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,7 +49,9 @@ - + diff --git a/app/src/main/java/com/sealdice/dice/DimWakeLockService.kt b/app/src/main/java/com/sealdice/dice/DimWakeLockService.kt new file mode 100644 index 0000000..4789c17 --- /dev/null +++ b/app/src/main/java/com/sealdice/dice/DimWakeLockService.kt @@ -0,0 +1,19 @@ +package com.sealdice.dice + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import android.os.PowerManager + +class DimWakeLockService : Service(){ + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + val wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "DIM:LocationManagerService") + wakeLock.acquire() + return START_STICKY + } + override fun onBind(intent: Intent?): IBinder? { + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sealdice/dice/FirstFragment.kt b/app/src/main/java/com/sealdice/dice/FirstFragment.kt index 9942a51..c8d898c 100644 --- a/app/src/main/java/com/sealdice/dice/FirstFragment.kt +++ b/app/src/main/java/com/sealdice/dice/FirstFragment.kt @@ -1,14 +1,14 @@ package com.sealdice.dice import android.Manifest -import android.content.Context -import android.content.DialogInterface -import android.content.Intent +import android.content.* import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.IBinder +import android.provider.Settings import android.util.Log import android.view.* import android.widget.Toast @@ -25,7 +25,9 @@ import com.sealdice.dice.databinding.FragmentFirstBinding import com.sealdice.dice.utils.Utils import com.sealdice.dice.utils.ViewModelMain import kotlinx.coroutines.* +import java.io.BufferedReader import java.io.File +import java.io.InputStreamReader import kotlin.system.exitProcess @@ -40,6 +42,20 @@ class FirstFragment : Fragment() { // onDestroyView. private val binding get() = _binding private var shellLogs = "" + private var isBound = false + private var processService: ProcessService? = null + + private val connection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + val binder = service as ProcessService.MyBinder + processService = binder.getService() + isBound = true + } + + override fun onServiceDisconnected(name: ComponentName) { + isBound = false + } + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -53,9 +69,6 @@ class FirstFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var isrun = false -// val packageManager = this.activity?.packageManager -// val packageName = this.activity?.packageName -// val packageInfo = packageName?.let { packageManager?.getPackageInfo(it, 0) } val versionName = BuildConfig.VERSION_NAME val packageName = BuildConfig.APPLICATION_ID val sharedPreferences = context?.let { PreferenceManager.getDefaultSharedPreferences(it) } @@ -94,7 +107,7 @@ class FirstFragment : Fragment() { ) } alertDialogBuilder?.setTitle("控制台") - alertDialogBuilder?.setMessage(shellLogs) + alertDialogBuilder?.setMessage(processService?.getShellLogs()) alertDialogBuilder?.setPositiveButton("确定") { _: DialogInterface, _: Int -> } alertDialogBuilder?.create()?.show() @@ -228,8 +241,22 @@ class FirstFragment : Fragment() { alertDialogBuilder?.create()?.show() } binding.buttonFirst.setOnClickListener { - if (isrun) { - shellLogs += "sealdice is running" + val intentBtr = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + intentBtr.data = Uri.parse("package:$packageName") + startActivity(intentBtr) + if (BuildConfig.DEBUG) { + val alertDialogBuilder = context?.let { it1 -> + AlertDialog.Builder( + it1, R.style.Theme_Mshell_DialogOverlay + ) + } + alertDialogBuilder?.setTitle("DEBUG") + alertDialogBuilder?.setMessage("Support 64 abis:"+Build.SUPPORTED_64_BIT_ABIS.contentToString()+"\nSupport 32 abis:"+Build.SUPPORTED_32_BIT_ABIS.contentToString()+"\nSupport abis:"+Build.SUPPORTED_ABIS.contentToString()) + alertDialogBuilder?.setPositiveButton("确定") { _: DialogInterface, _: Int -> + } + alertDialogBuilder?.create()?.show() + } + if (processService?.isRunning() == true) { val alertDialogBuilder = context?.let { it1 -> AlertDialog.Builder( it1, R.style.Theme_Mshell_DialogOverlay @@ -245,8 +272,8 @@ class FirstFragment : Fragment() { if (sharedPreferences?.getBoolean("extract_on_start", true) == true) { ExtractAssets(context).extractResources("sealdice") } - val args = sharedPreferences?.getString("launch_args", "") - execShell("cd sealdice&&./sealdice-core $args",true) +// val args = sharedPreferences?.getString("launch_args", "") +// execShell("cd sealdice&&./sealdice-core $args\n",true) binding.buttonTut.visibility = View.GONE binding.buttonInput.visibility = View.GONE binding.buttonOutput.visibility = View.GONE @@ -255,17 +282,34 @@ class FirstFragment : Fragment() { binding.buttonThird.visibility = View.VISIBLE binding.buttonConsole.visibility = View.VISIBLE binding.buttonFirst.visibility = View.GONE - if (!launchAliveService(context)) { - val alertDialogBuilder = context?.let { it1 -> - AlertDialog.Builder( - it1, R.style.Theme_Mshell_DialogOverlay - ) + if (Build.VERSION.SDK_INT >= 28) { + val permissionState = + context?.let { it1 -> ContextCompat.checkSelfPermission(it1, Manifest.permission.FOREGROUND_SERVICE) } + if (permissionState != PackageManager.PERMISSION_GRANTED) { + this.activity?.let { it1 -> ActivityCompat.requestPermissions(it1, arrayOf(Manifest.permission.FOREGROUND_SERVICE), 1) } } - alertDialogBuilder?.setTitle("提示") - alertDialogBuilder?.setMessage("似乎并没有开启任何保活策略,这可能导致后台被清理") - alertDialogBuilder?.setPositiveButton("确定") { _: DialogInterface, _: Int ->} - alertDialogBuilder?.create()?.show() } + val intentNoti = Intent(context, ProcessService::class.java) + if (Build.VERSION.SDK_INT >= 26) { + context?.startForegroundService(intentNoti).also { _ -> + activity?.bindService(intentNoti, connection, Context.BIND_AUTO_CREATE) + } + } else { + context?.startService(intentNoti).also { _ -> + activity?.bindService(intentNoti, connection, Context.BIND_AUTO_CREATE) + } + } + launchAliveService(context) +// val alertDialogBuilder = context?.let { it1 -> +// AlertDialog.Builder( +// it1, R.style.Theme_Mshell_DialogOverlay +// ) +// } +// alertDialogBuilder?.setTitle("提示") +// alertDialogBuilder?.setMessage("似乎并没有开启任何保活策略,这可能导致后台被清理") +// alertDialogBuilder?.setPositiveButton("确定") { _: DialogInterface, _: Int ->} +// alertDialogBuilder?.create()?.show() + GlobalScope.launch(context = Dispatchers.IO) { for (i in 0..10) { withContext(Dispatchers.Main) { @@ -292,12 +336,36 @@ class FirstFragment : Fragment() { } binding.buttonSecond.setOnClickListener { binding.buttonSecond.visibility = View.GONE - this.activity?.stopService(Intent(context, NotificationService::class.java)) + activity?.unbindService(connection) + this.activity?.stopService(Intent(context, ProcessService::class.java)) this.activity?.stopService(Intent(context, MediaService::class.java)) this.activity?.stopService(Intent(context, WakeLockService::class.java)) this.activity?.stopService(Intent(context, FloatWindowService::class.java)) this.activity?.stopService(Intent(context, HeartbeatService::class.java)) - execShell("pkill -SIGINT sealdice-core",false) + this.activity?.stopService(Intent(context, UpdateService::class.java)) + val builder: AlertDialog.Builder? = context?.let { it1 -> AlertDialog.Builder(it1) } + builder?.setCancelable(false) // if you want user to wait for some process to finish, + builder?.setView(R.layout.layout_loading_dialog) + val dialog = builder?.create() + dialog?.show() + GlobalScope.launch(context = Dispatchers.IO){ + for (i in 0..5) { + Thread.sleep(1000) + } + withContext(Dispatchers.Main){ + dialog?.dismiss() + } + } + val alertDialogBuilder = context?.let { it1 -> + AlertDialog.Builder( + it1, R.style.Theme_Mshell_DialogOverlay + ) + } + alertDialogBuilder?.setTitle("提示") + alertDialogBuilder?.setMessage("请等到ui彻底无法打开后再点退出!") + alertDialogBuilder?.setPositiveButton("确定") { _: DialogInterface, _: Int -> + } + alertDialogBuilder?.create()?.show() binding.buttonExit.visibility = View.VISIBLE } } @@ -307,17 +375,21 @@ class FirstFragment : Fragment() { val sharedPreferences = context?.let { PreferenceManager.getDefaultSharedPreferences(it) } var executed = false if (sharedPreferences != null) { - if (sharedPreferences.getBoolean("alive_notification", true)) { - if (Build.VERSION.SDK_INT >= 28) { - val permissionState = context.let { it1 -> ContextCompat.checkSelfPermission(it1, Manifest.permission.FOREGROUND_SERVICE) } - if (permissionState != PackageManager.PERMISSION_GRANTED) { - this.activity?.let { it1 -> ActivityCompat.requestPermissions(it1, arrayOf(Manifest.permission.FOREGROUND_SERVICE), 1) } - } - } - val intentNoti = Intent(context, NotificationService::class.java) - context.startForegroundService(intentNoti) - executed = true - } +// if (sharedPreferences.getBoolean("alive_notification", true)) { +// if (Build.VERSION.SDK_INT >= 28) { +// val permissionState = context.let { it1 -> ContextCompat.checkSelfPermission(it1, Manifest.permission.FOREGROUND_SERVICE) } +// if (permissionState != PackageManager.PERMISSION_GRANTED) { +// this.activity?.let { it1 -> ActivityCompat.requestPermissions(it1, arrayOf(Manifest.permission.FOREGROUND_SERVICE), 1) } +// } +// } +// val intentNoti = Intent(context, NotificationService::class.java) +// if (Build.VERSION.SDK_INT >= 26) { +// context.startForegroundService(intentNoti) +// } else { +// context.startService(intentNoti) +// } +// executed = true +// } if (sharedPreferences.getBoolean("alive_media", false)) { val intentMedia = Intent(context, MediaService::class.java) context.startService(intentMedia) @@ -349,37 +421,25 @@ class FirstFragment : Fragment() { @OptIn(DelicateCoroutinesApi::class) private fun execShell(cmd: String, recordLog: Boolean) { GlobalScope.launch(context = Dispatchers.IO) { - val process = Runtime.getRuntime().exec("sh") + val process = ProcessBuilder("sh").redirectErrorStream(true).directory(context?.filesDir?.absolutePath?.let { + File( + it + ) + }).start() val os = process.outputStream os.write("cd ${context?.filesDir?.absolutePath}&&".toByteArray()) os.write(cmd.toByteArray()) os.flush() os.close() -// Thread.sleep(3000) -// val data = process.inputStream.readBytes() -// val error = process.errorStream.readBytes() -// if (data.isNotEmpty()) { -// shellLogs += String(data) -// shellLogs += "\n" -// } else { -// shellLogs += String(error) -// shellLogs += "\n" -// } -// Log.i("ExecShell", shellLogs) -// withContext(Dispatchers.Main) { -// binding.textviewFirst.text = shellLogs -// } + val data = process.inputStream + val ir = BufferedReader(InputStreamReader(data)) while (recordLog) { - val data = process.inputStream.readBytes() - val error = process.errorStream.readBytes() - if (data.isNotEmpty()) { - shellLogs += String(data) - shellLogs += "\n" - } else { - shellLogs += String(error) + var line = ir.readLine() + while (line != null) { + shellLogs += line shellLogs += "\n" + line = ir.readLine() } - Log.i("ExecShell", shellLogs) Thread.sleep(1000) } } @@ -392,11 +452,6 @@ class FirstFragment : Fragment() { private fun delete(delFile: String): Boolean { val file = File(delFile) return if (!file.exists()) { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除文件失败:" + delFile + "不存在!", -// Toast.LENGTH_SHORT -// ).show() false } else { if (file.isFile) deleteSingleFile(delFile) else deleteDirectory(delFile) @@ -418,19 +473,9 @@ class FirstFragment : Fragment() { ) true } else { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除单个文件" + `filePath$Name` + "失败!", -// Toast.LENGTH_SHORT -// ).show() false } } else { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除单个文件失败:" + `filePath$Name` + "不存在!", -// Toast.LENGTH_SHORT -// ).show() false } } @@ -446,11 +491,6 @@ class FirstFragment : Fragment() { val dirFile = File(filePath) // 如果dir对应的文件不存在,或者不是一个目录,则退出 if (!dirFile.exists() || !dirFile.isDirectory) { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除目录失败:" + filePath + "不存在!", -// Toast.LENGTH_SHORT -// ).show() return false } var flag = true @@ -469,11 +509,6 @@ class FirstFragment : Fragment() { } } if (!flag) { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除目录失败!", -// Toast.LENGTH_SHORT -// ).show() return false } // 删除当前目录 @@ -481,11 +516,6 @@ class FirstFragment : Fragment() { Log.e("--Method--", "Copy_Delete.deleteDirectory: 删除目录" + filePath + "成功!") true } else { -// Toast.makeText( -// ApplicationProvider.getApplicationContext(), -// "删除目录:" + filePath + "失败!", -// Toast.LENGTH_SHORT -// ).show() false } } diff --git a/app/src/main/java/com/sealdice/dice/MediaService.kt b/app/src/main/java/com/sealdice/dice/MediaService.kt index ec30f4d..d7764df 100644 --- a/app/src/main/java/com/sealdice/dice/MediaService.kt +++ b/app/src/main/java/com/sealdice/dice/MediaService.kt @@ -13,10 +13,10 @@ class MediaService : Service() { // declaring object of MediaPlayer private lateinit var player: MediaPlayer - // execution of service will start // on calling this method - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onCreate() { + super.onCreate() val base64 = "AAAAGGZ0eXBtcDQyAAAAAG1wNDFpc29tAAAAKHV1aWRcpwj7Mo5CBahhZQ7KCpWWAAAADDEwLjAuMTgzNjMuMAAAAG5tZGF0AAAAAAAAABAnDEMgBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBDSX5AAAAAAAAB9Pp9Pp9Pp9Pp9Pp9Pp9Pp9Pp9Pp9Pp9Pp9AAAC/m1vb3YAAABsbXZoZAAAAADeilCc3opQnAAAu4AAAAIRAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAHBdHJhawAAAFx0a2hkAAAAAd6KUJzeilCcAAAAAgAAAAAAAAIRAAAAAAAAAAAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAABXW1kaWEAAAAgbWRoZAAAAADeilCc3opQnAAAu4AAAAIRVcQAAAAAAC1oZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU291bmRIYW5kbGVyAAAAAQhtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAMxzdGJsAAAAZHN0c2QAAAAAAAAAAQAAAFRtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAAu4AAAAAAADBlc2RzAAAAAAOAgIAfAAAABICAgBRAFQAGAAACM2gAAjNoBYCAgAIRkAYBAgAAABhzdHRzAAAAAAAAAAEAAAABAAACEQAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAYc3RzegAAAAAAAAAAAAAAAQAAAF4AAAAUc3RjbwAAAAAAAAABAAAAUAAAAMl1ZHRhAAAAkG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXIAAAAAAAAAAAAAAAAAAAAAY2lsc3QAAAAeqW5hbQAAABZkYXRhAAAAAQAAAADlvZXpn7MAAAAcqWRheQAAABRkYXRhAAAAAQAAAAAyMDIyAAAAIWFBUlQAAAAZZGF0YQAAAAEAAAAA5b2V6Z+z5py6AAAAMVh0cmEAAAApAAAAD1dNL0VuY29kaW5nVGltZQAAAAEAAAAOABUA2rD/dVfYAQ==" @@ -37,6 +37,9 @@ class MediaService : Service() { // value as true to play // the audio on loop player.isLooping = true + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // starting the process player.start() diff --git a/app/src/main/java/com/sealdice/dice/NotificationActivity.kt b/app/src/main/java/com/sealdice/dice/NotificationActivity.kt index 99a5b7d..3146ef7 100644 --- a/app/src/main/java/com/sealdice/dice/NotificationActivity.kt +++ b/app/src/main/java/com/sealdice/dice/NotificationActivity.kt @@ -17,6 +17,7 @@ class NotificationActivity : AppCompatActivity() { return when (item.itemId) { android.R.id.home -> { onBackPressedDispatcher.onBackPressed() + finish() true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/com/sealdice/dice/NotificationService.kt b/app/src/main/java/com/sealdice/dice/NotificationService.kt deleted file mode 100644 index e6d3cd3..0000000 --- a/app/src/main/java/com/sealdice/dice/NotificationService.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.sealdice.dice - -import android.app.* -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.IBinder - - -class NotificationService : Service(){ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val ns: String = Context.NOTIFICATION_SERVICE - val mNotificationManager = getSystemService(ns) as NotificationManager - val notificationChannel = NotificationChannel("sealdice","SealDice", NotificationManager.IMPORTANCE_HIGH) - mNotificationManager.createNotificationChannel(notificationChannel) - val pendingIntent = PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, NotificationActivity::class.java), PendingIntent.FLAG_MUTABLE) - val notification: Notification = Notification.Builder(this,"sealdice") - .setContentTitle("SealDice is running") - .setSmallIcon(R.drawable.ic_launcher_foreground) - .setContentIntent(pendingIntent) - .build() - startForeground(1, notification) - return START_STICKY - } - - override fun onBind(p0: Intent?): IBinder? { - return null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sealdice/dice/ProcessService.kt b/app/src/main/java/com/sealdice/dice/ProcessService.kt new file mode 100644 index 0000000..72e0715 --- /dev/null +++ b/app/src/main/java/com/sealdice/dice/ProcessService.kt @@ -0,0 +1,104 @@ +package com.sealdice.dice + +import android.app.* +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.os.Build +import android.os.IBinder +import androidx.preference.PreferenceManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader + + +class ProcessService : Service(){ + private val binder = MyBinder() + private var processBuilder: ProcessBuilder = ProcessBuilder("sh").redirectErrorStream(true) + private lateinit var process: Process + private var isRunning = false + private var shellLogs = "" + inner class MyBinder : Binder() { + fun getService(): ProcessService = this@ProcessService + } + fun getShellLogs(): String { + return shellLogs + } + fun stopProcess() { + isRunning = false + Thread.sleep(5) + process.destroy() + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (Build.VERSION.SDK_INT >= 26) { + val ns: String = Context.NOTIFICATION_SERVICE + val mNotificationManager = getSystemService(ns) as NotificationManager + val notificationChannel = NotificationChannel("sealdice","SealDice", NotificationManager.IMPORTANCE_HIGH) + mNotificationManager.createNotificationChannel(notificationChannel) + val pendingIntent = PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, NotificationActivity::class.java), PendingIntent.FLAG_MUTABLE) + val notification: Notification = Notification.Builder(this,"sealdice") + .setContentTitle("SealDice is running") + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentIntent(pendingIntent) + .build() + startForeground(1, notification) + } + else { + val pendingIntent = PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, NotificationActivity::class.java), PendingIntent.FLAG_MUTABLE) + val notification: Notification = Notification.Builder(this) + .setContentTitle("SealDice is running") + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentIntent(pendingIntent) + .build() + startForeground(1, notification) + } + if (!isRunning) { + isRunning = true + process = processBuilder.directory(File(this.filesDir.absolutePath)).start() + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + val args = sharedPreferences.getString("launch_args", "") + val cmd = "cd sealdice&&./sealdice-core $args" + GlobalScope.launch(context = Dispatchers.IO) { + val os = process.outputStream + os.write(cmd.toByteArray()) + os.flush() + os.close() + val data = process.inputStream + val ir = BufferedReader(InputStreamReader(data)) + while (isRunning) { + var line: String? + try { + line = ir.readLine() + } catch (e: Exception) { + break + } + while (line != null && isRunning) { + shellLogs += line + shellLogs += "\n" + try { + line = ir.readLine() + } catch (e: Exception) { + break + } + } + Thread.sleep(1000) + } + } + } + return START_STICKY + } + fun isRunning(): Boolean { + return isRunning + } + override fun onBind(p0: Intent?): IBinder { + return binder + } + override fun onDestroy() { + super.onDestroy() + stopProcess() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sealdice/dice/SettingsActivity.kt b/app/src/main/java/com/sealdice/dice/SettingsActivity.kt index da1c14d..a482abb 100644 --- a/app/src/main/java/com/sealdice/dice/SettingsActivity.kt +++ b/app/src/main/java/com/sealdice/dice/SettingsActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity + class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -19,6 +20,7 @@ class SettingsActivity : AppCompatActivity() { return when (item.itemId) { android.R.id.home -> { onBackPressedDispatcher.onBackPressed() + finish() true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/com/sealdice/dice/SettingsFragment.kt b/app/src/main/java/com/sealdice/dice/SettingsFragment.kt index 9d79d43..80c74d8 100644 --- a/app/src/main/java/com/sealdice/dice/SettingsFragment.kt +++ b/app/src/main/java/com/sealdice/dice/SettingsFragment.kt @@ -7,6 +7,7 @@ import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import androidx.preference.PreferenceFragmentCompat import androidx.preference.SeekBarPreference +import androidx.preference.SwitchPreferenceCompat class SettingsFragment : PreferenceFragmentCompat() { @@ -15,5 +16,7 @@ class SettingsFragment : PreferenceFragmentCompat() { setPreferencesFromResource(R.xml.prefrences, rootKey) sharedPreferences = preferenceScreen.sharedPreferences!! val mySeekBarPreference : SeekBarPreference? = findPreference("launch_waiting_time") +// val switchPreference: SwitchPreferenceCompat = findPreference("my_switch_preference") +// switchPreference.setTitleTextColor(resources.getColor(android.R.color.my_color)) } } diff --git a/app/src/main/java/com/sealdice/dice/UpdateService.kt b/app/src/main/java/com/sealdice/dice/UpdateService.kt index dd3b737..8992e15 100644 --- a/app/src/main/java/com/sealdice/dice/UpdateService.kt +++ b/app/src/main/java/com/sealdice/dice/UpdateService.kt @@ -6,9 +6,11 @@ import android.app.PendingIntent import android.app.Service import android.content.Intent import android.net.Uri +import android.os.Build import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -21,6 +23,7 @@ class UpdateService : Service() { private val UPDATE_URL = "https://get.sealdice.com/seal/version/android" private val NOTIFICATION_ID = 2 + @OptIn(DelicateCoroutinesApi::class) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { GlobalScope.launch(context = Dispatchers.IO) { checkForUpdates() @@ -61,14 +64,33 @@ class UpdateService : Service() { private fun showUpdateNotification(version: String) { // Create a notification channel for Android Oreo and higher val notificationManager = getSystemService(NotificationManager::class.java) - val channel = NotificationChannel( - "update_channel", - "Update Channel", - NotificationManager.IMPORTANCE_DEFAULT - ) - notificationManager.createNotificationChannel(channel) + if (Build.VERSION.SDK_INT >= 26) { + val channel = NotificationChannel( + "update_channel", + "Update Channel", + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(channel) + } else { + val builder = NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle("SealDice 检测到更新") + .setContentText("点击此处下载新版本 $version") + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + // Create a pending intent for the update dialog + val uri = Uri.parse("https://d.catlevel.com/seal/android/latest") + val intent = Intent() + intent.action = "android.intent.action.VIEW" + intent.data = uri + val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + builder.setContentIntent(pendingIntent) + // Show the notification + notificationManager.notify(NOTIFICATION_ID, builder.build()) + return + } // Create a notification builder - val builder = NotificationCompat.Builder(this, "update_channel") + val builder = NotificationCompat.Builder(this,"update_channel") .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("SealDice 检测到更新") .setContentText("点击此处下载新版本 $version") @@ -85,6 +107,4 @@ class UpdateService : Service() { notificationManager.notify(NOTIFICATION_ID, builder.build()) } - // ... - } \ No newline at end of file diff --git a/app/src/main/java/com/sealdice/dice/WakeLockService.kt b/app/src/main/java/com/sealdice/dice/WakeLockService.kt index e8258f5..e529df7 100644 --- a/app/src/main/java/com/sealdice/dice/WakeLockService.kt +++ b/app/src/main/java/com/sealdice/dice/WakeLockService.kt @@ -1,5 +1,6 @@ package com.sealdice.dice +import android.annotation.SuppressLint import android.app.Service import android.content.Context import android.content.Intent @@ -7,30 +8,17 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest -import android.os.Handler import android.os.IBinder -import android.os.Looper import android.os.PowerManager class WakeLockService : Service(){ - private lateinit var wakeLockHelper: WakeLockHelper - private val handler = Handler(Looper.getMainLooper()) - - private val task = object : Runnable { - override fun run() { - wakeLockHelper.restartWakeLock(0, 0) - handler.postDelayed(this, 300000) // 每隔5分钟执行一次 - } - } - - override fun onCreate() { - super.onCreate() - wakeLockHelper = WakeLockHelper(this) - wakeLockHelper.acquireWakeLock() - } - + @SuppressLint("InvalidWakeLockTag", "WakelockTimeout") override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - handler.postDelayed(task, 300000) // 开始执行任务 + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LocationManagerService") + wakeLock.acquire() + +// Keep the network connection active using the ConnectivityManager class val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val networkRequest = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) @@ -45,40 +33,7 @@ class WakeLockService : Service(){ connectivityManager.registerNetworkCallback(networkRequest, networkCallback) return START_STICKY } - - override fun onDestroy() { - super.onDestroy() - handler.removeCallbacks(task) - wakeLockHelper.releaseWakeLock() - } - override fun onBind(intent: Intent?): IBinder? { return null } - - class WakeLockHelper(private val context: Context) { - private var wakeLock: PowerManager.WakeLock? = null - - fun acquireWakeLock() { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SealDice::SealDiceWakelockTag") - wakeLock?.acquire(10*60*1000L /*10 minutes*/) - } - - fun releaseWakeLock() { - wakeLock?.let { - if (it.isHeld) { - it.release() - } - } - wakeLock = null - } - - fun restartWakeLock(releaseDelayMillis: Long, acquireDelayMillis: Long) { - releaseWakeLock() - Thread.sleep(releaseDelayMillis) - acquireWakeLock() - Thread.sleep(acquireDelayMillis) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/sealdice/dice/WebviewActivity.kt b/app/src/main/java/com/sealdice/dice/WebviewActivity.kt index b53c2d1..6c27f85 100644 --- a/app/src/main/java/com/sealdice/dice/WebviewActivity.kt +++ b/app/src/main/java/com/sealdice/dice/WebviewActivity.kt @@ -1,12 +1,14 @@ package com.sealdice.dice import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.KeyEvent import android.view.KeyEvent.KEYCODE_BACK import android.view.MenuItem +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat.startActivity import com.tencent.smtt.export.external.interfaces.SslError @@ -129,6 +131,16 @@ class WebViewActivity : AppCompatActivity() { webSettings.domStorageEnabled = true val url = intent.getStringExtra("url") webView.webViewClient = object : WebViewClient() { + override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) { + super.onReceivedError(view, errorCode, description, failingUrl) + val alertDialogBuilder = AlertDialog.Builder( + this@WebViewActivity, R.style.Theme_Mshell_DialogOverlay + ) + alertDialogBuilder.setTitle("提示") + alertDialogBuilder.setMessage("加载ui失败,请点返回键根据日志内信息进行处理") + alertDialogBuilder.setPositiveButton("确定") { _: DialogInterface, _: Int ->} + alertDialogBuilder.create().show() + } override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? @@ -154,6 +166,7 @@ class WebViewActivity : AppCompatActivity() { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KEYCODE_BACK && mWebView.canGoBack()) { mWebView.goBack() + finish() return true } return super.onKeyDown(keyCode, event) diff --git a/app/src/main/res/layout/danger_preference_layout.xml b/app/src/main/res/layout/danger_preference_layout.xml new file mode 100644 index 0000000..ce3d737 --- /dev/null +++ b/app/src/main/res/layout/danger_preference_layout.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 8800fc7..bf1ca0c 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -27,6 +27,14 @@ +