activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="World!"/>

    <LinearLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#c1cfba"/>

</LinearLayout>

 

MainActivity.kt

 

package kr.co.test

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button1.setOnClickListener {
            val button1Fragment = Button1Fragment()
            val manager = supportFragmentManager
            val transaction = manager.beginTransaction()

            transaction.replace(R.id.fragment_container, button1Fragment)
            transaction.addToBackStack(null)
            transaction.commit()
        }

        button2.setOnClickListener {
            val button2Fragment = Button2Fragment()
            val manager = supportFragmentManager
            val transaction = manager.beginTransaction()

            transaction.replace(R.id.fragment_container, button2Fragment)
            transaction.addToBackStack(null)
            transaction.commit()
        }
    }
}

 

fragment_button1.xml

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/button1_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:textSize="50sp" />

</LinearLayout>

 

Button1Fragment.kt

 

package kr.co.test

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment

class Button1Fragment : Fragment(){

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_button1, container, false)
        val tv = view.findViewById<TextView>(R.id.button1_text_view)

        tv.setOnClickListener{
            tv.setTextColor(Color.RED)
            Toast.makeText(view.context,"TextView clicked.", Toast.LENGTH_SHORT).show()
        }

        return view
    }
}

 

fragment_button2.xml

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/button2_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="World!"
        android:textSize="50sp" />

</LinearLayout>

 

Button2Fragment.kt

 

package kr.co.test

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment

class Button2Fragment : Fragment(){

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_button2, container, false)
        val tv = view.findViewById<TextView>(R.id.button2_text_view)

        tv.setOnClickListener{
            tv.setTextColor(Color.GREEN)
            Toast.makeText(view.context,"TextView clicked.", Toast.LENGTH_SHORT).show()
        }

        return view
    }
}

 

 

결과

 

 

참고사이트

https://android--code.blogspot.com/2018/02/android-kotlin-fragment-example.html

 아래 그림과 같이 EditText(TextView)에 이미지를 추가하고 싶을 때가 있다. 

 

 그럴 때 다음과 같이 xml 파일의 EditText 태그에서 drawbleStart(왼쪽에 이미지 추가), drawbleEnd(오른쪽에 이미지 추가) 속성을 추가한 다음 원하는 이미지를 등록함으로써 해결할 수 있다. 또한, drawblePadding 속성을 추가하여 이미지와 텍스트 사이에 거리를 줄 수 있다.

<EditText
android:layout_width="300dp"
android:layout_height="wrap_content"
android:hint="WiFi 선택"
android:inputType="text"
android:drawableStart="@drawable/ic_wifi"
android:drawableEnd="@drawable/ic_next"
android:drawablePadding="10dp"/>

'Android' 카테고리의 다른 글

[Android Kotlin] 음성 녹음  (0) 2019.10.04
[Android Kotlin] MobileFFmpeg 사용법  (0) 2019.09.25

 프로젝트 진행 중 프로그래밍을 통해 통해 무선 랜카드의 모드를 managed 모드에서 AP 모드로 바꿔야하는 일이 생겼다. 그래서 Command Line에서 무선 랜카드의 모드를 AP 모드로 바꾸는 방법을 찾아보았고 다음과 같은 방법으로 해결할 수 있었다.

 

 먼저 WiFi AP 모드를 설정하기 위해서는 먼저 가지고 있는 무선 랜카드가 AP 모드를 지원해야한다. AP 모드 지원 여부를 확인하는 방법은 아래와 같이 Command Line에서 iw list 명령을 실행한 다음 Supported interface modes에서 AP가 있는지 확인하면 된다.

 

※ 참고로 내가 사용하는 무선 랜카드는 ipTime의 A3000U이다.

 

 

 무선 랜카드가 AP 모드를 지원하는 것을 확인하였으면 먼저 hostapd를 설치한다. hostapd는 네트워크 인터페이스 카드를 AP 또는 인증 서버 역활을 할 수 있게하는 데몬이다.

sudo apt install hostapd

 

그리고 /etc/hostapd/hostapd.conf 파일을 아래와 같이 작성한다.

