이번에는 간단한 오버레이 뷰를 만들어 보도록 하자


안드로이드 6.0이후의 기기는 오버레이 기능을 위해 퍼미션을 받아야 한다.


하지만 그 방식은 다른 퍼미션을 받는 방식과는 약간 다르다.


다른 퍼미션(카메라나 저장장치)는 퍼미션 허가창만 띄우면 가능하지만 오버레이는 설정창을 띄워주어야 한다.


코드로 확인한다면 아래와 같다.


public void openView() {
if(Settings.canDrawOverlays(this))
startService(new Intent(this, FloatingViewService.class));
else
onObtainingPermissionOverlayWindow();
}
public void onObtainingPermissionOverlayWindow() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQ_CODE_OVERLAY_PERMISSION);
}

오버레이 뷰를 실행할 때는  openView()함수를 호출할 예정이다.


Settings.canDrawOverlays(this)를 호출하면 현재 앱이 오버레이를 띄울수 있는지 확인하는 단계이다.


만일 현재 오버레이를 띄울수 있다면 오버레이를 호출하고, 띄울수없다면 오버레이 퍼이션을 받아야 한다.


오버레이 퍼미션은 onObtainingPermissionOverlayWindow()함수 내부 처럼 두줄로 받을 수 있다.


오버레이 퍼미션의 더 자세한 내용은 아래 블로그에서 확인 가능하다. 


꿈 많은 개발자가 되자 : http://thdev.tech/androiddev/2017/01/30/Android-Overlay-Permission.html





오버레이 뷰는 다른 액티비티가 종료되어도 동작해야하기 때문에 Service에서 동작시킨다. 서비스가 실행된다면 오버레이 뷰를 실행시킨다.



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

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
onTopView = inflater.inflate(R.layout.always_on_top_layout, null);
onTopView.setOnTouchListener(this);

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);

params.gravity = Gravity.LEFT | Gravity.TOP;

manager = (WindowManager) getSystemService(WINDOW_SERVICE);
manager.addView(onTopView, params);

Button closeBtn = onTopView.findViewById(R.id.close_this_window);
closeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
manager.removeView(onTopView);
onTopView = null;
stopSelf();
}
});
}

위는 서비스의 onCreate이다. 서비스가 시작되면 레이아웃을 설정하고 레이아웃 파라메터를 설정해주면 된다.


그리고 버튼은 현재 뷰를 지워주는 역활을 하게되는데 뷰와 서비스를 종료하면 된다.





마지막으로 뷰의 이동을 구현한다.


switch (action) {
case MotionEvent.ACTION_DOWN:
if (pointerCount == 1) {
xpos = motionEvent.getRawX();
ypos = motionEvent.getRawY();
}
break;
case MotionEvent.ACTION_MOVE:
if (pointerCount == 1) {
WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
float dx = xpos - motionEvent.getRawX();
float dy = ypos - motionEvent.getRawY();
xpos = motionEvent.getRawX();
ypos = motionEvent.getRawY();

Log.d(TAG, "lp.x : " + lp.x + ", dx : " + dx + "lp.y : " + lp.y + ", dy : " + dy);

lp.x = (int) (lp.x - dx);
lp.y = (int) (lp.y - dy);

manager.updateViewLayout(view,lp);
return true;
}
break;

}

onTouch내부에  switch함수를 넣어 구성하였다. layoutparam을 변경해주어 이동시키면 된다.



GitHub : https://github.com/pchan1401-ICIL/AlwaysOnTopViewTest

개발자가 실내에서 GPS를 이용한 프로그램을 개발하는 경우 GPS를 가상위치에 놓아야 하는 상황이 발생한다. 


이럴때 가장 쉬운 방법은 가상GPS프로그램을 사용하면 된다.


하지만, 이러한 프로그램은 자신의 위치를 계속적으로 컨트롤 하거나 개발자가 필요한 기능을 가지지 않는 경우가 있다.


예를들어 반복적인 위치 움직임을 필요로하는 경우가 있다. 이러한 프로그램은 찾아도 없었다. 따라서 직접 만들어보았다.





해당 프로그램은 백그라운드 작업을 필요로한다. 액티비티가 종료되어도 사용자의 위치는 계속적으로 나타나져야 한다. 따라서 아래와 같이 서비스를 이용하였다.

public class LocationUpdateService extends IntentService




아래는 본격적인 가상위치를 제공하는 코드이다.

