본문 바로가기

프로그램개발

[Android] 다른 앱 위에 그리기 (With Service, Overlay View)

다른 앱 위에 그리기

 

 

Service 를 이용하여 최상위 화면에 항상 이미지를 그리는 방법을 알아보자.


AndroidManifest 파일을 설정하자.

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
	
<service
    android:name=".AwindowService"
    android:enabled="true"
    android:permission="android.permission.SYSTEM_ALERT_WINDOW">
</service>

다른 앱 위에 그리기 권한과

Android O 이상에서 서비스를 실행하기 위한 Foreground Service 권한을 설정한다.

 

public class MainActivity extends AppCompatActivity {
					.................

public void checkPermission() {
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {   // 마시멜로우 이상일 경우
		if (!Settings.canDrawOverlays(this)) {              // 다른앱 위에 그리기 체크
			Uri uri = Uri.fromParts("package" , getPackageName(), null);
			Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, uri);
			startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
        	} else {
        		startMain();
        	}
	} else {
		startMain();
	}
}

@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
    	if (!Settings.canDrawOverlays(this)) {
        	finish();
        } else {
        	startMain();
        }
    }
}


void startMain(){
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
		startForegroundService(new Intent(this, AwindowService.class));
	} else {
		startService(new Intent(this, AwindowService.class));
	}
}
					.................

다른 앱위에 그리기 권한을 사용자에게 요구하는 소스 코드이다.

권한 획득 후에 서비스를 실행한다.

Android O 이상에서는 ForeGround Service를 호출한다.

 

 

public class AwindowService extends Service {
				...................
                
	@Override
    public IBinder onBind(Intent intent) { return null; }

    @Override
    public void onCreate() {
        super.onCreate();
		
        // Android O 이상일 경우 Foreground 서비스를 실행
        // Notification channel 설정.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 
            final String strId = getString(R.string.noti_channel_id);
            final String strTitle = getString(R.string.app_name);
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel channel = notificationManager.getNotificationChannel(strId);
            if (channel == null) {
                channel = new NotificationChannel(strId, strTitle, NotificationManager.IMPORTANCE_HIGH);
                notificationManager.createNotificationChannel(channel);
            }

            Notification notification = new NotificationCompat.Builder(this, strId).build();
            startForeground(1, notification);
        }

        LayoutInflater inflate = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        // inflater 를 사용하여 layout 을 가져오자
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 윈도우매니저 설정

        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O? 
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                // Android O 이상인 경우 TYPE_APPLICATION_OVERLAY 로 설정
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT);


        params.gravity = Gravity.LEFT|Gravity.CENTER_VERTICAL;
        // 위치 지정
        
        View mView = inflate.inflate(R.layout.view_in_service, null); 
        // view_in_service.xml layout 불러오기
        // mView.setOnTouchListener(onTouchListener);
		// Android O 이상의 버전에서는 터치리스너가 동작하지 않는다. ( TYPE_APPLICATION_OVERLAY 터치 미지원)
		
        
        final ImageButton btn_img =  (ImageButton) mView.findViewById(R.id.btn_img);
        btn_img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("test","onClick ");
                // do something!
            }
        });
        
        // btn_img 에 android:filterTouchesWhenObscured="true" 속성 추가하면 터치리스너가 동작한다.
        btn_img.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.d("test","touch DOWN ");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.d("test","touch UP");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.d("test","touch move ");
                        break;
                }
                return false;
            }
        });
        wm.addView(mView, params); // 윈도우에 layout 을 추가 한다.
    }

	@Override
    public void onDestroy() {
        super.onDestroy();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true); // Foreground service 종료
        }

        if(wm != null) {
            if(mView != null) {
                wm.removeView(mView); // View 초기화
                mView = null;
            }
            wm = null;
        }
    }

}

Android 8.0 이상 부터는

ForeGround Service를 실행하기 위한 Notification channel 을 생성한다.

또한 TYPE_APPLICATION_OVERLAY 를 기본으로 설정하여야 한다.

* TYPE_APPLICATION_OVERLAY를 사용하면 터치 이벤트가 발생하지 않아 View의 위치이동이 불가하다. * 

 

android:filterTouchesWhenObscured="true"

뷰에 위 속성 값을 추가하면 터치 이벤트가 발생하는 것을 확인 할수 있다.

 

<ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_img"
        android:text="click"
        android:filterTouchesWhenObscured="true"
        android:textColor="#ffffff"
        android:src="@drawable/arrow"
        android:background="@null"
        android:scaleType="centerCrop"/>

결과 화면

 

안드로이드 9.0 기기에서 테스트한 결과이다.

 

홈화면에서 지정된 이미지가 표시되며

 

또한 터치이벤트를 이용하여 사용자가 이미지를 드래그시 동작하는 것을 확인할 수 있다.

 

댓글