interface=wlx88366cfb576c
driver=nl80211
ssid=Catch Catch
hw_mode=g
channel=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=3
wpa_passphrase=1234567890
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

 위의 작성한 부분에서 interface=wlx88366cfb576c 부분은 자신의 무선 랜카드의 인터페이스 이름을 적어줘야 한다. 무선 랜카드의 인터페이스 이름을 확인하는 방법은 아래와 같이 ifconfig 명령어로 확인할 수 있다.

 

 

 그리고 ssid=Catch Catch 부분은 다른 사람들에게 보여줄 ssid를 쓰면되고 wpa_passphrase=1234567890 부분은 내 AP에 접속할 때 필요한 비밀번호로 원하는 비밀번호로 변경하면 된다.

 

 이제 /etc/default/hostapd 파일에서 아래와 같이 DAEMON_CONF="/etc/hostapd/hostapd.conf"를 추가해 hostapd 설정 파일의 위치를 /etc/hostapd/hostapd.conf로 지정해준다.

 

 

 이제 hostapd에서 해야할 작업은 끝났다. 다음으로는 내 AP에 접속하는 사용자들에게 IP를 동적으로 할당해주기 위한 DHCP 서버를 설정해야한다.

 

 먼저 isc-dhcp-server를 설치한다.

sudo apt install isc-dhcp-server

 그리고 /etc/default/isc-dhcp-server 파일을 열어 마지막 줄에 INTERFACES="wlx88366cfb576c"와 같이 DHCP 요청을 처리할 인터페이스를 추가한다.

 

 

 그리고 /etc/dhcp/dhcpd.conf 파일을 열어 아래 보이는 부분들을 주석처리한다.

 

 

 그리고 마지막 줄에 아래 내용을 추가한다. 간단하게 내용을 살펴보면 내 AP에 접속한 기기들에게 10.10.0.2 ~ 10.10.0.16 사이에 있는 IP를 할당해주고 라우터(내 AP) IP는 10.10.0.1이다.

subnet 10.10.0.0 netmask 255.255.255.0 {
        range 10.10.0.2 10.10.0.16;
        option domain-name-servers 8.8.8.8;
        option routers 10.10.0.1;
}

 그리고 /etc/network/interfaces 파일을 열어 아래 부분을 추가해준다. wlx88366cfb576c는 AP가 될 인터페이스 이름을 입력하면 된다.

auto wlx88366cfb576c
iface wlx88366cfb576c inet static
address 10.10.0.1
netmask 255.255.255.0

 이제 아래 명령어로 isc-dhcp-server와 hostapd를 실행시킨다.

sudo service isc-dhcp-server start
sudo service hostapd start

 그리고 재부팅 후 iwconfig로 무선 랜카드의 상태를 보면 Mode가 Master로 변한 것을 확인할 수 있고 다른 장치에서 와이파이 목록을 확인해보면 내가 만든 AP가 보이는 것을 확인할 수 있다. 그리고 연결하면 IP가 10.10.0.2 ~ 10.10.0.16 사이에서 할당받는 것을 확인할 수 있다.

 

※ sudo service hostapd start 명령어 실행 시 Failed to start hostapd.service: Unit hostapd.service is masked. 오류가 발생할 경우 Command Line에서 아래 명령어를 입력한다.

sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo systemctl start hostapd

 마지막으로 AP에 접속한 기기들이 인터넷을 할 수 있도록 IP forwarding과 IP masquerading 해줘야한다. 먼저 /etc/sysctl.conf/ 파일을 열어 # net.ipv4.ip_forward=1 이 부분을 찾아 net.ipv4.ip_forward=1로 변경해 IP forwarding을 해준다. 

 

 그리고 iptables-persistent를 설치한 다음

sudo apt install iptables-persistent

 sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/16 -o ens33 -j MASQUERADE 명령어를 입력해 IP masquerading을 해준다. 이때 ens33은 인터넷이 가능한 인터페이스 이름이다. 그리고 sudo netfilter-persistent save 명령어로 추가한 정책을 저장한다.

 

 이제 재부팅 후 다른 디바이스 장치에서 내가 만든 AP에 접속하면 인터넷이 되는 것을 확인할 수 있다.

 

 

