Skip to content

Commit dad6c06

Browse files
authored
Merge pull request #238 from Team-HMH/feature/lock_service
[Feature/lock service]: ์ž ๊ธˆ ๊ธฐ๋Šฅ ์ˆ˜์ • ์ž‘์—…
2 parents 5b27f8f + ade65a1 commit dad6c06

File tree

44 files changed

+792
-736
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+792
-736
lines changed

โ€Žapp/src/main/AndroidManifest.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
1515
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
16+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
17+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
18+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
1619

1720
<application
1821
android:name=".HMHApplication"
@@ -29,8 +32,7 @@
2932
<provider
3033
android:name="androidx.startup.InitializationProvider"
3134
android:authorities="${applicationId}.androidx-startup"
32-
tools:node="remove">
33-
</provider>
35+
tools:node="remove"></provider>
3436

3537
<activity
3638
android:name=".SampleActivity"
@@ -60,5 +62,15 @@
6062
<activity
6163
android:name=".common.permission.PermissionActivity"
6264
android:exported="false" />
65+
66+
<service
67+
android:name=".core.service.LockForegroundService"
68+
android:exported="false"
69+
android:foregroundServiceType="specialUse">
70+
<property
71+
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
72+
android:value="ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์•ฑ์„ ํŠธ๋ž˜ํ‚นํ•˜๊ณ  ์‚ฌ์šฉ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ธฐ ์œ„ํ•ด ํฌ๊ทธ๋ผ์šด๋“œ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค." />
73+
</service>
74+
6375
</application>
6476
</manifest>

โ€Žapp/src/main/java/com/hmh/hamyeonham/HMHApplication.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package com.hmh.hamyeonham
33
import android.app.Application
44
import androidx.hilt.work.HiltWorkerFactory
55
import androidx.work.Configuration
6+
import com.hmh.hamyeonham.core.notification.AppNotificationManager
67
import com.kakao.sdk.common.KakaoSdk
78
import dagger.hilt.EntryPoint
89
import dagger.hilt.EntryPoints
910
import dagger.hilt.InstallIn
1011
import dagger.hilt.android.HiltAndroidApp
1112
import dagger.hilt.components.SingletonComponent
13+
import javax.inject.Inject
1214

1315
@HiltAndroidApp
1416
class HMHApplication : Application(), Configuration.Provider {
@@ -18,6 +20,8 @@ class HMHApplication : Application(), Configuration.Provider {
1820
interface HiltWorkerFactoryEntryPoint {
1921
fun workerFactory(): HiltWorkerFactory
2022
}
23+
@Inject
24+
lateinit var notificationManager: AppNotificationManager
2125

2226
override val workManagerConfiguration: Configuration = Configuration.Builder()
2327
.setWorkerFactory(
@@ -28,5 +32,6 @@ class HMHApplication : Application(), Configuration.Provider {
2832
override fun onCreate() {
2933
super.onCreate()
3034
KakaoSdk.init(this, BuildConfig.KAKAO_API_KEY)
35+
notificationManager.setupNotificationChannel()
3136
}
3237
}
Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package com.hmh.hamyeonham.common.activity
22

3-
import android.app.usage.UsageStatsManager
4-
import android.content.Context
5-
import android.content.Intent
6-
import android.net.Uri
7-
import android.os.PowerManager
8-
import android.provider.Settings
93
import androidx.appcompat.app.AppCompatActivity
104
import androidx.fragment.app.Fragment
115
import androidx.fragment.app.commit
@@ -22,67 +16,4 @@ fun AppCompatActivity.addFragment(containerViewId: Int, fragment: Fragment) {
2216
}
2317
}
2418

25-
fun AppCompatActivity.requestAccessibilitySettings() {
26-
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
27-
startActivity(intent)
28-
}
29-
30-
fun AppCompatActivity.requestOverlayPermission() {
31-
val packageUri = Uri.parse("package:$packageName")
32-
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, packageUri)
33-
startActivity(intent)
34-
}
35-
36-
fun AppCompatActivity.requestUsageAccessPermission() {
37-
try {
38-
val packageUri = Uri.parse("package:$packageName")
39-
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS, packageUri)
40-
startActivity(intent)
41-
} catch (e: Exception) {
42-
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
43-
}
44-
}
45-
46-
fun AppCompatActivity.checkAccessibilityServiceEnabled(classCanonicalName: String): Boolean {
47-
val service = "$packageName/$classCanonicalName"
48-
val enabledServicesSetting = Settings.Secure.getString(
49-
contentResolver,
50-
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
51-
)
52-
return enabledServicesSetting?.contains(service) == true
53-
}
54-
55-
fun AppCompatActivity.hasUsageStatsPermission(): Boolean {
56-
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
57-
val time = System.currentTimeMillis()
58-
val stats = usageStatsManager.queryUsageStats(
59-
UsageStatsManager.INTERVAL_DAILY,
60-
time - 1000 * 60,
61-
time,
62-
)
63-
return stats != null && stats.isNotEmpty()
64-
}
65-
66-
fun AppCompatActivity.hasOverlayPermission(): Boolean {
67-
return Settings.canDrawOverlays(this)
68-
}
69-
70-
fun AppCompatActivity.allPermissionIsGranted(classCanonicalName: String): Boolean {
71-
return checkAccessibilityServiceEnabled(classCanonicalName) && hasUsageStatsPermission() && hasOverlayPermission()
72-
}
73-
74-
fun AppCompatActivity.isBatteryOptimizationEnabled(): Boolean {
75-
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
76-
val packageName = packageName
77-
return !powerManager.isIgnoringBatteryOptimizations(packageName)
78-
}
79-
80-
fun AppCompatActivity.requestDisableBatteryOptimization() {
81-
if (isBatteryOptimizationEnabled()) {
82-
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
83-
data = Uri.parse("package:$packageName")
84-
}
85-
startActivity(intent)
86-
}
87-
}
8819

