Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions android/app/src/main/kotlin/com/sipeed/picoclaw/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,79 @@
package com.sipeed.picoclaw

import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {
companion object {
private const val TAG = "MainActivity"
private const val PERMISSION_REQUEST_CODE = 1001
}

private var methodChannel: PicoClawMethodChannel? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
logIncomingIntent(intent)
requestRequiredPermissions()
}

/**
* Request runtime permissions that the service needs.
*
* - API 23–29: WRITE_EXTERNAL_STORAGE / READ_EXTERNAL_STORAGE are required
* to write workspace files to shared storage.
* - API 30+: MANAGE_EXTERNAL_STORAGE is handled in onResume() instead.
* - READ_PHONE_STATE: required by Umeng analytics.
*
* On API < 23 all of these are granted at install time, so no prompt.
*/
private fun requestRequiredPermissions() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
// API 30+ uses MANAGE_EXTERNAL_STORAGE (handled in onResume).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) return

val needed = mutableListOf<String>()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
// WRITE_EXTERNAL_STORAGE is the only way to write to shared
// storage on API 23–29.
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) needed.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) needed.add(android.Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED
) needed.add(android.Manifest.permission.READ_PHONE_STATE)

if (needed.isNotEmpty()) {
ActivityCompat.requestPermissions(this, needed.toTypedArray(), PERMISSION_REQUEST_CODE)
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
for (i in permissions.indices) {
val granted = grantResults.getOrElse(i) { PackageManager.PERMISSION_GRANTED }
Log.d(TAG, "Permission ${permissions[i]}: ${if (granted == PackageManager.PERMISSION_GRANTED) "GRANTED" else "DENIED"}")
}
}
}

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
Expand Down
12 changes: 10 additions & 2 deletions android/app/src/main/kotlin/com/sipeed/picoclaw/PicoClawApp.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sipeed.picoclaw

import android.app.NotificationChannel
import android.os.Build
import android.app.NotificationManager
import io.flutter.app.FlutterApplication

Expand All @@ -18,7 +18,15 @@ class PicoClawApp : FlutterApplication() {
}

private fun createNotificationChannel() {
val channel = NotificationChannel(
// NotificationChannel is only available on API 26 (Android 8.0)+.
// On API 25 and below (e.g. Android 7.x devices), skip channel
// creation entirely — the foreground service still works, it just
// won't have a dedicated notification channel.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return
}

val channel = android.app.NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.sipeed.picoclaw.PicoClawApp
import com.sipeed.picoclaw.MainActivity
import java.io.BufferedReader
Expand Down Expand Up @@ -55,7 +56,9 @@ class PicoClawService : Service() {
action = ACTION_START
putExtra(EXTRA_PUBLIC_MODE, publicMode)
}
context.startForegroundService(intent)
// ContextCompat.startForegroundService calls startService on API < 26,
// automatically upgrading to startForegroundService on API 26+.
ContextCompat.startForegroundService(context, intent)
}

fun stop(context: Context) {
Expand Down Expand Up @@ -623,7 +626,10 @@ class PicoClawService : Service() {
thread.start()
thread.join(10_000)

if (proc.isAlive) {
// Use thread.isAlive() in place of Process.isAlive() which is
// only available on API 26+. After join() timed out, the
// wait-thread being alive means the process is still running.
if (thread.isAlive()) {
Log.w(TAG, "Force killing web service process")
proc.destroyForcibly()
}
Expand Down