참고사이트

 

https://medium.com/@arnab.k/ubuntu-how-to-setup-a-wi-fi-hotspot-access-point-mode-192cbb2eeb90

https://askubuntu.com/questions/119393/how-to-save-rules-of-the-iptables

https://github.com/raspberrypi/documentation/issues/1018

 Ubuntu 18.04 버전에서 이번에 새로 구매한 ipTIME A3000U를 사용하려고 하는데 드라이버가 자동으로 설치되지 않아 설치하는 방법을 찾아보았고 터미널에서 아래와 같이 입력하여 드라이버를 설치할 수 있었다.

 

sudo apt-get update

sudo apt-get install build-essential dkms git

sudo git clone https://github.com/cilynx/rtl88x2BU_WiFi_linux_v5.3.1_27678.20180430_COEX20180427-5959.git

cd rtl88x2BU_WiFi_linux_v5.3.1_27678.20180430_COEX20180427-5959

VER=$(sed -n 's/\PACKAGE_VERSION="\(.*\)"/\1/p' dkms.conf)

sudo rsync -rvhP ./ /usr/src/rtl88x2bu-${VER}

sudo dkms add -m rtl88x2bu -v ${VER}

sudo dkms build -m rtl88x2bu -v ${VER}

sudo dkms install -m rtl88x2bu -v ${VER}

sudo modprobe 88x2bu

 

 다 입력하였으면 이제 A3000U를 연결한 다음 터미널에서 ifconfig를 입력하면 아래와 같이 A3000U에 대한 인터페이스를 확인할 수 있다. 

 

 드라이버 설치에 관한 자세한 내용은 https://github.com/cilynx/rtl88x2BU_WiFi_linux_v5.3.1_27678.20180430_COEX20180427-5959의 README.md에서 확인할 수 있다.

 

 

참고사이트

 

1. http://blog.naver.com/PostView.nhn?blogId=namhong2001&logNo=221548649952&parentCategoryNo=&categoryNo=56&viewDate=&isShowPopularPosts=true&from=search

2. https://askubuntu.com/questions/1079377/how-do-i-install-drivers-for-realtek-rtl8812bu

3. https://github.com/cilynx/rtl88x2BU_WiFi_linux_v5.3.1_27678.20180430_COEX20180427-5959

무선 랜카드가 리눅스에서 지원하는 인터페이스 모드를 확인하고자 할 때 iw list 명령어로 확인할 수 있다.

 

 

그러면 아래와 같이 Supported interface modes를 확인할 수 있고 여기서 지원되는 모드를 확인할 수 있다.

 

안드로이드에서 음성 녹음 기능을 사용해보자.

 

먼저 AndroidManifest.xml에 다음 권한을 추가한다.

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

그리고 res/layout/activity_main.xml에 다음과 같이 입력한다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview_sound_recorder_heading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="음성 녹음"
        android:layout_centerHorizontal="true"
        android:textSize="32dp"
        android:textStyle="bold"
        android:textColor="#000"
        android:layout_marginTop="32dp"
        />

    <Button
        android:id="@+id/button_start_recording"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="시작"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="32dp"
        android:layout_marginBottom="32dp"
        android:layout_centerVertical="true"/>

    <Button
        android:id="@+id/button_pause_recording"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="정지"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="32dp"/>

    <Button
        android:id="@+id/button_stop_recording"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="중지"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="32dp"
        android:layout_marginRight="32dp"/>
    
</RelativeLayout>

 

그리고 MainActivity.kt 파일에 다음과 같이 입력한다.

package com.example.audio

