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" /> + +