Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Monday, April 15, 2024

Android add custom bitmap to Fresco image library cache

fun addCache(key: String, bitmap: Bitmap) {
    // add cache to memory
    val cacheKey = SimpleCacheKey(key)
    val imagePipeline = Fresco.getImagePipeline()
    val closeableBitmap = CloseableStaticBitmap(
        bitmap,
        {
            it.recycle()
        },
        ImmutableQualityInfo.FULL_QUALITY,
        0, 0
    )

    imagePipeline.bitmapMemoryCache.cache(cacheKey, CloseableReference.of(closeableBitmap))

    // add cache to disk
    val stream = ByteArrayOutputStream()
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
    val byteArray = stream.toByteArray()
    Fresco.getImagePipelineFactory().mainFileCache.insert(cacheKey) { outputStream ->
        outputStream.write(byteArray)
    }
}
 

Saturday, November 17, 2018

How to completely kill an app without it showing up in Android Task Manager

Here's how to completely kill an Android app without leaving the app's listing in the Task Manager.

moveTaskToBack(true);
if (android.os.Build.VERSION.SDK_INT >= 21) {
    finishAndRemoveTask();
} else {
    finish();
}
android.os.Process.killProcess(android.os.Process.myPid());

Existing data suggests using finish(), but starting with Android 6.0, you must call finishAndRemoveTask() so that the app does not remain in the task manager list after termination.

Sunday, November 11, 2018

Android Studio 3.1 Kotlin Support 빌드 에러

Android Studio 3.1에서 새로운 프로젝트 생성시 Kotlin Support를 활성화한 후 생성하면 다음과 같이 gradle sync가 에러나는 경우가 있다.

    Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.41.

    Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.41.

    Unable to resolve dependency for ':app@debugUnitTest/compileClasspath': Could not resolve org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.41.

    Unable to resolve dependency for ':app@release/compileClasspath': Could not resolve org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.41.

    Unable to resolve dependency for ':app@releaseUnitTest/compileClasspath': Could not resolve org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.41.

그럴때는 다음과 같이 app의 build.gradle에서 jre를 jdk로 수정하면 정상적으로 gradle sync 및 빌드가 된다.

    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

Android Google Drive API 예제

안드로이드에서 Google Drive API를 사용하는 예제이다.
구글에서 제공하는 예제넌 quick-start로서 해당 앱의 정보만 업로드하는 기능이고, 사용자의 전체 구글 드라이브의 정보를 리스팅하고 받아오지는 못한다.
우리는 사용자가 기존에 저장해 두었던 정보를 리스팅해 볼것이다.

아래의 페이지에서 사용할 package name과 keystore의 sha-1 키를 등록하자.
https://developers.google.com/drive/android/get-started

그리고 다음과 같이 소스코드를 구현한다. 단 package name은 위에서 등록한 package name이어야 한다.
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "GoogleDriveAndroidAPI";

    private final static int PERMISSION_CONTACTS = 1;
    private static final int REQUEST_AUTHORIZATION = 1;
    private static final int REQUEST_ACCOUNT_PICKER = 2;

    private final HttpTransport m_transport = AndroidHttp.newCompatibleTransport();
    private final JsonFactory m_jsonFactory = GsonFactory.getDefaultInstance();

    private GoogleAccountCredential m_credential;
    private Drive m_client;

    void startGoogleLogin() {
        // Google Accounts using OAuth2
        m_credential = GoogleAccountCredential.usingOAuth2(this, Collections.singleton(DriveScopes.DRIVE));

        m_client = new com.google.api.services.drive.Drive.Builder(
                m_transport, m_jsonFactory, m_credential).setApplicationName("AppName/1.0")
                .build();

        startActivityForResult(m_credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
    }

    boolean checkContactPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // get accounts 권한이 없으면 요청하자
            if (checkSelfPermission(Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(
                        new String[]{Manifest.permission.GET_ACCOUNTS,

                        },
                        PERMISSION_CONTACTS);
                return false;
            }
        }
        return true;
    }

    public void onRequestContactPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        Log.e("Example", "requestCode=" + requestCode);
        Log.e("Example", "permissions[0]=" + permissions[0]);
        Log.e("Example", "grantResults[0]=" + grantResults[0]);

        if (grantResults[0] == 0) {
            startGoogleLogin();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        onRequestContactPermissionsResult(requestCode, permissions, grantResults);
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (checkContactPermission()) {
            startGoogleLogin();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if ((requestCode == REQUEST_ACCOUNT_PICKER || requestCode == REQUEST_AUTHORIZATION)) {
            if (resultCode == RESULT_OK) {
                if (data != null && data.getExtras() != null) {
                    String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
                    if (accountName != null) {
                        m_credential.setSelectedAccountName(accountName);
                    }

                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            String pageToken = null;
                            do {
                                FileList result = null;
                                try {
                                    // GET_ACCOUNTS 권한을  수동으로 제어판 - 애플리케이션에서 설정해주어야 한다.
                                    result = m_client.files().list()
                                            .setQ("'root' in parents")
                                            .setPageSize(20)
                                            .setFields("nextPageToken, files(id, name)")
                                            .execute();
                                    List<File> files = result.getFiles();
                                    if (files == null || files.isEmpty()) {
                                        Log.e(TAG, "No files found.");
                                    } else {
                                        Log.e(TAG, "Files:");
                                        for (File file : files) {
                                            Log.e(TAG, String.format("%s (%s)\n", file.getName(), file.getId()));
                                        }
                                    }
                                    pageToken = result.getNextPageToken();
                                } catch (UserRecoverableAuthIOException e) {
                                    e.printStackTrace();
                                    startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            } while (pageToken != null);
                        }
                    }).start();
                }
                // call method to start accessing Google Drive
            } else { // CANCELLED
            }
        }
    }
}

