From ecfc64db2683557b5049e6c89d696987abe44f38 Mon Sep 17 00:00:00 2001
From: huyang <1184394624@qq.com>
Date: Tue, 20 Jan 2026 16:09:54 +0800
Subject: [PATCH] System upgrade: download a deb into sdcard/download and send
a http request to install
---
app/build.gradle | 9 +-
app/src/main/AndroidManifest.xml | 26 +
.../boringdroid/systemui/DownloadInfo.aidl | 3 +
.../systemui/IDownloadService.aidl | 18 +
.../systemui/InterfaceDownloadCallback.aidl | 10 +
.../boringdroid/systemui/DownloadCallback.kt | 15 +
.../com/boringdroid/systemui/DownloadInfo.kt | 126 ++++
.../boringdroid/systemui/DownloadManager.kt | 90 +++
.../boringdroid/systemui/DownloadService.kt | 546 ++++++++++++++++++
.../boringdroid/systemui/SystemUIOverlay.kt | 27 +-
.../systemui/adapter/DockAppAdapter.kt | 2 +-
.../adapter/SlideNotificationAdapter.kt | 23 +-
.../systemui/data/SystemDataHolder.kt | 33 ++
.../systemui/data/UpdateResponse.kt | 14 +
.../systemui/data/VersionCheckResponse.kt | 17 +
.../systemui/receiver/UpdateActionReceiver.kt | 27 +
.../boringdroid/systemui/utils/DeviceUtils.kt | 53 ++
.../systemui/utils/ImageUtils.java | 2 +-
.../boringdroid/systemui/utils/SPUtils.java | 8 +-
.../boringdroid/systemui/view/AboutWindow.kt | 470 +++++++++++++++
.../systemui/view/AbsTopPopWindow.kt | 3 +
.../systemui/view/AppOverviewWindow.kt | 2 +-
.../view/LoadedDockContextRecycleView.kt | 2 +-
.../view/LoadedOverviewRecycleView.kt | 2 +-
.../systemui/view/LoadedSearchRecycleView.kt | 2 +-
.../boringdroid/systemui/view/TopBarLayout.kt | 3 +
.../systemui/view/TopBarNotificationWindow.kt | 45 +-
.../systemui/view/TopBarPowerWindow.kt | 62 +-
.../res/layout/layout_notification_item.xml | 4 +-
.../main/res/layout/window_topbar_about.xml | 122 ++++
app/src/main/res/values-zh-rCN/strings.xml | 11 +-
app/src/main/res/values/strings.xml | 11 +-
app/src/main/res/xml/file_paths.xml | 12 +
33 files changed, 1714 insertions(+), 86 deletions(-)
create mode 100644 app/src/main/aidl/com/boringdroid/systemui/DownloadInfo.aidl
create mode 100644 app/src/main/aidl/com/boringdroid/systemui/IDownloadService.aidl
create mode 100644 app/src/main/aidl/com/boringdroid/systemui/InterfaceDownloadCallback.aidl
create mode 100644 app/src/main/java/com/boringdroid/systemui/DownloadCallback.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/DownloadInfo.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/DownloadManager.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/DownloadService.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/data/SystemDataHolder.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/data/UpdateResponse.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/data/VersionCheckResponse.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/receiver/UpdateActionReceiver.kt
create mode 100644 app/src/main/java/com/boringdroid/systemui/view/AboutWindow.kt
create mode 100644 app/src/main/res/xml/file_paths.xml
diff --git a/app/build.gradle b/app/build.gradle
index 129edef0..eff816e8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -35,8 +35,8 @@ android {
applicationId "com.boringdroid.systemui"
minSdkVersion 30
targetSdkVersion 34
- versionCode 153
- versionName "1.5.3"
+ versionCode 154
+ versionName "1.5.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -152,5 +152,10 @@ dependencies {
implementation 'com.github.hackware1993:MagicIndicator:1.7.0' // for androidx
implementation 'com.belerweb:pinyin4j:2.5.1'
+ implementation("androidx.work:work-runtime-ktx:2.9.0")
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
+
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1c794ce4..7bde355d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,6 +31,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/aidl/com/boringdroid/systemui/DownloadInfo.aidl b/app/src/main/aidl/com/boringdroid/systemui/DownloadInfo.aidl
new file mode 100644
index 00000000..99ab7bd7
--- /dev/null
+++ b/app/src/main/aidl/com/boringdroid/systemui/DownloadInfo.aidl
@@ -0,0 +1,3 @@
+package com.boringdroid.systemui;
+
+parcelable DownloadInfo;
\ No newline at end of file
diff --git a/app/src/main/aidl/com/boringdroid/systemui/IDownloadService.aidl b/app/src/main/aidl/com/boringdroid/systemui/IDownloadService.aidl
new file mode 100644
index 00000000..1f547472
--- /dev/null
+++ b/app/src/main/aidl/com/boringdroid/systemui/IDownloadService.aidl
@@ -0,0 +1,18 @@
+// IDownloadService.aidl
+package com.boringdroid.systemui;
+
+
+import com.boringdroid.systemui.DownloadInfo;
+import com.boringdroid.systemui.InterfaceDownloadCallback;
+
+interface IDownloadService {
+ String startDownload(String url, String fileName);
+ void pauseDownload(String downloadId);
+ void resumeDownload(String downloadId);
+ void cancelDownload(String downloadId);
+ DownloadInfo getDownloadInfo(String downloadId);
+ List getAllDownloads();
+ void registerCallback(InterfaceDownloadCallback callback);
+ void unregisterCallback(InterfaceDownloadCallback callback);
+}
+
diff --git a/app/src/main/aidl/com/boringdroid/systemui/InterfaceDownloadCallback.aidl b/app/src/main/aidl/com/boringdroid/systemui/InterfaceDownloadCallback.aidl
new file mode 100644
index 00000000..268d4ded
--- /dev/null
+++ b/app/src/main/aidl/com/boringdroid/systemui/InterfaceDownloadCallback.aidl
@@ -0,0 +1,10 @@
+// InterfaceDownloadCallback.aidl
+package com.boringdroid.systemui;
+
+import com.boringdroid.systemui.DownloadInfo;
+
+interface InterfaceDownloadCallback {
+ void onDownloadProgress(in DownloadInfo info);
+ void onDownloadComplete(in DownloadInfo info);
+ void onDownloadFailed(in DownloadInfo info, String error);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/DownloadCallback.kt b/app/src/main/java/com/boringdroid/systemui/DownloadCallback.kt
new file mode 100644
index 00000000..61dc8c5c
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/DownloadCallback.kt
@@ -0,0 +1,15 @@
+package com.boringdroid.systemui
+
+abstract class DownloadCallback : InterfaceDownloadCallback {
+ override fun onDownloadProgress(downloadInfo: DownloadInfo) {
+ // 子类实现
+ }
+
+ override fun onDownloadComplete(downloadInfo: DownloadInfo) {
+ // 子类实现
+ }
+
+ override fun onDownloadFailed(downloadInfo: DownloadInfo, error: String) {
+ // 子类实现
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/DownloadInfo.kt b/app/src/main/java/com/boringdroid/systemui/DownloadInfo.kt
new file mode 100644
index 00000000..53722038
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/DownloadInfo.kt
@@ -0,0 +1,126 @@
+package com.boringdroid.systemui
+
+
+import android.os.Parcel
+import android.os.Parcelable
+
+class DownloadInfo : Parcelable {
+
+ companion object {
+ const val STATUS_PENDING:Int = 0
+ const val STATUS_DOWNLOADING:Int = 1
+ const val STATUS_PAUSED:Int = 2
+ const val STATUS_COMPLETED:Int = 3
+ const val STATUS_FAILED:Int = 4
+ const val STATUS_CANCELLED:Int = 5
+
+ @JvmField
+ val CREATOR = object : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): DownloadInfo {
+ return DownloadInfo(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ var url: String = ""
+ var fileName: String? = null
+ var downloadId: String = System.currentTimeMillis().toString()
+ var totalSize: Long = 0L
+ var downloadedSize: Long = 0L
+ var status: Int = STATUS_PENDING
+ var savePath: String = ""
+ var errorMessage: String? = null
+
+ constructor()
+
+ constructor(url: String, fileName: String? = null) {
+ this.url = url
+ this.fileName = fileName
+ }
+
+ constructor(url: String, fileName: String? = null, downloadId:String, savePath:String, status:Int) {
+ this.url = url
+ this.fileName = fileName
+ this.downloadId = downloadId
+ this.savePath = savePath
+ this.status = status
+
+ }
+
+
+ constructor(parcel: Parcel) {
+ url = parcel.readString() ?: ""
+ fileName = parcel.readString()
+ downloadId = parcel.readString() ?: System.currentTimeMillis().toString()
+ totalSize = parcel.readLong()
+ downloadedSize = parcel.readLong()
+ status = parcel.readInt()
+ savePath = parcel.readString() ?: ""
+ errorMessage = parcel.readString()
+ }
+
+ val progress: Int
+ get() {
+ if (totalSize <= 0) return 0
+ return ((downloadedSize * 100) / totalSize).toInt()
+ }
+
+ fun getFormattedProgress(): String {
+ val downloadedMB = downloadedSize / 1024.0 / 1024.0
+ val totalMB = totalSize / 1024.0 / 1024.0
+ return if (totalSize > 0) {
+ String.format("%.1fMB / %.1fMB (%d%%)", downloadedMB, totalMB, progress)
+ } else {
+ String.format("%.1fMB", downloadedMB)
+ }
+ }
+
+ fun getStatusText(): String {
+ return when (status) {
+ STATUS_DOWNLOADING -> "下载中"
+ STATUS_PAUSED -> "已暂停"
+ STATUS_COMPLETED -> "已完成"
+ STATUS_FAILED -> "失败"
+ STATUS_CANCELLED -> "已取消"
+ else -> "等待中"
+ }
+ }
+
+ fun isDownloading(): Boolean = status == STATUS_DOWNLOADING
+ fun isPaused(): Boolean = status == STATUS_PAUSED
+ fun isCompleted(): Boolean = status == STATUS_COMPLETED
+ fun isFailed(): Boolean = status == STATUS_FAILED
+ fun isCancelled(): Boolean = status == STATUS_CANCELLED
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(url)
+ parcel.writeString(fileName)
+ parcel.writeString(downloadId)
+ parcel.writeLong(totalSize)
+ parcel.writeLong(downloadedSize)
+ parcel.writeInt(status)
+ parcel.writeString(savePath)
+ parcel.writeString(errorMessage)
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun toString(): String {
+ return "DownloadInfo(url='$url', fileName=$fileName, downloadId='$downloadId', " +
+ "totalSize=$totalSize, downloadedSize=$downloadedSize, status=$status, " +
+ "savePath='$savePath', errorMessage=$errorMessage), progress=$progress"
+ }
+}
+
+sealed class DownloadStatus(val value: Int) {
+ val Pending:Int = 0
+ val DOWNLOADING:Int = 1
+ val Paused:Int = 2
+ val Completed:Int = 3
+ val Failed:Int = 4
+ val Cancelled:Int = 5
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/DownloadManager.kt b/app/src/main/java/com/boringdroid/systemui/DownloadManager.kt
new file mode 100644
index 00000000..ab8fc759
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/DownloadManager.kt
@@ -0,0 +1,90 @@
+package com.boringdroid.systemui
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+
+object DownloadManager {
+
+ private var downloadService: DownloadService? = null
+ private var isBound = false
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ val binder = service as DownloadService.DownloadBinder
+ downloadService = binder.getService()
+ isBound = true
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ downloadService = null
+ isBound = false
+ }
+ }
+
+ fun bindService(context: Context) {
+ if (!isBound) {
+ val intent = Intent(context, DownloadService::class.java)
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+ }
+
+ fun unbindService(context: Context) {
+ if (isBound) {
+ context.unbindService(serviceConnection)
+ isBound = false
+ downloadService = null
+ }
+ }
+
+ fun startDownload(context: Context, url: String, fileName: String? = null): String? {
+ return if (isBound) {
+ downloadService?.startDownload(url, fileName)
+ } else {
+ DownloadService.startService(context, url, fileName)
+ null
+ }
+ }
+
+ fun pauseDownload(context: Context, downloadId: String) {
+ if (isBound) {
+ downloadService?.pauseDownload(downloadId)
+ } else {
+ DownloadService.pauseDownload(context, downloadId)
+ }
+ }
+
+ fun resumeDownload(context: Context, downloadId: String) {
+ if (isBound) {
+ downloadService?.resumeDownload(downloadId)
+ } else {
+ DownloadService.resumeDownload(context, downloadId)
+ }
+ }
+
+ fun cancelDownload(context: Context, downloadId: String) {
+ if (isBound) {
+ downloadService?.cancelDownload(downloadId)
+ } else {
+ DownloadService.cancelDownload(context, downloadId)
+ }
+ }
+
+ fun getDownloadInfo(downloadId: String): DownloadInfo? {
+ return downloadService?.getDownloadInfo(downloadId)
+ }
+
+ fun getAllDownloads(): List {
+ return downloadService?.getAllDownloads() ?: emptyList()
+ }
+
+ fun registerCallback(callback: InterfaceDownloadCallback) {
+ downloadService?.registerCallback(callback)
+ }
+
+ fun unregisterCallback(callback: InterfaceDownloadCallback) {
+ downloadService?.unregisterCallback(callback)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/DownloadService.kt b/app/src/main/java/com/boringdroid/systemui/DownloadService.kt
new file mode 100644
index 00000000..3e1ccc5f
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/DownloadService.kt
@@ -0,0 +1,546 @@
+package com.boringdroid.systemui
+
+import android.app.*
+import android.content.Context
+import android.content.Intent
+import android.os.*
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.content.FileProvider
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_CANCELLED
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_COMPLETED
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_DOWNLOADING
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_FAILED
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_PAUSED
+import kotlinx.coroutines.*
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.File
+import java.io.RandomAccessFile
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.CoroutineContext
+
+class DownloadService : Service(), CoroutineScope {
+
+ companion object {
+ private const val CHANNEL_ID = "download_service_channel"
+ private const val NOTIFICATION_ID = 1001
+ private const val ACTION_START_DOWNLOAD = "action_start_download"
+ private const val ACTION_PAUSE_DOWNLOAD = "action_pause_download"
+ private const val ACTION_RESUME_DOWNLOAD = "action_resume_download"
+ private const val ACTION_CANCEL_DOWNLOAD = "action_cancel_download"
+
+ fun startService(context: Context, url: String? = null, fileName: String? = null) {
+ val intent = Intent(context, DownloadService::class.java)
+ if (url != null) {
+ intent.action = ACTION_START_DOWNLOAD
+ intent.putExtra("url", url)
+ intent.putExtra("fileName", fileName)
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent)
+ } else {
+ context.startService(intent)
+ }
+ }
+
+ fun pauseDownload(context: Context, downloadId: String) {
+ val intent = Intent(context, DownloadService::class.java).apply {
+ action = ACTION_PAUSE_DOWNLOAD
+ putExtra("downloadId", downloadId)
+ }
+ context.startService(intent)
+ }
+
+ fun resumeDownload(context: Context, downloadId: String) {
+ val intent = Intent(context, DownloadService::class.java).apply {
+ action = ACTION_RESUME_DOWNLOAD
+ putExtra("downloadId", downloadId)
+ }
+ context.startService(intent)
+ }
+
+ fun cancelDownload(context: Context, downloadId: String) {
+ val intent = Intent(context, DownloadService::class.java).apply {
+ action = ACTION_CANCEL_DOWNLOAD
+ putExtra("downloadId", downloadId)
+ }
+ context.startService(intent)
+ }
+ }
+
+ private val TAG: String = "DownloadService"
+ private lateinit var job: Job
+ override val coroutineContext: CoroutineContext
+ get() = Dispatchers.IO + job
+
+ private val downloads = ConcurrentHashMap()
+ private val downloadJobs = ConcurrentHashMap()
+ private val callbacks = CopyOnWriteArrayList()
+
+ private val binder = object :IDownloadService.Stub() {
+ override fun startDownload(url: String, fileName: String): String? {
+ return this@DownloadService.startDownload(url, fileName)
+ }
+
+ override fun pauseDownload(downloadId: String) {
+ this@DownloadService.pauseDownload(downloadId)
+ }
+
+ override fun resumeDownload(downloadId: String) {
+ this@DownloadService.resumeDownload(downloadId)
+ }
+
+ override fun cancelDownload(downloadId: String) {
+ this@DownloadService.cancelDownload(downloadId)
+ }
+
+ override fun getDownloadInfo(downloadId: String): DownloadInfo? {
+ return this@DownloadService.getDownloadInfo(downloadId)
+ }
+
+ override fun getAllDownloads(): List? {
+ return this@DownloadService.getAllDownloads()
+ }
+
+ override fun registerCallback(callback: InterfaceDownloadCallback) {
+ this@DownloadService.registerCallback(callback)
+ }
+
+ override fun unregisterCallback(callback: InterfaceDownloadCallback) {
+ this@DownloadService.unregisterCallback(callback)
+ }
+
+ }
+
+ inner class DownloadBinder : Binder() {
+ fun getService(): DownloadService = this@DownloadService
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ job = Job()
+ createNotificationChannel()
+ }
+
+ override fun onBind(intent: Intent?): IBinder {
+ Log.d(TAG, "onBind() called with: intent = $intent")
+ return binder
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ // 启动前台服务
+ if (!isServiceStarted()) {
+ startForeground(NOTIFICATION_ID, createNotification("下载服务正在运行"))
+ }
+
+ // 处理不同的操作
+ when (intent?.action) {
+ ACTION_START_DOWNLOAD -> {
+ val url = intent.getStringExtra("url")
+ val fileName = intent.getStringExtra("fileName")
+ if (url != null) {
+ startDownloadInternal(url, fileName)
+ }
+ }
+ ACTION_PAUSE_DOWNLOAD -> {
+ val downloadId = intent.getStringExtra("downloadId")
+ if (downloadId != null) {
+ pauseDownloadInternal(downloadId)
+ }
+ }
+ ACTION_RESUME_DOWNLOAD -> {
+ val downloadId = intent.getStringExtra("downloadId")
+ if (downloadId != null) {
+ resumeDownloadInternal(downloadId)
+ }
+ }
+ ACTION_CANCEL_DOWNLOAD -> {
+ val downloadId = intent.getStringExtra("downloadId")
+ if (downloadId != null) {
+ cancelDownloadInternal(downloadId)
+ }
+ }
+ }
+
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ job.cancel()
+ stopForeground(true)
+ }
+
+ // 公共方法供绑定客户端调用
+ fun startDownload(url: String, fileName: String? = null): String {
+ return startDownloadInternal(url, fileName)
+ }
+
+ fun pauseDownload(downloadId: String) {
+ pauseDownloadInternal(downloadId)
+ }
+
+ fun resumeDownload(downloadId: String) {
+ resumeDownloadInternal(downloadId)
+ }
+
+ fun cancelDownload(downloadId: String) {
+ cancelDownloadInternal(downloadId)
+ }
+
+ fun getDownloadInfo(downloadId: String): DownloadInfo? {
+ return downloads[downloadId]
+ }
+
+ fun getAllDownloads(): List {
+ return downloads.values.toList()
+ }
+
+ fun registerCallback(callback: InterfaceDownloadCallback) {
+ if (!callbacks.contains(callback)) {
+ callbacks.add(callback)
+ }
+ }
+
+ fun unregisterCallback(callback: InterfaceDownloadCallback) {
+ callbacks.remove(callback)
+ }
+
+ private fun startDownloadInternal(url: String, fileName: String? = null): String {
+ val downloadId = System.currentTimeMillis().toString()
+ val savePath = File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
+ fileName ?: url.substringAfterLast("/")
+ ).absolutePath
+
+ val downloadInfo = DownloadInfo(
+ url = url,
+ fileName = fileName,
+ downloadId = downloadId,
+ savePath = savePath,
+ status = STATUS_DOWNLOADING
+ )
+
+ downloads[downloadId] = downloadInfo
+
+ // 开始下载
+ downloadJobs[downloadId] = launch {
+ executeDownload(downloadInfo)
+ }
+
+ return downloadId
+ }
+
+ private fun pauseDownloadInternal(downloadId: String) {
+ downloadJobs[downloadId]?.cancel()
+ Log.d(TAG, "pauseDownloadInternal() called with: downloadId = ${downloadJobs[downloadId]}")
+ downloads[downloadId]?.let { info ->
+ info.status = STATUS_PAUSED
+ notifyCallbacks { it.onDownloadProgress(info) }
+ updateNotification(info)
+ }
+ }
+
+ private fun resumeDownloadInternal(downloadId: String) {
+ val downloadInfo = downloads[downloadId] ?: return
+ downloadInfo.status = STATUS_DOWNLOADING
+ notifyCallbacks { it.onDownloadProgress(downloadInfo) }
+ updateNotification(downloadInfo)
+
+ downloadJobs[downloadId] = launch {
+ executeDownload(downloadInfo)
+ }
+ }
+
+ private fun cancelDownloadInternal(downloadId: String) {
+ downloadJobs[downloadId]?.cancel()
+ downloads[downloadId]?.let { info ->
+ info.status = STATUS_CANCELLED
+ notifyCallbacks { it.onDownloadProgress(info) }
+
+ // 删除文件
+ try {
+ File(info.savePath).delete()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ downloads.remove(downloadId)
+ downloadJobs.remove(downloadId)
+ }
+
+ // 如果没有活跃下载,停止服务
+ if (downloads.values.none { it.status == STATUS_DOWNLOADING }) {
+ stopSelf()
+ }
+ }
+
+ private suspend fun executeDownload(downloadInfo: DownloadInfo) {
+ withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "开始下载: ${downloadInfo.url}")
+
+ val client = OkHttpClient.Builder()
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .build()
+
+ // 检查文件是否存在,获取已下载大小
+ val file = File(downloadInfo.savePath)
+ val existingSize = if (file.exists()) file.length() else 0L
+
+ // 创建带Range头的请求(支持断点续传)
+ val request = Request.Builder()
+ .url(downloadInfo.url)
+ .apply {
+ if (existingSize > 0) {
+ header("Range", "bytes=$existingSize-")
+ }
+ }
+ .build()
+
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) {
+ handleDownloadError(downloadInfo, "HTTP错误: ${response.code()}")
+ return@withContext
+ }
+
+ val body = response.body() ?: run {
+ handleDownloadError(downloadInfo, "响应体为空")
+ return@withContext
+ }
+
+ // 如果是新下载,获取总大小
+ if (existingSize == 0L) {
+ downloadInfo.totalSize = body.contentLength()
+ } else {
+ // 对于断点续传,需要从Content-Range头获取总大小
+ val contentRange = response.header("Content-Range")
+ contentRange?.let {
+ val totalSize = it.substringAfterLast("/").toLongOrNull()
+ totalSize?.let { size -> downloadInfo.totalSize = size }
+ }
+ }
+
+ downloadInfo.downloadedSize = existingSize
+ notifyCallbacks { it.onDownloadProgress(downloadInfo) }
+ updateNotification(downloadInfo)
+
+ val inputStream = body.byteStream()
+ val randomAccessFile = RandomAccessFile(file, "rw")
+ randomAccessFile.seek(existingSize)
+
+ val buffer = ByteArray(8192)
+ var bytesRead: Int
+ var lastUpdateTime = System.currentTimeMillis()
+
+ try {
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
+ Log.d(TAG, "executeDownload() called with: isActive = $isActive")
+ // 检查是否被取消或暂停
+ if (!isActive) {
+ if (downloadInfo.status == STATUS_DOWNLOADING) {
+ downloadInfo.status = STATUS_PAUSED
+ }
+ return@use
+ }
+
+ randomAccessFile.write(buffer, 0, bytesRead)
+ downloadInfo.downloadedSize += bytesRead
+
+ // 限制更新频率
+ val currentTime = System.currentTimeMillis()
+ if (currentTime - lastUpdateTime > 200) {
+ lastUpdateTime = currentTime
+ notifyCallbacks { it.onDownloadProgress(downloadInfo) }
+ updateNotification(downloadInfo)
+ }
+ }
+
+ // 下载完成
+ downloadInfo.status = STATUS_COMPLETED
+ notifyCallbacks {
+ it.onDownloadProgress(downloadInfo)
+ it.onDownloadComplete(downloadInfo)
+ }
+
+ showDownloadCompleteNotification(downloadInfo)
+
+ } finally {
+ randomAccessFile.close()
+ inputStream.close()
+ }
+
+ // 清理工作
+ downloadJobs.remove(downloadInfo.downloadId)
+
+ // 如果没有其他下载,停止服务
+ if (downloads.values.none { it.status == STATUS_DOWNLOADING }) {
+ stopSelf()
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "下载失败", e)
+ handleDownloadError(downloadInfo, e.message ?: "未知错误")
+ }
+ }
+ }
+
+ private fun handleDownloadError(downloadInfo: DownloadInfo, error: String) {
+ downloadInfo.status = STATUS_FAILED
+ downloadInfo.errorMessage = error
+
+ notifyCallbacks { it.onDownloadFailed(downloadInfo, error) }
+ updateNotification(downloadInfo)
+
+ downloadJobs.remove(downloadInfo.downloadId)
+ }
+
+ private fun notifyCallbacks(action: (InterfaceDownloadCallback) -> Unit) {
+ callbacks.forEach { callback ->
+ try {
+ action(callback)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun isServiceStarted(): Boolean {
+ return try {
+ val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ manager.getRunningServices(Int.MAX_VALUE).any {
+ it.service.className == DownloadService::class.java.name
+ }
+ } catch (e: Exception) {
+ false
+ }
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ "下载服务",
+ NotificationManager.IMPORTANCE_LOW
+ ).apply {
+ description = "文件下载服务"
+ setShowBadge(false)
+ }
+
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager!!.createNotificationChannel(channel)
+ }
+ }
+
+ private fun createNotification(content: String): Notification {
+ val intent = packageManager.getLaunchIntentForPackage(packageName)
+ val pendingIntent = PendingIntent.getActivity(
+ this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ return NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("文件下载服务")
+ .setContentText(content)
+ .setSmallIcon(android.R.drawable.stat_sys_download)
+ .setContentIntent(pendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setOngoing(true)
+ .setOnlyAlertOnce(true)
+ .build()
+ }
+
+ private fun updateNotification(downloadInfo: DownloadInfo) {
+ val content = when (downloadInfo.status) {
+ STATUS_DOWNLOADING -> {
+ "${downloadInfo.fileName ?: "文件"} 下载中: ${downloadInfo.progress}%"
+ }
+ STATUS_PAUSED -> {
+ "${downloadInfo.fileName ?: "文件"} 已暂停: ${downloadInfo.progress}%"
+ }
+ STATUS_COMPLETED -> {
+ "${downloadInfo.fileName ?: "文件"} 下载完成"
+ }
+ STATUS_FAILED -> {
+ "${downloadInfo.fileName ?: "文件"} 下载失败"
+ }
+ else -> {
+ "下载服务运行中"
+ }
+ }
+
+// val notification = createNotification(content)
+// val notificationManager = getSystemService(NotificationManager::class.java)
+// notificationManager!!.notify(NOTIFICATION_ID, notification)
+ }
+
+ private fun showDownloadCompleteNotification(downloadInfo: DownloadInfo) {
+ val fileName = downloadInfo.fileName ?: "文件"
+ val channelId = "download_complete_channel"
+
+ // 创建通知渠道
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ channelId,
+ "下载完成",
+ NotificationManager.IMPORTANCE_DEFAULT
+ ).apply {
+ description = "文件下载完成通知"
+ }
+
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager!!.createNotificationChannel(channel)
+ }
+
+ // 创建打开文件的Intent
+ val file = File(downloadInfo.savePath)
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ setDataAndType(
+ FileProvider.getUriForFile(
+ this@DownloadService,
+ "${packageName}.fileprovider",
+ file
+ ),
+ getMimeType(file)
+ )
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ val pendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val notification = NotificationCompat.Builder(this, channelId)
+ .setContentTitle("下载完成")
+ .setContentText("$fileName 下载完成,点击打开")
+ .setSmallIcon(android.R.drawable.stat_sys_download_done)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .build()
+
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager!!.notify(downloadInfo.downloadId.hashCode(), notification)
+ }
+
+ private fun getMimeType(file: File): String {
+ return when (file.extension.toLowerCase()) {
+ "pdf" -> "application/pdf"
+ "jpg", "jpeg" -> "image/jpeg"
+ "png" -> "image/png"
+ "mp3" -> "audio/mpeg"
+ "mp4" -> "video/mp4"
+ "zip" -> "application/zip"
+ "txt" -> "text/plain"
+ else -> "*/*"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/SystemUIOverlay.kt b/app/src/main/java/com/boringdroid/systemui/SystemUIOverlay.kt
index 0ea863c9..f83e0458 100644
--- a/app/src/main/java/com/boringdroid/systemui/SystemUIOverlay.kt
+++ b/app/src/main/java/com/boringdroid/systemui/SystemUIOverlay.kt
@@ -32,6 +32,7 @@ import androidx.cardview.widget.CardView
import androidx.core.app.NotificationManagerCompat
import com.android.systemui.plugins.OverlayPlugin
import com.android.systemui.plugins.annotations.Requires
+import com.boringdroid.systemui.data.SystemDataHolder
import com.boringdroid.systemui.provider.AllAppsProvider
import com.boringdroid.systemui.receiver.BatteryReceiver
import com.boringdroid.systemui.receiver.DynamicReceiver
@@ -181,14 +182,15 @@ class SystemUIOverlay : OverlayPlugin, SystemStateLayout.NotificationListener, T
override fun onCreate(
sysUIContext: Context,
- pluginContext_1: Context,
+ sysPluginContext: Context,
) {
systemUIContext = sysUIContext
- pluginContext = pluginContext_1
- GlobalSystemUIContext.setGlobalSystemuiContext(systemUIContext)
+ pluginContext = sysPluginContext
+ GlobalSystemUIContext.setContext(sysUIContext)
+ SystemDataHolder.initialize(sysUIContext).initSystemData()
ImageUtils.init(sysUIContext)
initOKhttp()
- SPUtils.pluginContext = systemUIContext
+ SPUtils.pluginContext = pluginContext
navBarButtonGroupId = sysUIContext.resources.getIdentifier("ends_group", "id", "com.android.systemui")
loadCustomViewsWithInflater(pluginContext!!)
btAllAppsGroup = initializeAllAppsButton(this.pluginContext, btAllAppsGroup)
@@ -378,7 +380,7 @@ class SystemUIOverlay : OverlayPlugin, SystemStateLayout.NotificationListener, T
private fun initOKhttp() {
val logInterceptor = HttpLoggingInterceptor(HttpLog("fde-systemui"))
- logInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC)
+ logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val sOkHttpClient = OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(logInterceptor)
@@ -766,13 +768,18 @@ class SystemUIOverlay : OverlayPlugin, SystemStateLayout.NotificationListener, T
object GlobalSystemUIContext {
- private var globalSystemuiContext: Context ?= null
+ @Volatile
+ private lateinit var globalSystemUIContext: Context
- fun getGlobalSystemuiContext(): Context?{
- return globalSystemuiContext
+ fun setContext(context: Context) {
+ // 可选:只允许设置一次,避免覆盖
+ if (!::globalSystemUIContext.isInitialized) {
+ globalSystemUIContext = context.applicationContext
+ }
}
- fun setGlobalSystemuiContext(context: Context?){
- this.globalSystemuiContext = context
+ fun getContext(): Context {
+ // 如果未初始化就调用,会抛出异常(符合非空语义)
+ return globalSystemUIContext
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/adapter/DockAppAdapter.kt b/app/src/main/java/com/boringdroid/systemui/adapter/DockAppAdapter.kt
index e4fb56c7..39da4c03 100644
--- a/app/src/main/java/com/boringdroid/systemui/adapter/DockAppAdapter.kt
+++ b/app/src/main/java/com/boringdroid/systemui/adapter/DockAppAdapter.kt
@@ -99,7 +99,7 @@ class DockAppAdapter(private val context: Context) :
} else if(app.icon != null){
holder.iconIV.setImageDrawable(app.icon)
} else if(info != null && info.iconType.equals(ImageUtils.SURFFIX_PNG)){
- Glide.with(GlobalSystemUIContext.getGlobalSystemuiContext()!!)
+ Glide.with(GlobalSystemUIContext.getContext())
.load("${Utils.linuxRootPath}${info.iconPath}")
.centerCrop()
.placeholder(context.getDrawable(R.drawable.icon_menu))
diff --git a/app/src/main/java/com/boringdroid/systemui/adapter/SlideNotificationAdapter.kt b/app/src/main/java/com/boringdroid/systemui/adapter/SlideNotificationAdapter.kt
index 24f05d30..b7d8047d 100644
--- a/app/src/main/java/com/boringdroid/systemui/adapter/SlideNotificationAdapter.kt
+++ b/app/src/main/java/com/boringdroid/systemui/adapter/SlideNotificationAdapter.kt
@@ -1,11 +1,8 @@
package com.boringdroid.systemui.adapter
-import android.app.Notification
import android.app.PendingIntent.CanceledException
import android.content.Context
-import android.graphics.Color
-import android.graphics.PorterDuff
-import android.graphics.drawable.GradientDrawable
+import android.graphics.Typeface
import android.service.notification.StatusBarNotification
import android.util.Log
import android.view.LayoutInflater
@@ -21,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.boringdroid.systemui.NotificationService
import com.boringdroid.systemui.R
import com.boringdroid.systemui.data.DesktopNotification
-import com.boringdroid.systemui.utils.AppUtils
import com.boringdroid.systemui.utils.IconParserUtilities
import com.boringdroid.systemui.utils.Utils
@@ -97,10 +93,10 @@ class SlideNotificationAdapter(
if (actions != null) {
val actionLayoutParams = LinearLayout.LayoutParams(
- 0,
+ WRAP_CONTENT,
WRAP_CONTENT,
)
- actionLayoutParams.marginStart = Utils.dpToPx(context, 20)
+ actionLayoutParams.marginStart = Utils.dpToPx(context, 10)
actionLayoutParams.weight = 1f
// if (isMediaNotification(sbn)) {
// for (action in actions) {
@@ -131,10 +127,18 @@ class SlideNotificationAdapter(
actionTv.setTextColor(context.getColor(R.color.notification_text_color))
actionTv.isSingleLine = true
actionTv.text = action.title
+
+ val params = ViewGroup.LayoutParams(
+ WRAP_CONTENT,
+ WRAP_CONTENT
+ )
+ actionTv.layoutParams = params
+ actionTv.setTypeface(Typeface.DEFAULT_BOLD); // 使用系统默认加粗字体
actionTv.setOnClickListener {
try {
action.actionIntent.send()
- itemClickListener?.onItemClick(sbn, holder.root)
+// itemClickListener?.onItemClick(sbn, holder.root)
+ itemClickListener?.onItemClick(sbn, holder.root, action.title.toString())
} catch (_: CanceledException) {
}
}
@@ -283,5 +287,8 @@ class SlideNotificationAdapter(
interface OnNotificationItemClickListener {
fun onItemClick(sbn: DesktopNotification, item: View?)
+
+ fun onItemClick(sbn: DesktopNotification, item: View?, action:String)
+
fun onItemCancelClick(sbn: DesktopNotification, item: View?)
}
diff --git a/app/src/main/java/com/boringdroid/systemui/data/SystemDataHolder.kt b/app/src/main/java/com/boringdroid/systemui/data/SystemDataHolder.kt
new file mode 100644
index 00000000..69a3cafd
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/data/SystemDataHolder.kt
@@ -0,0 +1,33 @@
+package com.boringdroid.systemui.data
+
+import android.content.Context
+import com.boringdroid.systemui.utils.Utils
+
+class SystemDataHolder private constructor(val context: Context) {
+
+ var ro_openfde_version:String ?= null
+
+ companion object {
+ @Volatile
+ private var instance: SystemDataHolder? = null
+
+ fun initialize(context: Context): SystemDataHolder {
+ return instance ?: synchronized(this) {
+ instance ?: SystemDataHolder(context).also { instance = it }
+ }
+ }
+
+ fun getInstance(): SystemDataHolder {
+ return instance ?: throw IllegalStateException("SystemDataHolder not initialized")
+ }
+
+ fun clear() {
+ instance = null
+ }
+ }
+
+ fun initSystemData(){
+ ro_openfde_version = Utils.getProperty("ro.openfde.version", "2.0.1")
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/data/UpdateResponse.kt b/app/src/main/java/com/boringdroid/systemui/data/UpdateResponse.kt
new file mode 100644
index 00000000..f8468d19
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/data/UpdateResponse.kt
@@ -0,0 +1,14 @@
+package com.boringdroid.systemui.data
+
+import com.google.gson.annotations.SerializedName
+
+data class UpdateResponse(
+ @SerializedName("Code") val code: Int,
+ @SerializedName("Message") val message: String,
+ @SerializedName("Data") val data: UpdateData?
+)
+
+data class UpdateData(
+ @SerializedName("CurrentVersion") val version: String,
+ @SerializedName("Path") val path: String, // 注意:JSON 中是数字 1/0,不是布尔值
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/data/VersionCheckResponse.kt b/app/src/main/java/com/boringdroid/systemui/data/VersionCheckResponse.kt
new file mode 100644
index 00000000..63239de5
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/data/VersionCheckResponse.kt
@@ -0,0 +1,17 @@
+package com.boringdroid.systemui.data
+
+import com.google.gson.annotations.SerializedName
+
+data class VersionCheckResponse(
+ @SerializedName("Code") val code: Int,
+ @SerializedName("Message") val message: String,
+ @SerializedName("Data") val data: Data?
+)
+
+data class Data(
+ @SerializedName("Version") val version: String,
+ @SerializedName("IsNewer") val isNewer: Int, // 注意:JSON 中是数字 1/0,不是布尔值
+ @SerializedName("DownloadURL") val downloadUrl: String,
+ @SerializedName("Size") val size: String, // 注意:虽然是数字,但 JSON 中是字符串
+ @SerializedName("MD5") val md5: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/receiver/UpdateActionReceiver.kt b/app/src/main/java/com/boringdroid/systemui/receiver/UpdateActionReceiver.kt
new file mode 100644
index 00000000..6b98ae00
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/receiver/UpdateActionReceiver.kt
@@ -0,0 +1,27 @@
+package com.boringdroid.systemui.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.boringdroid.systemui.view.AboutWindow.Companion.ACTION_DEFER_UPDATE
+import com.boringdroid.systemui.view.AboutWindow.Companion.ACTION_UPDATE_NOW
+
+class UpdateActionReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ ACTION_UPDATE_NOW -> {
+ // TODO: 执行“立即更新”逻辑(如开始安装、调用系统更新服务等)
+ android.util.Log.d("UpdateActionReceiver", "User chose: Update Now")
+ }
+ ACTION_DEFER_UPDATE -> {
+ // TODO: 执行“下次开机更新”逻辑(如保存标记)
+ android.util.Log.d("UpdateActionReceiver", "User chose: Defer to Boot")
+ // 例如:保存到 SharedPreferences
+ context.getSharedPreferences("update_prefs", Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean("defer_update_on_boot", true)
+ .apply()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/utils/DeviceUtils.kt b/app/src/main/java/com/boringdroid/systemui/utils/DeviceUtils.kt
index d8a09eae..dd817084 100644
--- a/app/src/main/java/com/boringdroid/systemui/utils/DeviceUtils.kt
+++ b/app/src/main/java/com/boringdroid/systemui/utils/DeviceUtils.kt
@@ -19,6 +19,9 @@ import android.util.DisplayMetrics
import android.view.Display
import androidx.core.content.ContextCompat
import com.boringdroid.systemui.Log
+import com.boringdroid.systemui.data.UpdateResponse
+import com.boringdroid.systemui.data.VersionCheckResponse
+import com.boringdroid.systemui.view.VersionCheckCallback
import com.google.gson.Gson
import com.google.gson.JsonPrimitive
import com.google.gson.reflect.TypeToken
@@ -30,6 +33,7 @@ import java.io.*
object DeviceUtils {
+ private val TAG: String = "DeviceUtils"
const val BASIP = "127.0.0.1"
// const val BASIP = "localhost"
@@ -47,6 +51,10 @@ object DeviceUtils {
const val URL_SET_BRIGHTNESS = "/api/v1/brightness"
const val URL_DETECT_BRIGHTNESS = "/api/v1/brightness/detect"
+ const val URL_CHECK_VERSION = "/api/v1/version/check"
+ const val URL_UPDATE_VERSION = "/api/v1/version/update "
+
+
fun lockScreen(context: Context): Boolean {
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
try {
@@ -293,6 +301,51 @@ object DeviceUtils {
})
}
+ fun checkVersion(version: String?, callback: VersionCheckCallback){
+ android.util.Log.d(TAG, "checkVersion() called with: version = $version")
+ QuietOkHttp.post(BASEURL + URL_CHECK_VERSION)
+ .addParams("Version", version)
+ .setCallbackToMainUIThread(true)
+ .execute(object : JsonCallBack() {
+ override fun onFailure(call: Call, e: Exception) {
+ Log.d("fde", "onFailure() called with: call = [$call], e = [$e]")
+ }
+
+ override fun onSuccess(call: Call, response: VersionCheckResponse) {
+ callback.onCallback(response)
+ android.util.Log.d(
+ TAG,
+ "onSuccess() called with: call = $call, response = $response"
+ )
+ }
+ })
+ }
+
+ fun startInstall(version: String?, Path: String?, Policy: String?, callback: VersionCheckCallback){
+ android.util.Log.d(
+ TAG,
+ "startInstall() called with: version = $version, Path = $Path, Policy = $Policy"
+ )
+ QuietOkHttp.post(BASEURL + URL_UPDATE_VERSION)
+ .addParams("CurrentVersion", version)
+ .addParams("Path", Path)
+ .addParams("Policy", Policy)
+ .setCallbackToMainUIThread(true)
+ .execute(object : JsonCallBack() {
+ override fun onFailure(call: Call, e: Exception) {
+ Log.d("fde", "onFailure() called with: call = [$call], e = [$e]")
+ }
+
+ override fun onSuccess(call: Call, response: UpdateResponse) {
+ callback.onUpdateCallback(response)
+ android.util.Log.d(
+ TAG,
+ "onSuccess() called with: call = $call, response = $response"
+ )
+ }
+ })
+ }
+
fun gotoNetWork(context: Context, flag: String) {
val intent = Intent()
val componentName2 = ComponentName(
diff --git a/app/src/main/java/com/boringdroid/systemui/utils/ImageUtils.java b/app/src/main/java/com/boringdroid/systemui/utils/ImageUtils.java
index ddc8ba9e..c3ef890d 100644
--- a/app/src/main/java/com/boringdroid/systemui/utils/ImageUtils.java
+++ b/app/src/main/java/com/boringdroid/systemui/utils/ImageUtils.java
@@ -175,7 +175,7 @@ public static Drawable getImage(String imageStr, String iconType, String name, C
if (SURFFIX_SVG.equals(iconType) || SURFFIX_SVGZ.equals(iconType)) {
byte[] decodedData = Base64.decode(imageStr, Base64.DEFAULT);
FileOutputStream svgFile = null;
- Context globalSystemuiContext = GlobalSystemUIContext.INSTANCE.getGlobalSystemuiContext();
+ Context globalSystemuiContext = GlobalSystemUIContext.INSTANCE.getContext();
File file = new File(globalSystemuiContext.getFilesDir(), name + "_output.svg");
try {
svgFile = new FileOutputStream(file.getAbsolutePath());
diff --git a/app/src/main/java/com/boringdroid/systemui/utils/SPUtils.java b/app/src/main/java/com/boringdroid/systemui/utils/SPUtils.java
index cdacdfd2..0b280af4 100644
--- a/app/src/main/java/com/boringdroid/systemui/utils/SPUtils.java
+++ b/app/src/main/java/com/boringdroid/systemui/utils/SPUtils.java
@@ -74,13 +74,13 @@ public static int getIntUserInfo(Context context, String key) {
return shared_user_info.getInt(key, 0);
}
- public static void putIntUserInfo(Context context, String key, int values) {
+ public static void cleanUserInfo(Context context) {
SharedPreferences shared_user_info = context.getSharedPreferences(USER_INFO, context.MODE_PRIVATE);
- shared_user_info.edit().putInt(key, values).commit();
+ shared_user_info.edit().clear().commit();
}
- public static void cleanUserInfo(Context context) {
+ public static void putIntUserInfo(Context context, String key, int values) {
SharedPreferences shared_user_info = context.getSharedPreferences(USER_INFO, context.MODE_PRIVATE);
- shared_user_info.edit().clear().commit();
+ shared_user_info.edit().putInt(key, values).commit();
}
}
diff --git a/app/src/main/java/com/boringdroid/systemui/view/AboutWindow.kt b/app/src/main/java/com/boringdroid/systemui/view/AboutWindow.kt
new file mode 100644
index 00000000..2c047375
--- /dev/null
+++ b/app/src/main/java/com/boringdroid/systemui/view/AboutWindow.kt
@@ -0,0 +1,470 @@
+package com.boringdroid.systemui.view
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import com.boringdroid.systemui.DownloadInfo
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_DOWNLOADING
+import com.boringdroid.systemui.DownloadInfo.Companion.STATUS_PAUSED
+import com.boringdroid.systemui.DownloadService
+import com.boringdroid.systemui.GlobalSystemUIContext
+import com.boringdroid.systemui.IDownloadService
+import com.boringdroid.systemui.InterfaceDownloadCallback
+import com.boringdroid.systemui.R
+import com.boringdroid.systemui.data.UpdateResponse
+import com.boringdroid.systemui.data.VersionCheckResponse
+import com.boringdroid.systemui.receiver.UpdateActionReceiver
+import com.boringdroid.systemui.utils.DeviceUtils
+import com.boringdroid.systemui.utils.SPUtils
+import com.boringdroid.systemui.utils.Utils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
+
+class AboutWindow(
+ context: Context,
+ width: Int,
+ height: Int,
+ gravity: Int,
+ layoutResId: Int,
+ typeParam: Int
+) : AbsTopPopWindow(context, width, height, gravity, layoutResId, typeParam), View.OnClickListener {
+
+ companion object {
+ const val POWER_WINDOW_PADDING = 8
+ const val POWER_OUTLINE_RADIUS = 8f
+ const val POWER_OUTLINE_SHADOW = 60
+ const val TAG: String = "AboutWindow"
+ const val TIMING_NOW_INSTALL = 0
+ const val TIMING_LATER_INSTALL = 1
+ const val TIMING_NOT_YET_INSTALL = -1
+ const val ACTION_UPDATE_NOW = "com.boringdroid.systemui.ACTION_UPDATE_NOW"
+ const val ACTION_DEFER_UPDATE = "com.boringdroid.systemui.ACTION_DEFER_UPDATE"
+
+ const val NOTIFI_CHANAL_ID = 100
+
+ }
+
+ private var downloadId: String? = null
+ private var downloadService: IDownloadService? = null
+ private var isBound = false
+ private var close: View? = null
+ private var versionTv: TextView? = null
+ private var deviceTv: TextView? = null
+ private var latestedTv: TextView? = null
+ private var installNowTv: TextView? = null
+ private var installLaterTv: TextView? = null
+
+ private var updateLl: LinearLayout? = null
+ private var updateBt: Button? = null
+ private var updateNowBt: Button? = null
+ private var updateLaterBt: Button? = null
+ private var checkversionbBt: Button? = null
+ private var downloadLl: LinearLayout? = null
+ private var installLl: LinearLayout? = null
+ private var fileNameTv: TextView? = null
+ private var progressbar: ProgressBar? = null
+ private var statusBt: Button? = null
+ private var version: String? = null
+ private var path: String? = null
+ private var response: VersionCheckResponse? = null
+ val STATUS_PAUSE: Int = 0
+ val STATUS_START: Int = 1
+ private val uiScope = CoroutineScope(Dispatchers.Main)
+ var mNotificationId: Int = NOTIFI_CHANAL_ID
+ private var status: Int = STATUS_PAUSE
+
+ override fun showPopupWindow() {
+ super.showPopupWindow()
+ initView()
+ }
+
+ private fun initView() {
+ val contentView = getContentView()
+ Utils.setBackgroundBlurRadius(contentView?.findViewById(R.id.root_blur), 100, 12f)
+ close = contentView?.findViewById(R.id.close_iv)
+ versionTv = contentView?.findViewById(R.id.version_tv)
+ deviceTv = contentView?.findViewById(R.id.device_tv)
+ downloadLl = contentView?.findViewById(R.id.download_ll)
+ installLl = contentView?.findViewById(R.id.install_ll)
+ val openfde = getContext().resources.getString(R.string.openfde_version)
+ version = Utils.getProperty("ro.openfde.version", "2.0.1")
+ versionTv?.text = "$openfde $version"
+ val androidv = getContext().resources.getString(R.string.android_version)
+ val majorVersion = Utils.getMajorVersion()
+ deviceTv?.text = "$androidv $majorVersion"
+ latestedTv = contentView?.findViewById(R.id.latested_tv)
+ updateLl = contentView?.findViewById(R.id.update_ll)
+ updateBt = contentView?.findViewById(R.id.update_bt)
+ checkversionbBt = contentView?.findViewById(R.id.checkversion_bt)
+ fileNameTv = contentView?.findViewById(R.id.filename_tv)
+ progressbar = contentView?.findViewById(R.id.progressbar)
+ statusBt = contentView?.findViewById(R.id.status_bt)
+ updateNowBt = contentView?.findViewById(R.id.update_now)
+ updateLaterBt = contentView?.findViewById(R.id.update_later)
+ installNowTv = contentView?.findViewById(R.id.install_now_tv)
+ installLaterTv = contentView?.findViewById(R.id.install_later_tv)
+
+ close?.setOnClickListener(this)
+ updateBt?.setOnClickListener(this)
+ checkversionbBt?.setOnClickListener(this)
+ statusBt?.setOnClickListener(this)
+ updateNowBt?.setOnClickListener(this)
+ updateLaterBt?.setOnClickListener(this)
+
+ initUI()
+ bindService()
+ }
+
+ private fun initUI() {
+ val deb_path = SPUtils.getUserInfo(SPUtils.pluginContext, "fde_deb_path")
+ Log.d(TAG, "initUI: $deb_path")
+ if (!TextUtils.isEmpty(deb_path) && File(deb_path).exists()) {
+ val timing = SPUtils.getIntUserInfo(SPUtils.pluginContext, "timing_install")
+ showInstallUI(timing)
+ path = deb_path
+ }
+ }
+
+
+ override fun onClick(v: View?) {
+ if (v == close) {
+ dismiss()
+ } else if (v == updateBt) {
+ startDownloadDeb(response)
+ } else if (v == checkversionbBt) {
+ showCheckVersionUI()
+ } else if (v == statusBt) {
+ changeStatus()
+ } else if (v == updateNowBt) {
+ showInstallUI(TIMING_NOW_INSTALL)
+ onAction(ACTION_UPDATE_NOW)
+ } else if (v == updateLaterBt) {
+ showInstallUI(TIMING_LATER_INSTALL)
+ onAction(ACTION_DEFER_UPDATE)
+ }
+ }
+
+ private fun changeStatus() {
+ if (status == STATUS_START) {
+ downloadService?.pauseDownload(downloadId)
+ statusBt?.text = getContext().resources.getString(R.string.resume_todownload)
+ status = STATUS_PAUSE
+ } else {
+ downloadService?.resumeDownload(downloadId)
+ statusBt?.text = getContext().resources.getString(R.string.pause)
+ status = STATUS_START
+ }
+ }
+
+ private val callback = object : InterfaceDownloadCallback.Stub() {
+ override fun onDownloadProgress(info: DownloadInfo) {
+ Log.d(TAG, "onDownloadProgress() called with: info = $info")
+ showDownloadUI()
+ status = info.status
+ downloadId = info.downloadId
+ uiScope.launch {
+ fileNameTv?.text = info.fileName
+ progressbar?.progress = info.progress
+ }
+ }
+
+ override fun onDownloadComplete(info: DownloadInfo?) {
+ Log.d(TAG, "onDownloadComplete: ")
+ uiScope.launch {
+ Toast.makeText(
+ getContext(),
+ "${info?.fileName} ${context.getString(R.string.download_complete)}",
+ Toast.LENGTH_LONG
+ ).show()
+ SPUtils.putUserInfo(SPUtils.pluginContext, "fde_deb_path", info?.savePath)
+ showInstallUI()
+ createNotification()
+ }
+ path = info?.savePath
+ }
+
+ override fun onDownloadFailed(
+ info: DownloadInfo?,
+ error: String?
+ ) {
+ Log.d(TAG, "onDownloadFailed() called with: info = $info, error = $error")
+ }
+
+ }
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ downloadService = IDownloadService.Stub.asInterface(service)
+ Log.d(
+ TAG,
+ "onServiceConnected() called with: name = $name, downloadService = $downloadService"
+ )
+ downloadService?.getAllDownloads()?.let { downloads ->
+ if (downloads.isNotEmpty()) {
+ val info = downloads[0]
+ path = info.savePath
+ if (info.status == STATUS_PAUSED) {
+ Log.d(TAG, "onServiceConnected() STATUS_PAUSED ")
+ showDownloadUI()
+ fileNameTv?.text = info.fileName
+ statusBt?.text =
+ getContext().resources.getString(R.string.resume_todownload)
+ status = STATUS_PAUSE
+ downloadId = info.downloadId
+ } else if(info.status == STATUS_DOWNLOADING ){
+ Log.d(TAG, "onServiceConnected() STATUS_DOWNLOADING ")
+ showDownloadUI()
+ }
+ } else {
+ showCheckResultUI()
+ initUI()
+ }
+ }
+ downloadService?.registerCallback(callback)
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ downloadService?.unregisterCallback(callback)
+ downloadService = null
+ isBound = false
+ }
+ }
+
+
+ private fun showInstallUI(timing:Int) {
+ Log.d(TAG, "showInstallUI() called with: timing = $timing")
+ installLl?.visibility = View.VISIBLE
+ checkversionbBt?.visibility = View.GONE
+ latestedTv?.visibility = View.GONE
+ updateLl?.visibility = View.GONE
+ downloadLl?.visibility = View.GONE
+ if(timing == TIMING_NOW_INSTALL) {
+ installNowTv?.visibility = View.VISIBLE
+ installLaterTv?.visibility = View.GONE
+ updateNowBt?.visibility = View.GONE
+ updateLaterBt?.visibility = View.GONE
+ } else if(timing == TIMING_LATER_INSTALL) {
+ installNowTv?.visibility = View.GONE
+ installLaterTv?.visibility = View.VISIBLE
+ updateNowBt?.visibility = View.GONE
+ updateLaterBt?.visibility = View.GONE
+ } else if(timing == TIMING_NOT_YET_INSTALL) {
+ installNowTv?.visibility = View.GONE
+ installLaterTv?.visibility = View.GONE
+ updateNowBt?.visibility = View.VISIBLE
+ updateLaterBt?.visibility = View.VISIBLE
+ }
+ }
+
+ private fun showInstallUI() {
+ showInstallUI(TIMING_NOT_YET_INSTALL)
+ }
+
+ private fun showLatestUI() {
+ Log.d(TAG, "showLatestUI: ")
+ checkversionbBt?.visibility = View.VISIBLE
+ latestedTv?.visibility = View.VISIBLE
+ updateLl?.visibility = View.GONE
+ downloadLl?.visibility = View.GONE
+ installLl?.visibility = View.GONE
+ }
+
+ private fun showDownloadUI() {
+ Log.d(TAG, "showDownloadUI: ")
+ latestedTv?.visibility = View.GONE
+ updateLl?.visibility = View.GONE
+ checkversionbBt?.visibility = View.GONE
+ downloadLl?.visibility = View.VISIBLE
+ installLl?.visibility = View.GONE
+ }
+
+ private fun showCheckResultUI() {
+ checkversionbBt?.visibility = View.VISIBLE
+ updateLl?.visibility = View.GONE
+ latestedTv?.visibility = View.GONE
+ downloadLl?.visibility = View.GONE
+ installLl?.visibility = View.GONE
+ }
+
+ private fun showCheckVersionUI() {
+ checkversionbBt?.visibility = View.VISIBLE
+ downloadLl?.visibility = View.GONE
+ installLl?.visibility = View.GONE
+ DeviceUtils.checkVersion(version, object : VersionCheckCallback {
+ override fun onCallback(response: VersionCheckResponse) {
+ val newer = response.data?.isNewer
+ this@AboutWindow.response = response
+ if (newer == 0) {
+ showLatestUI()
+ } else if (newer == 1) {
+ latestedTv?.visibility = View.GONE
+ updateLl?.visibility = View.VISIBLE
+ updateBt?.text =
+ getContext().resources.getString(R.string.update_to_version) + "${response.data.version}"
+ }
+ }
+
+ override fun onUpdateCallback(response: UpdateResponse) {
+ Log.d(TAG, "onUpdateCallback() called with: response = $response")
+ }
+ })
+ }
+
+ private fun startDownloadDeb(response: VersionCheckResponse?) {
+ showDownloadUI()
+ val split = response?.data?.downloadUrl?.split("/")
+ val fileName = split?.get(split.size - 1)
+ fileNameTv?.text = fileName
+ downloadId = downloadService?.startDownload(response?.data?.downloadUrl, fileName)
+ Log.d(TAG, "startDownloadDeb() called with: response = $response downloadId = $downloadId")
+ }
+
+ private fun bindService() {
+ val intent = Intent(getContext(), DownloadService::class.java)
+ getContext().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+
+
+ private fun createNotification() {
+ val notification= showUpdateNotification(GlobalSystemUIContext.getContext())
+ val systemService = GlobalSystemUIContext.getContext().getSystemService(Context.NOTIFICATION_SERVICE)
+ createNotificationChannel(GlobalSystemUIContext.getContext())
+ val currentUser: UserHandle? = UserHandle(0)
+ if (systemService != null) {
+ val mNotificationManager = systemService as NotificationManager
+ mNotificationManager.notifyAsUser(null, mNotificationId, notification, currentUser)
+ }
+ }
+
+ private fun showUpdateNotification(context: Context): Notification {
+ Log.d(TAG, "showUpdateNotification ${getContext()}")
+ val nowIntent = Intent(getContext(), UpdateActionReceiver::class.java).apply {
+ action = ACTION_UPDATE_NOW
+ }
+ nowIntent.setPackage("com.boringdroid.systemui")
+
+ // “下次开机更新” 的 PendingIntent
+ val deferIntent = Intent(getContext(), UpdateActionReceiver::class.java).apply {
+ action = ACTION_DEFER_UPDATE
+ }
+ deferIntent.setPackage("com.boringdroid.systemui")
+
+ val updateNowAction: Notification.Action = Notification.Action.Builder(
+ Icon.createWithResource(context, android.R.drawable.ic_dialog_info),
+ getContext().resources.getString(R.string.update_now),
+ PendingIntent.getService(
+ context,
+ mNotificationId, /* unique request code */
+ nowIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+
+ val updateLaterAction: Notification.Action = Notification.Action.Builder(
+ Icon.createWithResource(context, android.R.drawable.ic_dialog_info),
+ getContext().resources.getString(R.string.update_next),
+ PendingIntent.getService(
+ context,
+ mNotificationId, /* unique request code */
+ deferIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+
+ val extras = Bundle()
+ extras.putString(Notification.EXTRA_TITLE, "OpenFDE" + getContext().resources.getString(R.string.download_complete))
+
+ val builder = Notification.Builder(context, getContext().resources.getString(R.string.version_update))
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .addAction(updateNowAction)
+ .addAction(updateLaterAction)
+ .setAutoCancel(true)
+ .setGroup("fde_version_download")
+ .addExtras(extras)
+
+ return builder.build()
+ }
+
+ private fun createNotificationChannel(context: Context){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channelId = "版本更新";
+ val name = "版本更新";
+ val importance = NotificationManager.IMPORTANCE_HIGH;
+
+ val channel = NotificationChannel(channelId, name, importance);
+ channel.setDescription("系统版本更新通知");
+
+ // 使用 SystemUI 的 NotificationManager
+ val notificationManager = context.getSystemService (Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+
+ fun onAction(action: String) {
+ var policy :String ?= null
+ if(action.equals(ACTION_UPDATE_NOW)){
+ policy = "Immediately"
+ SPUtils.putIntUserInfo(SPUtils.pluginContext, "timing_install", TIMING_NOW_INSTALL)
+ }else if(action.equals(ACTION_DEFER_UPDATE)){
+ policy = "PreStart"
+ SPUtils.putIntUserInfo(SPUtils.pluginContext, "timing_install", TIMING_LATER_INSTALL)
+ }
+ DeviceUtils.startInstall(version,path, policy, object : VersionCheckCallback {
+ override fun onCallback(response: VersionCheckResponse) {
+ Log.d(TAG, "onCallback() called with: response = $response")
+ }
+
+ override fun onUpdateCallback(response: UpdateResponse) {
+ Log.d(TAG, "onUpdateCallback() called with: response = $response")
+ when(response.code){
+ 5001->{
+ Toast.makeText(getContext(), response.message,
+ Toast.LENGTH_LONG).show()
+ Log.e(TAG, "5001: response = $response")
+ }
+ 5002->{
+ Toast.makeText(getContext(), response.message,
+ Toast.LENGTH_LONG).show()
+ Log.e(TAG, "5002: response = $response")
+ }
+ 5004->{
+ Toast.makeText(getContext(), response.message,
+ Toast.LENGTH_LONG).show()
+ Log.e(TAG, "5003: response = $response")
+ }
+ else ->{
+ SPUtils.putIntUserInfo(SPUtils.pluginContext, "timing_install", TIMING_NOT_YET_INSTALL)
+ SPUtils.putUserInfo(SPUtils.pluginContext, "fde_deb_path", "")
+ }
+
+ }
+ }
+ })
+ initUI()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/boringdroid/systemui/view/AbsTopPopWindow.kt b/app/src/main/java/com/boringdroid/systemui/view/AbsTopPopWindow.kt
index 5db2215f..06bc072b 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/AbsTopPopWindow.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/AbsTopPopWindow.kt
@@ -60,6 +60,8 @@ open class AbsTopPopWindow(
object Power : WindowType()
object Control : WindowType()
object DockContext : WindowType()
+ object About : WindowType()
+
object Default : WindowType()
}
@@ -331,6 +333,7 @@ open class AbsTopPopWindow(
is WindowType.Power -> TopBarPowerWindow(context, width, height, gravity, layoutResId, typeParam)
is WindowType.Control -> TopBarControlWindow(context, width, height, gravity, layoutResId, typeParam)
is WindowType.DockContext -> DockContextWindow(context, width, height, gravity, layoutResId, typeParam)
+ is WindowType.About -> AboutWindow(context, width, height, gravity, layoutResId, typeParam)
is WindowType.Default -> AbsTopPopWindow(context, width, height, gravity, layoutResId, typeParam)
}
diff --git a/app/src/main/java/com/boringdroid/systemui/view/AppOverviewWindow.kt b/app/src/main/java/com/boringdroid/systemui/view/AppOverviewWindow.kt
index 930b8f1e..fb48a949 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/AppOverviewWindow.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/AppOverviewWindow.kt
@@ -178,7 +178,7 @@ class AppOverviewWindow(
return@setOnKeyListener true
} else if(keyCode == KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP){
return@setOnKeyListener true
- } else {
+ }else {
return@setOnKeyListener false
}
}
diff --git a/app/src/main/java/com/boringdroid/systemui/view/LoadedDockContextRecycleView.kt b/app/src/main/java/com/boringdroid/systemui/view/LoadedDockContextRecycleView.kt
index 51aaf7bb..b5b035ce 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/LoadedDockContextRecycleView.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/LoadedDockContextRecycleView.kt
@@ -124,7 +124,7 @@ constructor(
)
holder.iconIv?.setImageDrawable(svgDrawable)
} else {
- Glide.with(GlobalSystemUIContext.getGlobalSystemuiContext()!!)
+ Glide.with(GlobalSystemUIContext.getContext())
.load("${Utils.linuxRootPath}${appData?.iconPath}")
.centerCrop()
.placeholder(context.getDrawable(R.drawable.linux_x11))
diff --git a/app/src/main/java/com/boringdroid/systemui/view/LoadedOverviewRecycleView.kt b/app/src/main/java/com/boringdroid/systemui/view/LoadedOverviewRecycleView.kt
index 6155477d..3b813f29 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/LoadedOverviewRecycleView.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/LoadedOverviewRecycleView.kt
@@ -98,7 +98,7 @@ constructor(
)
holder.iconIV?.setImageDrawable(svgDrawable)
} else {
- Glide.with(GlobalSystemUIContext.getGlobalSystemuiContext()!!)
+ Glide.with(GlobalSystemUIContext.getContext())
.load("${Utils.linuxRootPath}${appData?.iconPath}")
.centerCrop()
.placeholder(context.getDrawable(R.drawable.linux_x11))
diff --git a/app/src/main/java/com/boringdroid/systemui/view/LoadedSearchRecycleView.kt b/app/src/main/java/com/boringdroid/systemui/view/LoadedSearchRecycleView.kt
index 6cd3cff0..07b98581 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/LoadedSearchRecycleView.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/LoadedSearchRecycleView.kt
@@ -129,7 +129,7 @@ constructor(
)
holder.iconIV?.setImageDrawable(svgDrawable)
} else {
- Glide.with(GlobalSystemUIContext.getGlobalSystemuiContext()!!)
+ Glide.with(GlobalSystemUIContext.getContext())
.load("${Utils.linuxRootPath}${appData?.iconPath}")
.centerCrop()
.placeholder(context.getDrawable(R.drawable.linux_x11))
diff --git a/app/src/main/java/com/boringdroid/systemui/view/TopBarLayout.kt b/app/src/main/java/com/boringdroid/systemui/view/TopBarLayout.kt
index 11558675..4d3e8656 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/TopBarLayout.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/TopBarLayout.kt
@@ -76,6 +76,7 @@ class TopBarLayout(context: Context?, attrs: AttributeSet?) :
var inited: Boolean = false
}
+ var aboutWindow: AboutWindow ?= null
private var needUpdateBattery: Boolean = false
private var plugged: Int = 0
private var status: Int = 0
@@ -304,6 +305,7 @@ class TopBarLayout(context: Context?, attrs: AttributeSet?) :
notificationsWindow?.systemUIContext = systemUIContext
notificationsWindow?.enterView = imageView
notificationsWindow?.setNotifications(notifications)
+ notificationsWindow?.topBarLayout = this
windowList.add(notificationsWindow!!)
}
@@ -392,6 +394,7 @@ class TopBarLayout(context: Context?, attrs: AttributeSet?) :
}
}
powerWindow?.enterView = imageView
+ powerWindow?.topBarLayout = this
windowList.add(powerWindow!!)
}
diff --git a/app/src/main/java/com/boringdroid/systemui/view/TopBarNotificationWindow.kt b/app/src/main/java/com/boringdroid/systemui/view/TopBarNotificationWindow.kt
index 5bd97cd8..cb24b273 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/TopBarNotificationWindow.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/TopBarNotificationWindow.kt
@@ -1,24 +1,25 @@
package com.boringdroid.systemui.view
import android.app.NotificationManager
-import android.app.PendingIntent.CanceledException
import android.content.Context
import android.content.Intent
-import android.service.notification.StatusBarNotification
import android.util.Log
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.boringdroid.systemui.GlobalSystemUIContext
import com.boringdroid.systemui.R
import com.boringdroid.systemui.adapter.OnNotificationItemClickListener
import com.boringdroid.systemui.adapter.SlideNotificationAdapter
import com.boringdroid.systemui.data.DesktopNotification
import com.boringdroid.systemui.receiver.NotificationReceiver.Companion.NOTIFICATION_ID
-import com.boringdroid.systemui.receiver.NotificationReceiver.Companion.NOTIFI_AQUIRE_ACTION
import com.boringdroid.systemui.receiver.NotificationReceiver.Companion.NOTIFI_CANCEL_ALL_ACTION
import com.boringdroid.systemui.receiver.NotificationReceiver.Companion.NOTIFI_CLICK_ACTION
+import com.boringdroid.systemui.view.AboutWindow.Companion.ACTION_DEFER_UPDATE
+import com.boringdroid.systemui.view.AboutWindow.Companion.ACTION_UPDATE_NOW
+import com.boringdroid.systemui.view.AboutWindow.Companion.NOTIFI_CHANAL_ID
class TopBarNotificationWindow(
context: Context,
@@ -43,6 +44,8 @@ class TopBarNotificationWindow(
private var clearTv: TextView? = null
private var notificationAdapter: SlideNotificationAdapter? = null
var systemUIContext: Context? = null
+ var topBarLayout: TopBarLayout? = null
+
private var notifications: Array ? = null
private var rootRl:RelativeLayout ? = null
private var nm: NotificationManager ? = null
@@ -51,7 +54,7 @@ class TopBarNotificationWindow(
super.showPopupWindow()
runWindowAnim(WindowGravity.right, true)
initViews()
- val systemService = systemUIContext?.getSystemService(Context.NOTIFICATION_SERVICE)
+ val systemService = GlobalSystemUIContext.getContext().getSystemService(Context.NOTIFICATION_SERVICE)
if (systemService != null) {
nm = systemService as NotificationManager
}
@@ -71,6 +74,7 @@ class TopBarNotificationWindow(
}
override fun onItemClick(sbn: DesktopNotification, item: View?) {
+ Log.d(TAG, "onItemClick() called with: sbn = $sbn, contentIntent = ${sbn.contentIntent}")
if (sbn.contentIntent != null) {
dismiss()
val intent = Intent(NOTIFI_CLICK_ACTION)
@@ -79,9 +83,42 @@ class TopBarNotificationWindow(
if (sbn.isClearable) {
nm?.cancel(sbn.id)
}
+ } else if(sbn.id == NOTIFI_CHANAL_ID){
+
}
}
+ override fun onItemClick(
+ sbn: DesktopNotification,
+ item: View?,
+ action: String
+ ) {
+ Log.d(TAG, "onItemClick() called with: sbn = $sbn, contentIntent = ${sbn.contentIntent} $action")
+ if (sbn.contentIntent != null) {
+ dismiss()
+ val intent = Intent(NOTIFI_CLICK_ACTION)
+ intent.putExtra(NOTIFICATION_ID, sbn.id)
+ getContext().sendBroadcast(intent)
+ } else if(sbn.id == NOTIFI_CHANAL_ID){
+ if(action.equals(getContext().resources.getString(R.string.update_now))){
+ val intent = Intent(ACTION_UPDATE_NOW)
+ intent.setPackage("com.boringdroid.systemui")
+ getContext().sendBroadcast(intent)
+ topBarLayout?.aboutWindow?.onAction(ACTION_UPDATE_NOW)
+ } else if(action.equals(getContext().resources.getString(R.string.update_next))){
+ val intent = Intent(ACTION_DEFER_UPDATE)
+ intent.setPackage("com.boringdroid.systemui")
+ getContext().sendBroadcast(intent)
+ topBarLayout?.aboutWindow?.onAction(ACTION_DEFER_UPDATE)
+ }
+ }
+ Log.d(TAG, "onItemClick: ${sbn.isClearable} $nm")
+ if (sbn.isClearable) {
+ nm?.cancel(sbn.id)
+ }
+ dismiss()
+ }
+
override fun onItemCancelClick(sbn: DesktopNotification, item: View?) {
dismiss()
diff --git a/app/src/main/java/com/boringdroid/systemui/view/TopBarPowerWindow.kt b/app/src/main/java/com/boringdroid/systemui/view/TopBarPowerWindow.kt
index 96c756be..8c3322d1 100644
--- a/app/src/main/java/com/boringdroid/systemui/view/TopBarPowerWindow.kt
+++ b/app/src/main/java/com/boringdroid/systemui/view/TopBarPowerWindow.kt
@@ -1,22 +1,16 @@
package com.boringdroid.systemui.view
-import android.animation.Animator
-import android.animation.ObjectAnimator
import android.content.Context
import android.content.Intent
-import android.graphics.Outline
-import android.os.Build
import android.provider.Settings
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
-import android.view.ViewOutlineProvider
-import android.view.animation.LinearInterpolator
import android.widget.TextView
-import androidx.cardview.widget.CardView
import com.boringdroid.systemui.R
+import com.boringdroid.systemui.data.UpdateResponse
+import com.boringdroid.systemui.data.VersionCheckResponse
import com.boringdroid.systemui.utils.DeviceUtils
-import com.boringdroid.systemui.utils.Utils
class TopBarPowerWindow(
context: Context,
@@ -41,6 +35,8 @@ class TopBarPowerWindow(
private var rebootBtn: TextView? = null
private var logoutBtn: TextView? = null
private var lockBtn: TextView? = null
+ private var aboutWindow: AboutWindow ?= null
+ var topBarLayout: TopBarLayout ?= null
private val hoverListener = View.OnHoverListener { v, event ->
val what = event?.action
@@ -71,7 +67,6 @@ class TopBarPowerWindow(
rebootBtn = mContentView?.findViewById(R.id.reboot_tv)
logoutBtn = mContentView?.findViewById(R.id.logout_tv)
lockBtn = mContentView?.findViewById(R.id.lock_tv)
-
aboutBtn?.setOnClickListener(this)
settingBtn?.setOnClickListener(this)
sleepBtn?.setOnClickListener(this)
@@ -79,7 +74,6 @@ class TopBarPowerWindow(
rebootBtn?.setOnClickListener(this)
logoutBtn?.setOnClickListener(this)
lockBtn?.setOnClickListener(this)
-
aboutBtn?.setOnHoverListener(hoverListener)
settingBtn?.setOnHoverListener(hoverListener)
sleepBtn?.setOnHoverListener(hoverListener)
@@ -87,20 +81,6 @@ class TopBarPowerWindow(
rebootBtn?.setOnHoverListener(hoverListener)
logoutBtn?.setOnHoverListener(hoverListener)
lockBtn?.setOnHoverListener(hoverListener)
-
-// val cardView = getContentView()?.findViewById(R.id.root)
-// cardView?.elevation = 8f
-
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-// // 设置阴影偏移(X轴向右,Y轴向下)
-// cardView?.outlineProvider = object : ViewOutlineProvider() {
-// override fun getOutline(view: View, outline: Outline) {
-// outline.setRoundRect(0, 0, view.width, view.height - 4, 8f)
-// }
-// }
-// cardView?.elevation = 8f
-// }
-
}
@@ -128,32 +108,12 @@ class TopBarPowerWindow(
private fun showAboutWindow() {
val width = getContext().resources.getDimension(R.dimen.top_bar_about_width).toInt()
val height = getContext().resources.getDimension(R.dimen.top_bar_about_height).toInt()
- var powerWindow: AbsTopPopWindow =
+ aboutWindow =
Builder(getContext(), width, height, R.layout.window_topbar_about)
.gravity(Gravity.CENTER)
- .build(WindowType.Default)
- powerWindow.showPopupWindow()
- val contentView = powerWindow.getContentView()
- Utils.setBackgroundBlurRadius(contentView?.findViewById(R.id.root_blur), 100, 12f)
- if (contentView != null) {
- var close: View? = contentView.findViewById(R.id.close_iv)
- var versionTv: TextView? = contentView.findViewById(R.id.version_tv)
- var deviceTv: TextView? = contentView.findViewById(R.id.device_tv)
-
- close?.setOnClickListener {
- powerWindow.dismiss()
- }
-
- val openfde = getContext().resources.getString(R.string.openfde_version)
- val version = Utils.getProperty("ro.openfde.version", "2.0.1")
- versionTv?.text = "$openfde $version"
-
- val androidv = getContext().resources.getString(R.string.android_version)
- val majorVersion = Utils.getMajorVersion()
- deviceTv?.text = "$androidv $majorVersion"
-
- }
-
+ .build(WindowType.About) as AboutWindow
+ aboutWindow?.showPopupWindow()
+ topBarLayout?.aboutWindow = aboutWindow
}
private fun showSetting() {
@@ -161,4 +121,10 @@ class TopBarPowerWindow(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
getContext().startActivity(intent)
}
+}
+
+interface VersionCheckCallback{
+ fun onCallback(response: VersionCheckResponse)
+ fun onUpdateCallback(response: UpdateResponse)
+
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_notification_item.xml b/app/src/main/res/layout/layout_notification_item.xml
index adb43219..b2075bf7 100644
--- a/app/src/main/res/layout/layout_notification_item.xml
+++ b/app/src/main/res/layout/layout_notification_item.xml
@@ -108,8 +108,8 @@
android:layout_marginTop="6dp"
android:layout_below="@id/tv_title"
android:orientation="horizontal"
- android:paddingStart="16dp"
- android:paddingEnd="16dp" />
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp" />
diff --git a/app/src/main/res/layout/window_topbar_about.xml b/app/src/main/res/layout/window_topbar_about.xml
index 2f3caf84..cb793a39 100644
--- a/app/src/main/res/layout/window_topbar_about.xml
+++ b/app/src/main/res/layout/window_topbar_about.xml
@@ -62,6 +62,128 @@
android:layout_centerHorizontal="true"
style="@style/style_55"
android:text="OpenFDE 2.0.1" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 9d72edad..2b6bd7be 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -132,6 +132,15 @@
OpenFDE 版本
Android 版本
-
+ 检查版本更新
+ 已经是最新版本
+ 更新版本到
+ 暂停
+ 继续下载
+ 立即更新
+ 下一次开机自动更新
+ 下载成功
+ 更新
+ 版本更新
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e07ce02f..2d54dc89 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,6 +8,7 @@
Search...
Power off
Restart
+ Restart
Soft restart
Log Out
Pin
@@ -133,6 +134,14 @@
OpenFDE version
Android version
-
+ Check version
+ Already latested
+ Update to version
+ pause
+ Update now
+ Update on next startup
+ Download complete
+ update
+ Update OpenFDE
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..1b39fca2
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file