import android.Manifest
import android.media.MediaRecorder
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.core.app.ActivityCompat
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private var mediaRecorder: MediaRecorder? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var state = false
        var recordingStopped = false

        val output = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}" + "/test.mp3"

        val permissions = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE)
        ActivityCompat.requestPermissions(this, permissions,0)

        button_start_recording.setOnClickListener {
            mediaRecorder = MediaRecorder()

            mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
            mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
            mediaRecorder?.setOutputFile(output)

            mediaRecorder?.prepare()
            mediaRecorder?.start()

            state = true

            Toast.makeText(this, "녹음 시작", Toast.LENGTH_SHORT).show()
        }

        button_stop_recording.setOnClickListener{
            if(state){
                mediaRecorder?.stop()
                mediaRecorder?.release()

                state = false

                Toast.makeText(this, "녹음 중지", Toast.LENGTH_SHORT).show()
            }
        }

        button_pause_recording.setOnClickListener {
            if (state) {
                if (!recordingStopped) {
                    Toast.makeText(this, "녹음 정지", Toast.LENGTH_SHORT).show()
                    mediaRecorder?.pause()
                    recordingStopped = true
                    button_pause_recording.text = "다시 시작"
                } else {
                    Toast.makeText(this,"다시 시작", Toast.LENGTH_SHORT).show()
                    mediaRecorder?.resume()
                    recordingStopped = false
                    button_pause_recording.text = "정지"
                }
            }
        }
    }
}

 

※ 주의할 점으로 중지, 다시 시작 기능 즉, 위의 코드에서 mediaRecorder?.pause()와 mediaRecorder?.resume()

안드로이드 SDK 버전이 24 이상이어야 사용할 수 있다.

 

 

참고사이트

 

https://android.jlelse.eu/create-an-android-sound-recorder-using-kotlin-36902b3bf967

'Android' 카테고리의 다른 글

[Android] EditText에 이미지 추가  (0) 2019.10.27
[Android Kotlin] MobileFFmpeg 사용법  (0) 2019.09.25
val fileSize = File(path).length()

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] TCP 프로토콜을 이용한 파일 전송  (0) 2019.10.03
val file = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}" + "/test.mp3"
val fileInputStream = FileInputStream(file)

val buffer = ByteArray(1024)
var readBytes = fileInputStream.read(buffer) // file에서 buffer 크기만큼 읽어 buffer에 저장하고 실제 읽은 Byte 수 반환

val socket = Socket(IP, Port) // e.g. Socket("192.168.100.100", 10000)
val outputStream = socket.outputStream

var totalReadBytes = 0L

while (readBytes > 0) { // 더 이상 file에서 읽을 Byte가 없을 때까지 반복
    outputStream.write(buffer, 0, readBytes) // buffer[0] 부터 readBytes 만큼 데이터 전송

    totalReadBytes += readBytes
    Log.d("test", "totalReadBytes: $totalReadBytes")

    readBytes = fileInputStream.read(buffer)
}

Log.d("test", "end")
fileInputStream.close()
outputStream.close()
socket.close()

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] 파일 크기 구하기  (0) 2019.10.03

 프로그램을 개발하다가 터미널에서 프로세스를 포그라운드로 실행시킨 다음 해당 프로세스가 진행 중일 때 다른 명령어를 입력할 수 있도록 만들 필요가 생겼다.

 

 떠오른 방법은 새로운 터미널에 프로세스를 실행시켜 현재 터미널에서는 계속 다른 명령어를 입력할 수 있도록 하는 방법이었다.

 

 방법을 찾아보니 아래와 같은 명령어를 사용하면 현재 터미널은 유지되면서 새로운 터미널에서 원하는 프로세스(명령어)를 실행할 수 있다. 그리고 해당 프로세스가 끝나면 새로운 터미널은 자동으로 종료된다.

 

gnome-terminal --command "command"

gnome-terminal -e "command"

 

추가로 위의 명령어를 입력하면 아래와 같이  gnome-terminal의 이후 버전에서는 제거될 수도 있다고 나온다.

 

 

이를 해결 하려면 아래와 같이 --command, -e 대신 --를 사용하면 된다. 여기서 중요한 점은 command를 쓸 때 띄어쓰기가 포함되더라도 "command"나 'command'와 같이 command 양 끝에 큰 따옴표나 작은 따옴표를 붙이면 안된다는 점이다.

 

gnome-terminal -- command

 

 

import os

file_size = os.path.getsize("test.txt")
file_size_mb = file_size / 1024  # KB 단위
file_size_kb = file_size_mb / 1024  # MB 단위

 

+ Recent posts