Android Thread에서 Dialog 띄우기 (wait, notify 사용)

안드로이드에서 쓰레드 루프 중에 쓰레드를 멈추고 Modal Dialog를 받는 방법을 예제로 만들어 보았다. 동기화에 사용되는 오브젝트는 Object lock으로 wait/notify를 사용하였다.

package com.namjungsoo.www.androidthreadtest;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
    Object lock = new Object();
    void showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        AlertDialog dialog = builder
                .setTitle("title")
                .setMessage("message")
                .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        releaseLock();
                    }
                })
                .setNegativeButton("cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        releaseLock();
                    }
                })
                .setNeutralButton("skip", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        releaseLock();
                    }
                })
                .setCancelable(false)// 백버튼 불가, 바탕화면 클릭 불가
                .create();
        dialog.show();
    }
    void releaseLock() {
        synchronized (lock) {
            lock.notify();
            Log.e("TAG", "notify");
        }
    }
    Runnable runnable;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            showAlert();
                        }
                    });
                    Log.e("TAG", "before wait");
                    synchronized (lock) {
                        lock.wait();
                    }
                    Log.e("TAG", "after wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Button btn = (Button) findViewById(R.id.hello);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(runnable).start();
            }
        });
    }
}

Android Studio 3.0 새 프로젝트 빌드 에러 unable to resolve dependency

안드로이드 스튜디오 3.0이 출시되었다. 기존의 프로젝트들은 잘 동작하는데 새로운 프로젝트를 생성하였다면 새로운 프로젝트는 컴파일이 안되는 현상이 발생한다.
Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve com.android.support:appcompat-v7:26.0.0.
Unable to resolve dependency for ':app@debugAndroidTest/compileClasspath': Could not resolve com.android.support.test:runner:1.0.1.
...

이는 gradle plugin 버전이 3.0으로 높아졌고, gradle wrapper 버전이 4.1로 높아진 결과이다. 이 버전업으로 인한 변경점은 다음에 다루겠다.
이 문제를 수정하려면 일단 모듈 app에 대한 build.gradle을 열고 다음과 같이 되어 있는 항목을
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

다음과 같이 변경하면 된다. 변경한 부분은 최신버전이 아닌 부분을 자동으로 최신버전으로 적용하라고 정확한 버전 대신에 ‘+’를 붙여준 것이다.
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.+'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:+'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:+'
}

Android 8.0 notification channel

안드로이드 8.0에서는 제약사항으로 인해 notification channel을 생성하여야만 notification을 표시할 수가 있다.
아래는 notification channel 생성 예제이다.