public MockLocationProvider(String name, Context ctx) {
providerName = name;
mctx = ctx;

LocationManager lm = (LocationManager) ctx.getSystemService(
Context.LOCATION_SERVICE);
lm.addTestProvider(providerName, true, true, true, true, true,
true, true, Criteria.NO_REQUIREMENT, Criteria.ACCURACY_FINE);
lm.setTestProviderEnabled(providerName, true);
}

가상위치를 설정해주기 위하여 초기화하는 부분이다. addTestProvider함수로 가상위치를 사용할 준비를 한다.


addTestProvider함수는 여러변수를 가지는데 위치제공자의 이름, 필요한 것, 제공하는 값을 나타낸다. 자세한 내용은 아래 링크를 참조하면 된다.



https://developer.android.com/reference/android/location/LocationManager.html#addTestProvider(java.lang.String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int)





아래는 가상위치의 값들을 설정하는 코드이다.

public void pushLocation(double lat, double lon, double alt) {
try {
LocationManager lm = (LocationManager) mctx.getSystemService(
Context.LOCATION_SERVICE);

Location mockLocation = new Location(providerName);
long currentTime = System.currentTimeMillis();
mockLocation.setLatitude(lat);
mockLocation.setLongitude(lon);
mockLocation.setAltitude(alt);
mockLocation.setTime(currentTime);
mockLocation.setAccuracy(1.0f);
mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());

lm.setTestProviderStatus(providerName, LocationProvider.AVAILABLE, mockLocation.getExtras(), currentTime);
lm.setTestProviderLocation(providerName, mockLocation);
} catch(RuntimeException e){
e.printStackTrace();
}
}

여러 값을 설정하고 이 값들을 setTestProviderLocation에 넣으면 GPS값이 출력된다.


pushLocation을 지속적으로 호출하여 사용자의 위치를 계속 갱신한다.






마지막으로 Manifest파일에 가상위치를 사용한다고 권한을 설정해주어야한다.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

ACCES_COARSE_LOCATION과 ACCES_FINE_LOCATION은 위치제공자를 사용하기 위해 필요하다.


ACCES_MOCK_LOCATION은 가상위치를 사용하기 위해 필요하다.






테스트를 하기 위하여 위치사용허가를 해야한다. 


설정 > 시스템 > 개발자옵션 > 모의위치 앱에서 TestMockLocation을 선택한다.


이 내용은 안드로이드 버전에서 약간씩 다르다.






아래 화면에서 버튼을 누르면 가상 위치가 실행된다.


버튼을 누르면 실행


이 상태에서 지도(카카오맵)을 켜면 사용자의 위치가 나타난다.


카카오맵


위치값을 읽는 코드를 이용하여 제대로 작동하는지 확인한다.


이 코드는 아래 github링크를 놓겠다.


받는 데이터




마지막 사진에서 가상위치라고 isFromMockProvider가 true로 나온다. 실제 위치일 경우 false가 나타난다.




이처럼 개발자는 사무실에 있는데 광화문 한복판에 있다.


이러한 기능을 이용하여 GPS를 사용한 앱의 디버깅을 쉽게 할 수 있다.






아래는 참고한 웹페이지이다.


https://mobiarch.wordpress.com/2012/07/17/testing-with-mock-location-data-in-android/





Github


가상위치 : https://github.com/pchan1401-ICIL/TestMockLocation


GPS읽기 : https://github.com/pchan1401-ICIL/LocationReader










코드 : https://github.com/pchan1401-ICIL/Camera1_2_Compare



이번 글은 단순히 Camera2와 Camera API의 Fov를 비교하는 글이다.


최근에 Camera2를 사용하던 코드른 Camera API로 변환하는 과정에서 어떠한 값을 나타내는지 비교하는 것이다.


Camera 코드의 Fov를 구하는 코드는 많이 있으니 생략한다.


두 API의 차이점은 아래와 같다.


 - Camera : API가 이미 존재하여 값을 불러온다.

 - Camera2 : 센서의 크기를 이용하여 계산한다.


그리고 결과는 아래와 같다.


갤럭시탭 S3


한번 다른 기기를 이용해보았다


갤럭시 노트4




삼성기기만 그러한지는 모르겠으나 두 기기를 비교해보면 Camera와 Camera2의 FOV출력은 정확히 같은계산을 하는 것 같다.






안드로이드에서 플레이하기 위해서 VLC라이브러리를 빌드해야 한다.