โ€Žcore/common/src/main/java/com/hmh/hamyeonham/common/context/ContextExt.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.hmh.hamyeonham.common.context
22

3+
import android.Manifest
34
import android.app.Dialog
45
import android.content.Context
56
import android.content.pm.ApplicationInfo
@@ -14,7 +15,6 @@ import android.util.Log
1415
import android.view.View
1516
import android.view.WindowInsets
1617
import android.view.WindowManager
17-
import android.widget.TextView
1818
import android.widget.Toast
1919
import androidx.annotation.ColorRes
2020
import androidx.annotation.DrawableRes
@@ -136,3 +136,15 @@ fun Context.isSystemPackage(packageName: String): Boolean {
136136
}
137137
return false
138138
}
139+
140+
fun Context.hasNotificationPermission(): Boolean {
141+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
142+
ContextCompat.checkSelfPermission(
143+
this,
144+
Manifest.permission.POST_NOTIFICATIONS
145+
) == PackageManager.PERMISSION_GRANTED
146+
} else {
147+
// Android 13 ์ดํ•˜ ๋ฒ„์ „์—์„œ๋Š” ์ด ๊ถŒํ•œ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Œ
148+
true
149+
}
150+
}
Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package com.hmh.hamyeonham.common.fragment
22

3-
import android.app.usage.UsageStatsManager
4-
import android.content.Context
5-
import android.content.Intent
6-
import android.net.Uri
7-
import android.provider.Settings
83
import android.view.View
94
import android.widget.Toast
105
import androidx.annotation.ColorRes
@@ -27,7 +22,12 @@ fun Fragment.snackBar(anchorView: View, message: () -> String) {
2722
Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show()
2823
}
2924

30-
fun Fragment.snackBarWithAction(anchorView: View, message: String, actionMessage:String, action: View.OnClickListener) {
25+
fun Fragment.snackBarWithAction(
26+
anchorView: View,
27+
message: String,
28+
actionMessage: String,
29+
action: View.OnClickListener
30+
) {
3131
Snackbar.make(anchorView, message, Snackbar.LENGTH_SHORT)
3232
.setAction(actionMessage, action)
3333
.show()
@@ -46,68 +46,4 @@ val Fragment.viewLifeCycle
4646
val Fragment.viewLifeCycleScope
4747
get() = viewLifecycleOwner.lifecycleScope
4848

49-
fun Fragment.requestAccessibilitySettings() {
50-
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
51-
startActivity(intent)
52-
}
53-
54-
fun Fragment.requestOverlayPermission() {
55-
val packageUri = Uri.parse("package:${requireContext().packageName}")
56-
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, packageUri)
57-
startActivity(intent)
58-
}
59-
60-
fun Fragment.requestUsageAccessPermission() {
61-
try {
62-
val packageUri = Uri.parse("package:${requireContext().packageName}")
63-
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS, packageUri)
64-
startActivity(intent)
65-
} catch (e: Exception) {
66-
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
67-
}
68-
}
69-
70-
fun Fragment.checkAccessibilityServiceEnabled(classCanonicalName: String): Boolean {
71-
val service = "${requireContext().packageName}/${classCanonicalName}"
72-
val enabledServicesSetting = Settings.Secure.getString(
73-
requireContext().contentResolver,
74-
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
75-
)
76-
return enabledServicesSetting?.contains(service) == true
77-
}
78-
79-
fun Fragment.hasUsageStatsPermission(): Boolean {
80-
val usageStatsManager =
81-
requireContext().getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
82-
val time = System.currentTimeMillis()
83-
val stats = usageStatsManager.queryUsageStats(
84-
UsageStatsManager.INTERVAL_DAILY,
85-
time - 1000 * 60,
86-
time,
87-
)
88-
return stats != null && stats.isNotEmpty()
89-
}
90-
91-
fun Fragment.hasOverlayPermission(): Boolean {
92-
return Settings.canDrawOverlays(requireContext())
93-
}
94-
95-
fun Fragment.checkAllPermissionIsGranted(classCanonicalName: String) {
96-
when {
97-
!checkAccessibilityServiceEnabled(classCanonicalName) -> {
98-
requestAccessibilitySettings()
99-
}
10049

101-
!hasUsageStatsPermission() -> {
102-
requestUsageAccessPermission()
103-
}
104-
105-
!hasOverlayPermission() -> {
106-
requestOverlayPermission()
107-
}
108-
}
109-
}
110-
111-
fun Fragment.allPermissionIsGranted(classCanonicalName: String): Boolean {
112-
return this.checkAccessibilityServiceEnabled(classCanonicalName) && hasUsageStatsPermission() && hasOverlayPermission()
113-
}