class MainActivity : AppCompatActivity() {
    private fun createChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
            val channelMessage = NotificationChannel("channel_id", "channel_name", android.app.NotificationManager.IMPORTANCE_DEFAULT)
            channelMessage.description = "channel description"
            channelMessage.enableLights(true)
            channelMessage.lightColor = Color.GREEN
            channelMessage.enableVibration(true)
            channelMessage.vibrationPattern = longArrayOf(100, 200, 100, 200)
            channelMessage.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
            notificationManager.createNotificationChannel(channelMessage)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        createChannel()
        val extras = Bundle()
        extras.putString("title", "hello")
        extras.putString("message", "world")
        sendNotification(extras)
    }
    private fun sendNotification(extras: Bundle?) {
        Log.e("MainActivity", "sendNotification")
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val intent = Intent(this, MainActivity::class.java)
        intent.replaceExtras(extras)
        Log.e("MainActivity", "sendNotification intent size=$intent ${intent?.extras}")
        //PendingIntent.FLAG_UPDATE_CURRENT 추가
        //extras 전달을 위해서
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        val content = extras?.getString("message")
        val title = extras?.getString("title")
        val builder = NotificationCompat.Builder(this, "channel_id")
        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setContentText(content)
                .setContentTitle(title)
                .setContentIntent(pendingIntent)
        notificationManager.notify(3, builder.build())
    }
}

Thursday, November 8, 2018

Android MediaCodec MediaExtractor TextureView MP4 동영상 플레이하기

Android 내장 미디어 라이브러리인 MediaCodec, MediaExtractor 그리고 렌더링으로는 TextureView를 사용하여 간단히 MP4 동영상을 플레이 해보았다.

public class SurfaceTextureActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
    TextureView mTextureView;
    Surface mSurface;
    private PlayerThread mPlayer = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTextureView = new TextureView(this);
        mTextureView.setSurfaceTextureListener(this);

        setContentView(mTextureView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPlayer != null) {
            mPlayer.interrupt();
        }
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        Log.e("SurfaceTextureActivity", "onSurfaceTextureAvailable "+i + " " + i1);
        if (mPlayer == null) {
            mSurface = new Surface(mTextureView.getSurfaceTexture());
            mPlayer = new PlayerThread(mSurface);
            mPlayer.start();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
        Log.e("SurfaceTextureActivity", "onSurfaceTextureSizeChanged");
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        Log.e("SurfaceTextureActivity", "onSurfaceTextureDestroyed");
        if (mPlayer != null) {
            mPlayer.interrupt();
            return true;
        }
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        Log.e("SurfaceTextureActivity", "onSurfaceTextureUpdated");

    }
}

public class PlayerThread extends Thread {
    private MediaExtractor extractor;
    private MediaCodec decoder;
    private Surface surface;

    public PlayerThread(Surface surface) {
        this.surface = surface;
    }

    @Override
    public void run() {
        // api 16
        extractor = new MediaExtractor();
        try {
            extractor.setDataSource(SAMPLE);

            for (int i = 0; i < extractor.getTrackCount(); i++) {
                MediaFormat format = extractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("video/")) {
                    extractor.selectTrack(i);
                    decoder = MediaCodec.createDecoderByType(mime);

                    // Test surface disabled
                    decoder.configure(format, surface, null, 0);
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (decoder == null) {
            Log.e("DecodeActivity", "Can't find video info!");
            return;
        }

        decoder.start();

        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
        Log.e("Codec", "inputBuffers.length=" + inputBuffers.length + " outputBuffers.length=" + outputBuffers.length);

        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        boolean isEOS = false;
        long startMs = System.currentTimeMillis();

        //Test
        int frameNo = 0;

        while (!Thread.interrupted()) {
            Log.e("Codec", "presentationTimeUs=" + info.presentationTimeUs);
            if (!isEOS) {
                int inIndex = decoder.dequeueInputBuffer(100000);
                Log.e("Codec", "inIndex=" + inIndex);
                if (inIndex >= 0) {
                    // buffer is available
                    ByteBuffer buffer = inputBuffers[inIndex];

                    // get sample
                    int sampleSize = extractor.readSampleData(buffer, 0);

                    // end of stream
                    if (sampleSize < 0) {
                        // We shouldn't stop the playback at this point, just pass the EOS
                        // flag to decoder, we will get it again from the
                        // dequeueOutputBuffer
                        Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                        decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        isEOS = true;
                    } else {
                        long sampleTime = extractor.getSampleTime();
                        Log.e("Codec", "sampleTime=" + sampleTime);

                        decoder.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);

                        long target = sampleTime + 16670;
                        //extractor.seekTo(target, MediaExtractor.SEEK_TO_NEXT_SYNC);
                        extractor.advance();

                        frameNo++;
                        Log.e("Codec", "advance=" + frameNo + " target=" + target);
                    }
                }
            }

            int outIndex = decoder.dequeueOutputBuffer(info, 100000);
            Log.e("Codec", "outIndex=" + outIndex);
            switch (outIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
                    outputBuffers = decoder.getOutputBuffers();
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
                    break;
                default:
                    ByteBuffer buffer = outputBuffers[outIndex];
                    Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);

                    // We use a very simple clock to keep the video FPS, or the video
                    // playback will be too fast
                    while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                        try {
                            Log.e("Codec", "sleep 10");
                            sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }

                    decoder.releaseOutputBuffer(outIndex, true);
                    break;
            }

            // All decoded frames have been rendered, we can stop playing now
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                Log.e("Codec", "frameNo=" + frameNo);
                break;
            }
        }