빌드 방법은 VLC공식 사이트에 나와있다.


https://wiki.videolan.org/AndroidCompile/


리눅스에서 빌드를 해야하기 때문에 번거롭다.


그래서 아래에 빌드 결과물을 받을 수 있도록 하였다. 이 라이브러리는 구버전이기 때문에 직접 받아서 빌드하기를 추천한다.


libvlc.z01

libvlc.zip



이렇게 얻은 libvlc를 프로젝트에 추가한다.




우선 액티비티에 여러 인자를 Implement한다.

public class MainActivity extends AppCompatActivity implements IVLCVout.Callback, LibVLC.HardwareAccelerationError, TextureView.SurfaceTextureListener 

IVLCVout.Callback, LibVLC.HardwareAccelerationError, TextureView.SurfaceTextureListener를 implement한 이유는 콜백함수를 사용하기 위함이다.


각각의 기능은 아래에 설명한다.




IVLCVout.Callback는 비디오 실행시 콜백함수에 해당한다. 이 것을 위하여 아래의 세 함수를 작성해 주어야 한다.

@Override //VLC 레이아웃 설정
public void onNewLayout(IVLCVout vout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
if (width * height == 0)
return;
// store video size
mVideoWidth = width;
mVideoHeight = height;
setSize(mVideoWidth, mVideoHeight);
}

@Override
public void onSurfacesCreated(IVLCVout ivlcVout) {

}

@Override
public void onSurfacesDestroyed(IVLCVout ivlcVout) {

}

위 세 함수는 각각 새 레이아웃 생성시, 화면 생성시, 화면 제거시 각각 동작한다.


비디오를 실행하면 비디오의 크기를 변경해주어야 한다. 비디오 사이즈는 TextureView의 사이즈를 변경해주면 된다.


LibVLC.HardwareAccelerationError는 하드웨어 가속에 에러가 생기면 어떠한 동작을 하는지 나타낸다.

@Override  //하드웨어 가속 에러시 플레이어 종료
public void eventHardwareAccelerationError() {
releasePlayer();
Toast.makeText(this, "Error with hardware acceleration", Toast.LENGTH_LONG).show();
}

하드웨어 가속중 에러가 생기면 플레이어를 종료하고 에러 토스트 메시지를 띄운다.


마지막으로 SurfaceTextureListener는 TextureView의 상태에 따라 동작하는 함수이다.

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

}

@Override //SurfaceTexture 화면(동영상 해상도 및 사이즈)에 따라 크기 변경
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
setSize(mVideoWidth, mVideoHeight);
}

@Override //SurfaceTexture 화면이 종료되었을때 종료
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if(!(mMediaPlayer == null))
mMediaPlayer.release();
return true;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {

}

각각 텍스쳐뷰의 사용가능, 텍스쳐뷰의 크기 변화, 텍스쳐뷰의 제거, 텍스쳐뷰의 업데이트 시에 실행한다.


크기가 변하면(orientation의 변화) 동영상의 크기를 변화시킨다.


또한 텍스쳐뷰가 제거가 되면 비디오를 종료한다.



다음은 Mediaplayer.EventListener이다. 재생중에 어떠한 일이 발생하면 실행하는 함수이다.

//미디어 플레이어 리스너 클래스
private static class MyPlayerListener implements MediaPlayer.EventListener {
private WeakReference<MainActivity> mOwner;

//액티비티 변수를 받아오기 위하여 지정
private MyPlayerListener(MainActivity owner) {
mOwner = new WeakReference<>(owner);
}

@Override
public void onEvent(MediaPlayer.Event event) {
MainActivity player = mOwner.get();

switch(event.type) {
case MediaPlayer.Event.EndReached:
player.releasePlayer();
break;
case MediaPlayer.Event.Playing:
player.mSeekBar.setMax((int) player.mMediaPlayer.getLength());
player.mSeekBar.setOnSeekBarChangeListener(player.mSeekListener);
break;
case MediaPlayer.Event.Paused:
break;
case MediaPlayer.Event.Stopped:
break;
case MediaPlayer.Event.PositionChanged:
player.mSeekBar.setProgress((int)player.mMediaPlayer.getTime());
default:
break;
}
}
}

이 함수는 동영상이 플레이되는 중에 계속 호출된다.





아래는 VLC플레리어 실행을 위한 코드이다. 초기화 정보가 들어있다.

