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