        decoder.stop();
        decoder.release();
        extractor.release();
    }
}

Monday, October 29, 2018

Android 외장 SD카드 메모리 경로 획득

public static String getExternalSdCardPath() {
    File root = Environment.getExternalStorageDirectory();
    if (root == null)
        return null;

    File parent = root.getParentFile();
    if (parent == null)
        return null;

    File storage = parent.getParentFile();
    if (storage == null)
        return null;

    File[] files = storage.listFiles();
    for (File file : files) {
        String path = file.getName();
        if (path.equals("emulated"))
            continue;
        if (path.equals("self"))
            continue;
        return file.getAbsolutePath();
    }
    return null;
}


Friday, July 21, 2017

Android RecyclerView Ripple 이펙트

리스트 아이템의 layout에다가 다음을 추가한다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="70dp"
    android:background="?android:attr/selectableItemBackground">


Android 볼륨키 이벤트

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)){
            //Do something
        }
        return true;
    }

Thursday, July 20, 2017

Android 7.0 APK 파일 설치

프로그램 코드로 APK 파일을 설치해 보자. 안드로이드 7.0에서는 직접 외부 폴더(External Storage)의 APK를 설치할수 없다.
따라서 다음과 같이 FileProvider를 만들어야 한다.

xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

AndroidManifest.xml
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.duongame.XXX.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

JAVA
                final Intent intent = new Intent(Intent.ACTION_VIEW);
                final Uri apkURI = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", new File(item.path));
                intent.setDataAndType(apkURI, "application/vnd.android.package-archive");
                intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);

Tuesday, July 11, 2017

Android FragmentPagerAdapter vs FragmentStatePagerAdapter

FragmentPagerAdapter - 페이지 갯수가 고정적일때
About FragmentPagerAdapter Google's guide says:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

FragmentStatePagerAdapter - 페이지 갯수가 가변적일때
And about FragmentStatePagerAdapter:
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.

Thursday, June 15, 2017

Android ListView divider 없애기

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@null"
        android:dividerHeight="0dp"></ListView> 

Android RoundedImageView 예제

public class RoundedImageView extends ImageView {
    public RoundedImageView(Context context) {
        super(context);
    }

    public RoundedImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RoundedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final Path clipPath = new Path();

        final float radius = UnitConverter.dpToPx(5);
        final float padding = 0;
        final int w = this.getWidth();
        final int h = this.getHeight();

        clipPath.addRoundRect(new RectF(padding, padding, w - padding, h - padding), radius, radius, Path.Direction.CW);
        canvas.clipPath(clipPath);

        super.onDraw(canvas);
    }

Tuesday, April 25, 2017

Android OpenGL GLSurfaceView 예제

안드로이드에서 OpenGL ES를 사용하려면 가장 간단하게 GLSurfaceView를 사용하면 된다.
GLSurfaceView를 생성하고 GLSurfaceView.Renderer를 구현하면 기본적인 뼈대가 완성 된다.
아래의 예제는 화면을 빨간색으로 지우는 단순한 예제이다.

package com.duongame.opengl;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    GLSurfaceView glSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setRenderer(new MainRenderer());
        setContentView(glSurfaceView);

        //setContentView(R.layout.activity_main);
    }
}

package com.duongame.opengl;

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MainRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glClearColor(1,0,0,1);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

    }

    @Override
    public void onDrawFrame(GL10 gl) {
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    }
}

Sunday, December 18, 2016

Android EUC-KR 텍스트 파일 읽기 및 인코딩

안드로이드에서 TXT파일을 처리하다보면 EUC-KR 파일을 처리할때가 있다. 그럴때는 다음과 같이 UniversalDetector를 쓰면 되는데 gradle에 먼저 다음과 같이 추가해 주어야 한다.
    //encoding
    compile 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3'

그리고 다음과 같이 프로그래밍 하면 텍스트 파일 내용을 UTF-8 자바 문자열로 얻을 수 있다.
File file = new File(path);
FileInputStream is = new FileInputStream(file);