//VLC 플레이어 실행
private void createPlayer(String media) {
releasePlayer();
try {
if (media.length() > 0) {
Toast toast = Toast.makeText(this, media, Toast.LENGTH_LONG);
toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0,
0);
toast.show();
}

// Create LibVLC
ArrayList<String> options = new ArrayList<>();
//options.add("--subsdec-encoding <encoding>");
options.add("--aout=opensles");
options.add("--audio-time-stretch"); // time stretching
options.add("-vvv"); // verbosity
libvlc = new LibVLC(options);
libvlc.setOnHardwareAccelerationError(this);

mTexture.setKeepScreenOn(true);

// Create media player
mMediaPlayer = new MediaPlayer(libvlc);
mMediaPlayer.setEventListener(mPlayerListener);

// Set up video output
final IVLCVout vout = mMediaPlayer.getVLCVout();
vout.setVideoView(mTexture);
//vout.setSubtitlesView(mSurfaceSubtitles);
vout.addCallback(this);
vout.attachViews();

Media m = new Media(libvlc, media);
mMediaPlayer.setMedia(m);
mMediaPlayer.play();

} catch (Exception e) {
Toast.makeText(this, "Error creating player!", Toast.LENGTH_LONG).show();
}
}

코드를 요약하자면 비디오 실행 옵션설정, 콜백함수 설정, 플레이 할 비디오를 설정하고 재생시킨다.


여러가지 옵션는 VLC홈페이지에 나와있다.



VLC의 미디어플레이어는 안드로이드의 미디어플레이어와 사용법이 유사하다.


게다가 여러 재생옵션 기능을 제공한다. 


하지면 여러 콜백함수가 없어서 필요한 코드는 작성해주어야 한다.


이제 프로그램을 실행하고 play버튼을 누르면 동영상이 재생되면서 볼 수 있다.


모든 코드는 Github에 올려져있다.


Github : https://github.com/pchan1401-ICIL/VLCTestProject



출처 : http://androidarena.co.in/how-to-clear-or-kill-stack-of-activity-android/





Intent intent = new Intent(getApplicationContext(), NewActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

startActivity(intent);


새로운 액티비티 스택을 지우면서 새로운 액티비티를 생성하려면 인텐트에 플래그에 입력한다.


인터넷에서는 작동하는 경우도 있다.


하지만 어떤경우에서 작동이 안하는 경우가 있는데 아마 백그라운드에서 애니메이션처리같이 계속 동작하는 액티비티는 중지하지 않는 것 같다.


이런 경우 AndroidManifests.xml에 지워야 할 액티비티에 아래와같이 추가하면 된다.


android:noHistory="true"

위와 같이 추가하고 실행하면 액티비티 스택에 저장되지 않는다.


http://stackoverflow.com/a/21375102


위의 링크에서도 finish() 보다는 더 적절한 noHistory 설정을 적용시키는게 옳다고 나온다.

기존의 Camera API는 카메라를 카메라로 보았다면


Android 5.0이상에서는 카메라는 센서처럼 취급한다.


camera2 api 강좌 : http://myandroidarchive.tistory.com/1


우선 카메라의 화각 계산 공식은 아래 사이트에서 볼 수 있다.


Calculation Field of View : http://www.bobatkins.com/photography/technical/field_of_view.html


이전 코드와 변경사항은 아래 코드가 추가되었다는 점이다.


float horizonalAngle;
float verticalAngle;

private void calculateFOV(CameraManager cManager) {
try {
for (final String cameraId : cManager.getCameraIdList()) {
CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cameraId);
int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING);
if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) {
float[] maxFocus = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
SizeF size = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
float w = size.getWidth();
float h = size.getHeight();
horizonalAngle = (float) (2*Math.atan(w/(maxFocus[0]*2)));
verticalAngle = (float) (2*Math.atan(h/(maxFocus[0]*2)));
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

먼저, LENS_INFO_AVAILABLE_FOCUS_LENGTHS는 카메라가 사용가능한 렌즈의 초점거리를 얻기 위하여 사용하였다.


다음으로 SENSOR_INFO_PHYSICAL_SIZE는 센서의 크기를 불러오기 위해 사용하였다.


이 값을 이용하여 수직, 수평 FOV각도 값을 구할 수 있다.


FOV (rectilinear) =  2 * arctan (frame size/(focal length * 2))



GitHub : https://github.com/pchan1401-ICIL/Camera2FOV

+ Recent posts