From e5ba0fe34b1305d305080371f798b6f255d651d7 Mon Sep 17 00:00:00 2001 From: Raymond Yang Date: Mon, 10 Apr 2023 16:11:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E5=8B=95=E6=85=8B=E7=94=B3?= =?UTF-8?q?=E8=AB=8B=E6=AC=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 15 ++ .../ray650128/iosclockwidget/AlarmService.kt | 4 +- .../iosclockwidget/AutoStartReceiver.kt | 18 ++ .../ray650128/iosclockwidget/MainActivity.kt | 39 +++- .../ray650128/iosclockwidget/MyApplication.kt | 20 +++ .../iosclockwidget/PermissionUtil.kt | 169 ++++++++++++++++++ .../iosclockwidget/PreferenceUtil.kt | 20 +++ 7 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/ray650128/iosclockwidget/AutoStartReceiver.kt create mode 100644 app/src/main/java/com/ray650128/iosclockwidget/MyApplication.kt create mode 100644 app/src/main/java/com/ray650128/iosclockwidget/PermissionUtil.kt create mode 100644 app/src/main/java/com/ray650128/iosclockwidget/PreferenceUtil.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a389de..0cfa53e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,11 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/ray650128/iosclockwidget/AlarmService.kt b/app/src/main/java/com/ray650128/iosclockwidget/AlarmService.kt index 7b37011..5d68547 100644 --- a/app/src/main/java/com/ray650128/iosclockwidget/AlarmService.kt +++ b/app/src/main/java/com/ray650128/iosclockwidget/AlarmService.kt @@ -34,8 +34,6 @@ class AlarmService : Service() { isServiceRunning = true - //RetrofitClient.init("http://192.168.0.173:8080") - startForeground() } @@ -105,7 +103,7 @@ class AlarmService : Service() { companion object { private val TAG = AlarmService::class.java.simpleName - const val SERVICE_ID = 0x1 + const val SERVICE_ID = 0x101 private const val NOTIFICATION_SERVICE_CH = "clock.service" diff --git a/app/src/main/java/com/ray650128/iosclockwidget/AutoStartReceiver.kt b/app/src/main/java/com/ray650128/iosclockwidget/AutoStartReceiver.kt new file mode 100644 index 0000000..81d94e4 --- /dev/null +++ b/app/src/main/java/com/ray650128/iosclockwidget/AutoStartReceiver.kt @@ -0,0 +1,18 @@ +package com.ray650128.iosclockwidget + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log + +class AutoStartReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + Log.d("AutoStartReceiver", "intent: ${intent?.action}") + if (context == null || intent == null) return + if (!PreferenceUtil.isGranted) return + val i = Intent(context, AlarmService::class.java) + context.startForegroundService(i) + Log.d("AutoStartReceiver", "Boot completed.") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ray650128/iosclockwidget/MainActivity.kt b/app/src/main/java/com/ray650128/iosclockwidget/MainActivity.kt index 76c1444..f6255e9 100644 --- a/app/src/main/java/com/ray650128/iosclockwidget/MainActivity.kt +++ b/app/src/main/java/com/ray650128/iosclockwidget/MainActivity.kt @@ -1,15 +1,52 @@ package com.ray650128.iosclockwidget +import android.Manifest import android.content.Intent +import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class MainActivity : AppCompatActivity() { + + private val callback = object : PermissionUtil.PermissionResultCallback { + override fun onGrant() { + PreferenceUtil.isGranted = true + if (AlarmService.isServiceRunning) return + startForegroundService( + Intent(this@MainActivity, AlarmService::class.java) + ) + } + + override fun onDeny(denies: ArrayList?) { + PreferenceUtil.isGranted = false + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - if (!AlarmService.isServiceRunning) { + checkPermissionForNotification() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + PermissionUtil.onRequestPermissionsResult(this, requestCode, permissions, grantResults) + } + + private fun checkPermissionForNotification() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + PermissionUtil.checkPermission(this, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + callback + ) + } else { + PreferenceUtil.isGranted = true + if (AlarmService.isServiceRunning) return startForegroundService( Intent(this@MainActivity, AlarmService::class.java) ) diff --git a/app/src/main/java/com/ray650128/iosclockwidget/MyApplication.kt b/app/src/main/java/com/ray650128/iosclockwidget/MyApplication.kt new file mode 100644 index 0000000..dddceac --- /dev/null +++ b/app/src/main/java/com/ray650128/iosclockwidget/MyApplication.kt @@ -0,0 +1,20 @@ +package com.ray650128.iosclockwidget + +import android.app.Application +import android.content.Context + +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + instance = this + } + + companion object { + var instance: Application? = null + + fun getAppContext(): Context { + return instance!!.applicationContext + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ray650128/iosclockwidget/PermissionUtil.kt b/app/src/main/java/com/ray650128/iosclockwidget/PermissionUtil.kt new file mode 100644 index 0000000..311e21e --- /dev/null +++ b/app/src/main/java/com/ray650128/iosclockwidget/PermissionUtil.kt @@ -0,0 +1,169 @@ +package com.ray650128.iosclockwidget + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.annotation.NonNull +import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + + +/** + * 權限工具 + * 用法: + * 1.在 Activity 中定義所需的權限陣列 + * 2.呼叫 PermissionUtil.checkPermissions() + * 3.在 PermissionResultCallback 中處理權限授與或拒絕的處裡 + */ +object PermissionUtil { + /** + * 當權限拒絕時,是否顯示系統的應用程式設定頁面 + */ + var showSystemSetting: Boolean = true + + private const val mRequestCode: Int = 100 + private var mPermissionResult: PermissionResultCallback? = null + private var mPermissionDialog: AlertDialog? = null + + /** + * 檢查權限 + * @param context Activity 對象 + * @param permissions 要求的權限陣列 + * @param permissionResultCallback 權限授與/拒絕的回呼 + */ + fun checkPermission( + context: Activity, + permissions: Array, + @NonNull permissionResultCallback: PermissionResultCallback + ) { + mPermissionResult = permissionResultCallback + + if (Build.VERSION.SDK_INT < 23) { // 如果系統低於 Android 6.0,不使用動態申請 + permissionResultCallback.onGrant() + return + } + + // 遍歷整個 permissions 陣列,檢查有哪些權限未允許 + val mPermissionList = ArrayList() + for (permission in permissions) { + if (ContextCompat.checkSelfPermission( + context, + permission + ) != PackageManager.PERMISSION_GRANTED + ) { + // 將沒有允許的權限加入 mPermissionList + mPermissionList.add(permission) + } + } + + // 申請權限 + if (mPermissionList.size > 0) { // mPermissionList 中有權限沒有被允許 + ActivityCompat.requestPermissions(context, permissions, mRequestCode) + } else { + // 全部的權限都已被允許 + permissionResultCallback.onGrant() + return + } + } + + /** + * onRequestPermissionsResult, + * 請務必在 Activity 的 onRequestPermissionsResult 呼叫此方法 + * @param context Activity 對象 + * @param requestCode + * @param permissions + * @param grantResults + */ + fun onRequestPermissionsResult( + context: Activity, + requestCode: Int, + @NonNull permissions: Array, + @NonNull grantResults: IntArray + ) { + // 檢查是否有權限被拒 + var hasPermissionDenied = false + + // 被拒絕的權限 + val denyList = ArrayList() + + if (mRequestCode == requestCode) { + for (i in grantResults.indices) { + if (grantResults[i] == -1) { + hasPermissionDenied = true + denyList.add(permissions[i]) + } + } + } + + // 如果有權限被拒絕時 + if (hasPermissionDenied) { + if (showSystemSetting) { + showSystemPermissionSettingDialog(context, denyList) + } else { + mPermissionResult?.onDeny(denyList) + } + } else { + // 全部的權限都已被允許 + mPermissionResult?.onGrant() + } + } + + /** + * 顯示系統設定對話框 + * @param context + * @param denies 被拒絕的權限 + */ + private fun showSystemPermissionSettingDialog(context: Activity, denies: ArrayList?) { + val mPackageName = context.packageName + if (mPermissionDialog == null) { + mPermissionDialog = AlertDialog.Builder(context) + .setMessage("已禁用權限,請手動授與") + .setPositiveButton("前往設定") { _, _ -> + cancelPermissionDialog() + + val packageUri = Uri.parse("package:$mPackageName") + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageUri) + context.startActivity(intent) + context.finish() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + // 關閉對話框 + cancelPermissionDialog() + + mPermissionResult?.onDeny(denies) + } + .create() + } + mPermissionDialog?.show() + } + + /** + * 關閉對話框 + */ + private fun cancelPermissionDialog() { + if (mPermissionDialog != null) { + mPermissionDialog?.cancel() + mPermissionDialog = null + } + } + + /** + * 動態申請權限的狀態回呼 + */ + interface PermissionResultCallback { + /** + * 已授與 + */ + fun onGrant() + + /** + * 已拒絕 + * @param denies 被拒絕的權限 + */ + fun onDeny(denies: ArrayList?) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ray650128/iosclockwidget/PreferenceUtil.kt b/app/src/main/java/com/ray650128/iosclockwidget/PreferenceUtil.kt new file mode 100644 index 0000000..2e6e429 --- /dev/null +++ b/app/src/main/java/com/ray650128/iosclockwidget/PreferenceUtil.kt @@ -0,0 +1,20 @@ +package com.ray650128.iosclockwidget + +import android.content.Context +import java.lang.reflect.Type + + +/** + * Shared Preferences 工具類別 + * @author Raymond Yang + */ +object PreferenceUtil { + private const val MAIN_KEY = "iOS_Clock_Widget" + private const val IS_GRANTED = "IS_GRANTED" + + private val sharedPreferences = MyApplication.getAppContext().getSharedPreferences(MAIN_KEY, Context.MODE_PRIVATE) + + var isGranted: Boolean + get() = sharedPreferences.getBoolean(IS_GRANTED, false) + set(value) = sharedPreferences.edit().putBoolean(IS_GRANTED, value).apply() +} \ No newline at end of file