ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android Floation Widget 구현하기
    Android 2021. 9. 25. 17:27
    test

    요구사항 및 구현 방법


     

    앱 밖에 표시되는 즉 안드로이드 디바이스 화면 에 표시되는 view를 만들고싶다.

    1. 화면위에 그리기 원한을 사용한다.
    2. 서비스에서 view를 호출한다.

     

    구현


    1. 권한

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    화면위에 그리기 권한과
    안드로이드 오레오 이상에서 서비스를 호출하기위한 포그라운드 서비스 등록

    2. 권한획득 및 서비스 호출

    class MainActivity : AppCompatActivity() {
        private val serviceIntent by lazy {  Intent(this, ImmortalService::class.java)  }
        companion object {
            private const val ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 1004
        }
    
        @RequiresApi(Build.VERSION_CODES.M)
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            checkPermission()
        }
        @RequiresApi(Build.VERSION_CODES.M)
        private fun checkPermission() {
    
            when{
                Settings.canDrawOverlays(this) -> {
                    callService()
                }
                else->{
                    permissionPopup()
                }
            }
    
        }
        @RequiresApi(Build.VERSION_CODES.M)
        private fun permissionPopup(){
            AlertDialog.Builder(this@MainActivity)
                    .setTitle("권한이 필요합니다.")
                    .setMessage("화면위의 그리기 권한이 필요")
                    .setPositiveButton("확인") { _, _ ->
                        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 
                                            Uri.parse("package:$packageName"))
                        startActivityForResult(intent, 
                                            ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE)
                    }
                    .setNegativeButton("취소") { _, _ -> }
                    .create().show()
    
        }
        private fun callService() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(serviceIntent)
            } else {
                startService(serviceIntent)
            }
        }
        @RequiresApi(api = Build.VERSION_CODES.M)
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
                callService()
            }
        }
    }

    MainActivity에서는 화면위에 그리기 권한과 서비스 호출을 하고있다.

    3. 서비스 구현

    class ImmortalService : Service() {
    
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
    
        override fun onCreate() {
            super.onCreate()
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channelID = "채널id"
                val strTitle = "타이틀"
    
                val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                var channel = notificationManager.getNotificationChannel(channelID)
                var builder: NotificationCompat.Builder? = null
                var notification: Notification? = null
                if (channel == null) {
                    channel = NotificationChannel(
                        channelID,
                        strTitle,
                        NotificationManager.IMPORTANCE_HIGH
                    )
                    notificationManager.createNotificationChannel(channel)
                }
                builder = NotificationCompat.Builder(this, channelID)
                builder.setSmallIcon(R.mipmap.ic_launcher)
                builder.setContentText("노티 텍스트")
                notification = builder.build()
                notificationManager.notify(1, notification)
                startForeground(1, notification)
            }
            initView()
        }
    
        fun initView(){
            // inflater 를 사용하여 layout 을 가져오자
            val inflate = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            // 윈도우매니저 설정
            val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
            val params = WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 
                else WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,  // Android O 이상인 경우 TYPE_APPLICATION_OVERLAY 로 설정
                
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT
            )
            // 위치 지정
            params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
    
            // activity_overlay.xml 불러오기
            val mView = inflate.inflate(R.layout.activity_overlay,null)
            val overlayButton :Button = mView.findViewById(R.id.btn)
            overlayButton.setOnClickListener{
                Log.d("오버레이", "initView: 버튼 클릭")
            }
            windowManager.addView(mView,params)
    
        }
    
    }

    해당 서비스의 역활은 죽지않는 서비스를 위한 Notification channel 생성과
    xml 파일을 WindowManager에게 적용시켜
    앱 밖에 표시되도록 한다.

    4. 앱 밖에 표시될 화면 구현

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        xmlns:android="http://schemas.android.com/apk/res/android">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btn"
            android:text="click"
            android:textColor="#ffffff"
            android:layout_marginTop="12dp" />
    
    </LinearLayout>



    결과