โ€Žcore/common/src/main/java/com/hmh/hamyeonham/common/permission/PermissionActivity.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import android.provider.Settings
99
import androidx.activity.result.ActivityResultLauncher
1010
import androidx.activity.result.contract.ActivityResultContracts
1111
import androidx.appcompat.app.AppCompatActivity
12-
import com.hmh.hamyeonham.common.activity.checkAccessibilityServiceEnabled
12+
import com.hmh.hamyeonham.common.context.hasNotificationPermission
1313
import com.hmh.hamyeonham.common.context.toast
1414
import com.hmh.hamyeonham.common.databinding.ActivityPermissionBinding
1515
import com.hmh.hamyeonham.common.navigation.NavigationProvider
@@ -42,6 +42,13 @@ class PermissionActivity : AppCompatActivity() {
4242
}
4343
}
4444

45+
private val requestNotificationPermissionLauncher =
46+
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
47+
if (hasNotificationPermission()) {
48+
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.success_notification_permission))
49+
}
50+
}
51+
4552
override fun onCreate(savedInstanceState: Bundle?) {
4653
super.onCreate(savedInstanceState)
4754
setContentView(binding.root)
@@ -61,29 +68,38 @@ class PermissionActivity : AppCompatActivity() {
6168
binding.run {
6269
clUsageinfoPermission.setOnClickListener {
6370
if (hasUsageStatsPermission()) {
64-
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.already_usage_stats_permission))
71+
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.already_permission_granted))
6572
tgUsageinfoPermission.isChecked = true
6673
} else {
6774
requestUsageAccessPermission()
6875
}
6976
}
7077
clDrawoverPermission.setOnClickListener {
7178
if (hasOverlayPermission()) {
72-
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.already_overlay_permission))
79+
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.already_permission_granted))
7380
tgDrawoverPermission.isChecked = true
7481
} else {
7582
requestOverlayPermission()
7683
}
7784
}
85+
clNotificationPermission.setOnClickListener {
86+
if (hasNotificationPermission()) {
87+
toast(getString(com.hmh.hamyeonham.core.designsystem.R.string.already_permission_granted))
88+
} else {
89+
requestNotificationPermission(requestNotificationPermissionLauncher)
90+
}
91+
}
7892
}
7993
}
8094

8195
private fun setPermissionToggleState() {
8296
binding.run {
8397
tgUsageinfoPermission.isClickable = false
8498
tgDrawoverPermission.isClickable = false
99+
tgNotificationPermission.isClickable = false
85100
tgUsageinfoPermission.isChecked = hasUsageStatsPermission()
86101
tgDrawoverPermission.isChecked = hasOverlayPermission()
102+
tgNotificationPermission.isChecked = hasNotificationPermission()
87103
}
88104
}
89105

@@ -107,15 +123,6 @@ class PermissionActivity : AppCompatActivity() {
107123
}
108124
}
109125

110-
private fun checkAccessibilityServiceEnabled(): Boolean {
111-
val service = "$packageName/com.hmh.hamyeonham.core.service.LockAccessibilityService"
112-
val enabledServicesSetting = Settings.Secure.getString(
113-
contentResolver,
114-
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
115-
)
116-
return enabledServicesSetting?.contains(service) == true
117-
}
118-
119126
private fun hasUsageStatsPermission(): Boolean {
120127
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as? UsageStatsManager
121128
val time = System.currentTimeMillis()
@@ -131,8 +138,4 @@ class PermissionActivity : AppCompatActivity() {
131138
private fun hasOverlayPermission(): Boolean {
132139
return Settings.canDrawOverlays(this)
133140
}
134-
135-
private fun allPermissionIsGranted(): Boolean {
136-
return checkAccessibilityServiceEnabled() && hasUsageStatsPermission() && hasOverlayPermission()
137-
}
138141
}

0 commit comments

Comments
ย (0)