UniversalDetector detector = new UniversalDetector(null);
byte[] buffer = new byte[is.available()];
is.read(buffer);
is.close();
detector.handleData(buffer, 0, buffer.length);
detector.dataEnd();

String text = new String(buffer, detector.getDetectedCharset());

Thursday, July 14, 2016

Android Camera2 API 기능

간단히 Camera2 API가 할 수 있는 기능은

- Burst capture at full resolution at up to 30fps.
센서 풀 해상도로 초당 30프레임의 사진을 찍을 수 있습니다.

- RAW (dng) image capture (more on this later).
DNG 파일로 캡처할 수 있습니다.

- Full manual focus.
흉내만 낸 수동초점이 아닌 진짜 리얼 풀 매뉴얼 포커스가 가능합니다.

- Faster autofocus
좀더 빠른 자동초점을 잡습니다.

- A smoother viewfinder.
부드러운 미리보기를 볼 수 있습니다.

- Full resolution video.
센서 풀 해상도의 영상을 저장할 수 있습니다.

- No viewfinder swapping when switching between modes.
모드변경을 위해 미리보는 화면 변경되는 과정이 없습니다.

- Implement granular settings before capture.
세부적인 설정이 가능합니다.

- Request target frame rates.
프레임레이트를 정할 수 있습니다.

- Access to raw sensor data.
RAW 데이타에 접근할 수 있습니다. 이전에는 포스트프로세스 애프터프로세스를 통해 JPG로 저장되었죠

- Flash firing support
플래쉬 터뜨리는 방법을 조절할 수 있습니다. 상상이지만 모델링 플래쉬도 구현되겠죠?

- Video HDR
HDR 영상을 지원합니다.

- Focus stacking.
Burst로 한 사진에 여러 초점 사진을 찍을 수 있습니다.

- Exposure bracketing
Burst로 노출 브라켓팅 (여러 노출로 찍는 기법입니다)을 찍을 수 있습니다.

Wednesday, July 13, 2016

Android ANR 디버깅 방법

안드로이드에서 ANR 디버깅 방법
-Method profiling
-Thread monitor
-StrictMode 정의
Main Thread에서는 시간이 많이 소모될 수 있는, 동작을 규정하고 막을 수 있다.
그 규정은 안드로이드에서 제공하는 범위에서 개발자가 정한다.
그 규정 위반시 안드로이드에서 제공하는 범위에서 처리를 할 수 있다.
-Dropbox 사용(제조사, 에뮬레이터)

http://cluster1.cafe.daum.net/_c21_/bbs_search_read?grpid=1MWA2&fldid=aAfL&datanum=99&openArticle=true&docid=1MWA2%7CaAfL%7C99%7C20110712112022

Tuesday, July 12, 2016

Android RenderScript mono filter 예제

// mono.rs
#pragma version(1)
#pragma rs java_package_name(com.example.renderscriptsample)

float3 gMonoMult = {0.2125, 0.7154, 0.0721};

void root(const uchar4 *v_in, uchar4 *v_out) {
    float4 f4 = rsUnpackColor8888(*v_in);
    float3 mono = dot(f4.rgb, gMonoMult);
    *v_out = rsPackColorTo8888(mono);
}

MainActivity.java
package com.example.renderscriptsample;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.view.Menu;
import android.widget.ImageView;

public class MainActivity extends Activity {
 // RenderScript
 private RenderScript mRS;
 private Allocation mInAllocation;
 private Allocation mOutAllocation;
 private ScriptC_mono mScript;

 private Bitmap loadBitmap(int resource) {
  final BitmapFactory.Options options = new BitmapFactory.Options();
  options.inPreferredConfig = Bitmap.Config.ARGB_8888;
  return BitmapFactory.decodeResource(getResources(), resource, options);
 }

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // bitmap load
  final Bitmap bm = loadBitmap(R.drawable.data);
  final Bitmap bmOut = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());

  // renderscript init
  mRS = RenderScript.create(this);
  mInAllocation = Allocation.createFromBitmap(mRS, bm, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
  mOutAllocation = Allocation.createFromBitmap(mRS, bm, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
  mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono);

  // renderscript run
  mScript.forEach_root(mInAllocation, mOutAllocation);
  mOutAllocation.copyTo(bmOut);
 
  // set output bitmap
  final ImageView iv = (ImageView) findViewById(R.id.imageView1);
  iv.setImageBitmap(bmOut);

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

}