일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- curl
- 안드로이드 푸쉬
- PHP
- Android
- roundcube
- php 취약점
- 자바스크립트
- 안드로이드 푸시
- php 시큐어코딩
- 폼메일
- C#
- 안드로이드
- chart.js
- WebView
- 설치
- javascript
- 자동 생성
- xe
- Mail Server
- 안드로이드 gcm
- UML
- FCM
- mysql
- dovecot
- not working
- html5
- 우분투
- android 효과음
- C# IO
- soundpool
- Today
- Total
그러냐
잠금화면 액티비티 띄우기 본문
Android에는 잠금화면 액티비티를 개발자가 개발 할 수있다.
그런데 스택오버플로우나 여러 블로그에는 똑같은 글만 돌고 도는듯 하고,
기타 오류에 대해서는 해결법을 찾기 어려웠다.
잠금화면을 띄우기 위해서는 BroadcastReceiver에서 ACTION_SCREEN_OFF, ACTION _SCREEN_ON 액션을 받아서
화면에 액티비티를 띄워야 되는데 매니페스트 파일에 receiver를 등록해 정적 동작은 실행되지 않는다.
그래서 Service를 만들어 Service에서 멤버 변수로 BroadcastReceiver를 동적으로 생성해서 IntentFilter로 위의 두
액션과 함께 registerReceiver로 등록해야된다.
여기서 또 문제는 Service를 Background에서 동작하게 하면 안드로이드 O 버전부터 백그라운드 동작을 죽이기 때문에
일정 시간이 지나면 더 이상 동작하지 않는다. 이걸 해결하기 위해 AlarmManager로 알람을 등록해 서비스를 계속 실행시키는 방법도 있는데 이것은 알람 주기 자체를 안드로이드에서 제한을 하는 것인지 30초이상은 지나야 동작하기 때문에 그전에 서비스가 죽어버리면 그 사이 시간에는 동작하지 않게 된다.
다른 해결법은 Service 시작을 startForegroundService로 시작을하고 Service의 onCreate나 onStartCommand 부분에 Notification을 등록하고 startForeground 함수를 호출하는 방법이다. 그런데 여기서 또 문제는 Foreground로 동작하게 해도 운영체제가 이 Service의 Context를 Sleep으로 만들어버리는지 일정시간 지나면 동작하지 않는 문제가 있다. Destroy를 하진 않는걸 보니 Sleep 같은 상태로 만들어버리는 듯하다.
이게 여타 블로그나 스택오버플로우에 돌아다니는 기본적은 내용이다.
위 문제를 해결하기 위해 Github의 많은 프로젝트들을 검색해서 방법을 알아냈다.
잠금화면을 위한 Service를 만들기 위해서는 매니페스트 파일에 permission 설정을 해야된다.
필요한 permission은 android.permission.SYSTEM_ALERT_WINDOW
아래부터 소스
소스 언어는 Kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkPermission()
}
fun checkPermission() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if(!Settings.canDrawOverlays(this)) {
val uri = Uri.fromParts("package", packageName, null)
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, uri)
startActivityForResult(intent, 0)
} else {
val intent = Intent(applicationContext, LockScreenService::class.java)
startForegroundService(intent)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 0) {
if(!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "해라", Toast.LENGTH_LONG).show()
} else {
val intent = Intent(applicationContext, LockScreenService::class.java)
startForegroundService(intent)
}
}
}
}
MainActivity.kt
class LockScreenService : Service() {
private val receiver = object:BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent != null) {
when(intent.action) {
Intent.ACTION_SCREEN_OFF -> {
val newIntent = Intent(context, LockScreenActivity::class.java)
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(newIntent)
}
}
}
}
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
private final val ALARM_ID = "com.bluewhale.lockscreentest.lockscreen"
override fun onCreate() {
super.onCreate()
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(ALARM_ID, "잠금화면 테스트", NotificationManager.IMPORTANCE_DEFAULT)
nm.createNotificationChannel(channel)
val pending = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_CANCEL_CURRENT)
val notification = Notification.Builder(this, ALARM_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("잠금화면 서비스")
.setContentText("잠금화면 서비스 동작중")
.setContentIntent(pending)
.build()
startForeground(1, notification)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val filter = IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF)
registerReceiver(receiver, filter)
return Service.START_STICKY
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
}
LockScreenService.kt
class LockScreenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
//setTurnScreenOn(true)
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this, null)
} else {
this.window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
}
setContentView(R.layout.activity_lock_screen)
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
finish()
}
}
}
LockScreenActivity.kt
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bluewhale.lockscreentest">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".LockScreenActivity"
android:label="@string/title_activity_lock_screen"
android:theme="@style/AppTheme.NoActionBar"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:taskAffinity="com.bluewhale.lockscreentest.lockscreen"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".service.LockScreenService"
android:enabled="true"
android:permission="android.permission.SYSTEM_ALERT_WINDOW"
android:exported="false"/>
</application>
</manifest>
AndroidManifest.xml
매니페스트 파일을 보면 잠금화면 액티비티에 launchMode, excludeFromRecents, taskAffinity를 추가했다.
excludeFromRecents는 최근 화면에 기록되지 않는 것이고, taskAffinity를 따로 설정해주는 이유는 설정하지않으면 다른액티비티와 같은 Task에서 생성되기 떄문에 잠금화면을 종료하면 최근화면에서 이 앱이 사라지게 된다. taskAffinity를 따로 줘서 다른 Task에서 동작하게하면 그런 영향에서 벗어날 수 있다. 최근화면에서 사라지는 현상은 캐시슬라이드의 잠금화면에서도 나타나나는 현상이다.
SYSTEM_ALERT_WINDOW 권한을 획득하고 Service 자체 권한에도 SYSTEM_ALERT_WINDOW 권한을 부여한다.
이 권한을 부여하면 안드로이드의 '다른 앱 위에 그리기' 권한이 승인이 되어야 서비스가 실행된다. 그래서 MainActivity에서 권한이 없으면 권한을 획득하기 위한 코드가 있다.
LockScreenService에서 receiver의 코드중에 addFlag로 ACTIVITY_NEW_TASK가 있는데 이 Flag를 주지않으면 익셉션이 발생하게 된다. Service에서 Activity를 생성하기 위해서는 이 Flag를 꼭 줘야되도록 되어 있는 듯.
LockScreenActivity에서는 다른 여타 블로그에서 알 수 있듯이 ShowWhenLocked와 KeyguardDismiss 를 수행해준다.
여기서 핵심은 SYSTEM_ALERT_WINDOW 권한이고 '다른 앱 위에 그리기' 권한을 사용자에게 승인하도록 하는 것이다.
혹시 나와 같이 해결법을 찾지 못한 사람들에게 도움이 되길 바란다.
'android' 카테고리의 다른 글
새로운 버전의 로그캣 (0) | 2024.04.17 |
---|---|
[Kotlin] 코틀린 문법 총정리 (0) | 2023.09.11 |
Android Studio에서 디버그 서명 인증서 SHA-1 확인하는법 (0) | 2023.08.21 |
[Firebase] Android Gradle 연동 문제 - build gradle plugin id com.google.gms:google-services (0) | 2023.04.26 |
안드로이드 공백 제거 (0) | 2022.05.31 |