Android 프로그램을 하면서 사용할 일은 거의 없지 찾으려면 구글링을 해야 해서 한번에 모아 놓고 참조하기 위해 정리를 해 보았습니다.

 

플랫폼 버전 별 API 레벨

 

플랫폼 버전

API 레벨

플랫폼 명

버전 코드

Android 10.0

29

Q

Q

Android 9

28

Pie

P

Android 8.1

27

Oreo

O_MR1

Android 8.0

26

Oreo

O

Android 7.1.1

Android 7.1

25

Nougat

N_MR1

Android 7.0

24

Nougat

N

Android 6.0

23

Marshmallow

M

Android 5.1

22

Lollipop

LOLLIPOP_MR1

Android 5.0

21

Lollipop

LOLLIPOP

Android 4.4W

20

KitKat Wear

KITKAT_WATCH

Android 4.4

19

KitKat

KITKAT

Android 4.3

18

Jelly Bean

JELLY_BEAN_MR2

Android 4.2

Android 4.2.2

17

Jelly Bean

JELLY_BEAN_MR1

Android 4.1

Android 4.1.1

16

Jelly Bean

JELLY_BEAN

Android 4.0.3

Android 4.0.4

15

IceCreamSandwich

ICE_CREAM_SANDWICH_MR1

Android 4.0

Android 4.0.1

Android 4.0.2

14

IceCreamSandwich

ICE_CREAM_SANDWICH

Android 3.2

13

Honeycomb

HONEYCOMB_MR2

Android 3.1.x

12

Honeycomb

HONEYCOMB_MR1

Android 3.0.x

11

Honeycomb

HONEYCOMB

Android 2.3.4

Android 2.3.3

10

Gingerbread

GINGERBREAD_MR1

Android 2.3.2

Android 2.3.1

Android 2.3

9

Gingerbread

GINGERBREAD

Android 2.2.x

8

Froyo

FROYO

Android 2.1.x

7

Eclair

ECLAIR_MR1

Android 2.0.1

6

Eclair

ECLAIR_0_1

Android 2.0

5

Eclair

ECLAIR

Android 1.6

4

Donut

DONUT

Android 1.5

3

Cupcake

CUPCAKE

Android 1.1

2

Base

BASE_1_1

Android 1.0

1

Base

BASE

 

프로그램에서 플랫폼 버전 별 작업을 해야 한다면 실제로 사용할 수 있는 값은 API 레벨과 버전 코드를 사용하여 조건문을 생성하여 사용하면 됩니다.

안드로이드 프로그램 내에서 현재 사용하는 단말기의 VersionBuild.VERSION.SDK_INT를 이용하여 구하면 되고 비교 값으로는 API 레벨을 사용 하거나 버전 코드는 Build.VERSION_CODES.[버전 코드]를 이용하여 API 레벨 상수 값을 구할 수 있습니다.


- API 레벨을 사용하는 경우


 if(Build.VERSION.SDK_INT >= 23){

    // 필요한 코딩 작업

}


- 버전 코드를 사용하는 경우


if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

    // 필요한 코딩 작업

}


 

안드로이드 단말기의 버전은 Build.VERSION.SDK_INT을 이용하여 얻을 수 있었는데 좀더 상세한 값들도 구할 수 있습니다.

  

Return Type

Code

Description

String

BASE_OS

The base OS build the product is based on.

String

CODENAME

The current development codename, or the string "REL" if this is a release build.

String

INCREMENTAL

The internal value used by the underlying source control to represent this build.

String

PREVIEW_SDK_INT

The developer preview revision of a prerelease SDK.

String

RELEASE

The user-visible version string.

String

RELEASE_OR_CODENAME

The version string we show to the user; may be RELEASE or CODENAME if not a final release build.

String

SDK

This field was deprecated in API level 15. Use SDK_INT to easily get this as an integer.

String

SDK_INT

The SDK version of the software currently running on this hardware device.

String

SECURITY_PATCH

The user-visible security patch level.

 

실제 프로그램에서 Code를 사용 하려면 Build.VERSION.[Code]와 같이 이용하면 됩니다간단하게 프로그램을 만들어서 출력을 하는 프로그램을 만들어 보면


 

public class MainActivity extends AppCompatActivity {

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

       
TextView textView = (TextView) findViewById(R.id.version);
       
StringBuffer sBuffer = new StringBuffer();
        if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            sBuffer.append(
"Build.VERSION.BASE_OS = " + Build.VERSION.BASE_OS);
           
Log.v("myDevice","Build.VERSION.BASE_OS = " + Build.VERSION.BASE_OS);
       
}
        Log.v(
"myDevice","Build.VERSION.CODENAME = " + Build.VERSION.CODENAME);
       
Log.v("myDevice","Build.VERSION.INCREMENTAL = " + Build.VERSION.INCREMENTAL);
       
Log.v("myDevice","Build.VERSION.RELEASE = " + Build.VERSION.RELEASE);
       
Log.v("myDevice","Build.VERSION.SDK = " + Build.VERSION.SDK);
       
Log.v("myDevice","Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
       
sBuffer.append("\nBuild.VERSION.CODENAME = " + Build.VERSION.CODENAME);
       
sBuffer.append("\nBuild.VERSION.INCREMENTAL = " + Build.VERSION.INCREMENTAL);
       
sBuffer.append("\nBuild.VERSION.RELEASE = " + Build.VERSION.RELEASE);
       
sBuffer.append("\nBuild.VERSION.SDK = " + Build.VERSION.SDK);
       
sBuffer.append("\nBuild.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
        if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            sBuffer.append(
"\nBuild.VERSION.SECURITY_PATCH = " + Build.VERSION.SECURITY_PATCH);
           
Log.v("myDevice","Build.VERSION.SECURITY_PATCH = " + Build.VERSION.SECURITY_PATCH);
       
}
        textView.setText(sBuffer)
;
   
}
}

 

이런 식으로 코드를 작성 하고 실행하면


android version


이렇게 버전에 대한 상세내역을 얻을 수 있습니다.


- copy coding -



GeoServerJava로 개발된 오픈 소스로 GIS 지리공간 데이터를 편집하고 웹으로 서비스할 수 있는 서버 입니다지도 서비스에 관심은 있지만 구글, 네이버, 다음에서 제공하는 기능만을 사용하는 경우에는 그다지 유용하지 않을 수 있지는데 자신만의 데이터를 사용하여 새로운 기능을 추가하여 만들고 싶다면 한번쯤은 관심을 가져야 하는 프로그램 중 하나로 생각 됩니다.

GeoServer 설치는 간단히 살펴 보고 어떤 식으로 사용을 하는지 사용방법도 하나를 알아 보도록 하겠습니다.

 

1. GeoServer 다운로드


설치 파일을 다운로드 하기 위해 사이트에 접속 합니다.

http://geoserver.org/download/


geoserver install shapefile

둘 중 아무거나 설치해도 되는데 2.18.1 버전을 설치해 보겠습니다.  Stable쪽을 클릭해서 들어가면


geoserver install shapefile


예전 버전에는 Windows용 설치 파일이 별도로 있었는데 이제는 binary 파일로 제공을 하고 있습니다. War 버전도 있는데 Tomcat등 자신이 좋아하는 WAS를 이용하여 사용할 수 있습니다여기서는 binary 파일로 설치를 진행 합니다.

 

다운받은 파일(geoserver-2.18.1-bin.zip)의 압축을 풀어 줍니다저는 C:\에 압축을 풀었더니 폴더 명이 geoserver-2.18.1-bin 이렇데 되어 있는데 뒷부분을 삭제 하여 geoserver 이렇게 만들었습니다폴더를 열어보면 파일 구성을 볼 수 있습니다.


geoserver install shapefile


2. 설정 및 기동

 

geoserver 폴더에 있는 start.ini  RUNNING.txt 파일을 읽어보면 쉽게 사용이 가능 합니다.

먼저 Java가 설치 되어 있는지 확인을 하고 없다면 설치를 해야 합니다.  GeoServerjava로 만들어서 사용하려면 꼭 필요 합니다.


geoserver install shapefile


Oracle Java가 없다면 openjdk(http://openjdk.java.net/)를 설치해도 됩니다.

 

start.ini 파일에는 설정을 하는 내용이 있고 RUNNING.txt 파일은 설치 방법이 순서대로 적혀 있습니다.

 

대부분 기본 설정 값을 사용해도 되고 start.ini 파일에서 GeoServer를 사용할 Port를 변경 합니다디폴트로 8080으로 설정되어 있지만 이 8080을 탐내는 프로그램들이 많아서 대부분 다른 프로그램이 사용을 하고 있을 것이므로 충돌이 나지 않도록 저는 9999로 변경 하기로 했습니다. start.ini 파일을 열고


geoserver install shapefile


jetty.port=8080

이 부분을 찾아

jetty.port=9999

이렇게 수정하고 저장 했습니다다른 포트로 설정해도 됩니다.

 

그리고 JAVA_HOME


geoserver install shapefile


GEOSERVER_HOME도 설정해 줍니다.


geoserver install shapefile


Port 변경, JAVA_HOME, GEOSERVER_HOME 추가. 이렇게 3개만 작업하면 됩니다.

이제 사용을 해볼까요명령 프롬프트를 하나 열고 명령어를 입력해 실행 합니다.

 

C:\geoserver\bin\startup


geoserver install shapefile


GeoServer가 스타드 되었습니다확인을 위해 웹브라우저를 아무거나 하나 실행 해서 URL을 입력해 봅니다.

 

http://localhost:9999/geoserver/web/


geoserver install shapefile


마치 Tomcat을 설치하고 있는 기분이 드는데 요즘에는 이런 프로그램이 많아져서 편합니다.

 

3. 로그인 및 테스트

 

이제 login을 해봅니다.


geoserver install shapefile


초기 관리자 ID와 비밀번호는

admin/geoserver

입니다.


geoserver install shapefile


로그인을 하니 좌측에 메뉴들이 많아졌습니다데이터 항목의 첫번째에 있는 [레이어 미리보기]를 선택해 봅니다.


geoserver install shapefile


그러면 우측에 미리보기 용 레이어 리스트가 나오는데 여기에서 제목이 USA Population을 찾아서 공통 포맷 중 OpenLayers를 클릭해 봅니다.


geoserver install shapefile


이렇게 지도 데이터를 웹으로 제공이 가능합니다그럼 바로 이전 [레이어 미리보기]의 리스트로 돌아가서 이름 항목을 보면 [topp:states] 두개가 합쳐저 있는데 이것이 나타내는 건 [작업공간:저장소] 입니다좌측 메뉴에서 저장소를 선택해 보면 알 수 있습니다.


geoserver install shapefile


리스트에서 유형 항목을 보면 Shapefile로 되어 있는데 만일 어느 지역의 Shapefile을 가지고 있다면 GeoServer에 등록해서 웹으로 서비스가 가능하다는 말이 되므로 Shapefile은 인터넷에 널려 있으니 다운로드 받아서 한번 작업을 해 봅니다저장공간과 저장소 두가지를 만들어 shp 파일을 등록만 하면 레이어로 서비스가 되겠군요.

 

4. 신규 레이어 생성

 

- 작업공간 생성


작업공간을 생성 하기 위해 좌측에서 [작업공간] 메뉴를 선택 합니다.


geoserver install shapefile


[새로운 작업공간 추가하기]를 선택 합니다.


geoserver install shapefile


이름과 uri를 아무렇게나 입력하는데 나중에 찾아볼 수 있게는 해야 합니다.

[본 작업공간으로 설정하기]를 체크 하고 저장 버튼을 클릭 합니다.


geoserver install shapefile


저장이 되고 [작업공간] 리스트에 추가된걸 확인할 수 있습니다.

 

- 저장소 생성

 

이번에는 좌측 메뉴에서 [저장소]를 클릭 합니다.


geoserver install shapefile


동일한 방법으로 [새로운 저장소 생성하기]를 클릭하고


geoserver install shapefile


저장소 종류가 참 많은데 여기서는 Shapefile을 사용하기로 하였으니 벡터 데이터 저장소 항목 중

Shapefile ESRI(tm) Shapefiles (*.shp)

를 찾아서 클릭 합니다.


geoserver install shapefile


대충 입력할 항목을 채우고 shp 파일을 추가하기 위해 [탐색]을 클릭 합니다.


geoserver install shapefile


파일이 저장된 위치를 찾아 선택해 줍니다.


geoserver install shapefile


그러면 [새로운 레이어]에 추가가 된걸 확인할 수 있습니다이번에는 [동작] 항목에서 


발행하기


를 클릭 합니다.


geoserver install shapefile

geoserver install shapefile


공간 좌표계는 다운로그 받은 파일 중에 (입력하려는파일명).prj 이런 파일이 있으면 자동으로 입력이 되고 아니면 직접 입력 합니다.

레이어 최소 경계 영역은 [데이터로부터 계산하기]와 [원본 영역으로부터 계산하기] 글자를 클릭 해주면 자동으로 입력이 됩니다.

 

입력이 끝나면 [저장] 버튼을 클릭 합니다.


geoserver install shapefile


레이어에 추가가 되었습니다이렇게 레이어 생성이 완료가 되었습니다.

 

구경 한번 해봐야죠?  좌측메뉴 [레이어 미리보기]를 선택 하고


geoserver install shapefile


새로 생성한 레이어의 [OpenLayers]를 클릭 합니다.


geoserver install shapefile


다운받은 파일이 뭔지 몰랐는데 이거였군요.

위에서 [새로운 데이터 저장소]의 종류가 많았는데 하나씩 추가해 보면서 테스트를 해보세요.


- copy coding -


안드로이드 스튜디오에서 제공하는 배경 테마는 기본적으로 Darcula, High contrast, IntelliJ 이렇게 3개를 제공하고 있습니다좀더 화려한 배경 테마를 사용하고 싶은 생각이 든다면 플러그인을 다운로드 받아서 적용을 하면 아주 다양한 환경을 구성할 수 있습니다.

테마 플러그인은 상당히 많은데 여기서는 한가지 플러그인을 설치해서 사용하는 방법을 알아봅니다다른 플러그인도 동일한 방법으로 사용하면 됩니다일단 어떤 플러그인들이 있는지 확인을 하기 위해 검색을 해야 합니다.

 

메뉴에서

 

File > Settings...

를 클릭 하여


android studio theme plugin


Settings 팝업 창을 오픈 합니다.

좌측 메뉴에서 Plugins를 선택 합니다.

못찾겠다면 위에 검색창에 Plugin을 입력해서 메뉴를 찾습니다.


android studio theme plugin


이제는 중간 검색 창에 키워드로 theme를 입력 하면 수많은 테마 플러그인이 나오는데 어떻게 생긴건지는 하나씩 클릭하면 우측에 설명과 샘플 화면 screenshot을 볼 수 있거나 화면이 없다면 플러그인 홈페이지가 링크되어 있으니 찾아 가서 확인할 수 있습니다.

구경이 끝나면 적당한 테마를 선택하고 우측 상단 install 버튼을 클릭해서 설치 합니다제일 많이 설치 하는 Material Theme UI를 선택해서 install 버튼을 클릭해 보았습니다.


android studio theme plugin


외부 플러그인 사용에 대한 위험성을 알리는 창이 나오는데 Accept 버튼을 클릭하면 설치가 진행 됩니다잠시 후 설치가 완료 되면


android studio theme plugin


Install 버튼이 Restart IDE 버튼으로 변해 있는데 클릭 해서 안드로이드 스튜디오를 재시작 합니다.


android studio theme plugin


재시작이 되고 첫 화면이 나옵니다어떤 테마가 설치 되었는지 알려주는 군요.

 

안드로이드 스튜디오를 시작하고 다시 Settings 을 열고 보면 이번에는

Appearance & Behavior > Material Theme 가 추가 된걸 볼 수 있습니다. 클릭을 하고 Selected Theme 우측에 보면 현재 설치된 테마를 볼 수 있고 클릭을 하면


android studio theme plugin


테마의 상세 선택이 가능 합니다테마를 하나 설치하면 하나 또는 여러 개의 부가적인 테마들이 같이 설치가 됩니다.

 

한번 몇개 감상해 볼까요?

 

Material Oceanic


android studio theme plugin


Material Darker


android studio theme plugin


Material Lighter


android studio theme plugin


Dracula


android studio theme plugin


Atom One Light


android studio theme plugin


등등 17개의 테마로 변경이 가능 합니다.

 

만약 Material Theme UI 플러그인에 마음에 맞는게 없다면 처음 테마 플러그인을 검색하던 Settings... 팝업 화면으로 돌아가 다른 테마를 설치해서 하나씩 테스트 해보면서 찾으면 됩니다.


- copy coding -



앱 개발을 하면서 간단한 키값 정도의 데이터는 데이터베이스를 사용하기 보다는 파일에 저장해서 사용하는게 편한 경우가 있어 파일 쓰기 및 읽기 테스트를 해 봅니다만들어 보는 최종 모습은 좀 엉성하지만 button 하나와 textview 하나 입니다.


android file write read


1. 프로젝트 생성

 

먼저 간단하게 프로젝트를 하나 만들고


android file write read


Empty Activity를 선택 합니다.


android file write read


Name은 적당히 적어주고 package copycoding.tistory.filetest로 하였습니다.



layout은 아이템이 2개밖에 없으니 TextView는 기본으로 생성되는 Hello World를 사용하고 id만 추가해 주었고 버튼은 OnClick을 추가해서 함수를 바로 호출 하도록 했습니다.

 

<TextView

        android:id="@+id/fileContents"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World!"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

 

    <Button

        android:id="@+id/button"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginStart="76dp"

        android:layout_marginLeft="76dp"

        android:layout_marginTop="108dp"

        android:text="Button"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toTopOf="parent"

        android:onClick="btnClick"/>

 

프로그램도 일반적인 java로 구현한 파일 읽기 쓰기와 비슷 합니다.

버튼을 클릭 하면 만들어 놓은 writeFile() 함수에 파일명과 데이터를 보내 파일에 쓰기를 하고 readFile()을 이용하여 입력된 내용을 읽어 옵니다그리고 읽어온 내용을 textView에 출력하는 내용 입니다.


public void btnClick(View view) {

try {

writeFile("testFile.txt", "testtttttttt");

            String rFile = readFile("testFile.txt");

            textView.setText(rFile);

        } catch (IOException e) {

            e.printStackTrace();

        }

}

 

writeFile() 함수는 전달받은 내용을 파일에 쓰기를 하고

OutputStreamWriter oStreamWriter = new OutputStreamWriter(openFileOutput(fileName,

                    Context.MODE_PRIVATE));

oStreamWriter.write(msg);

oStreamWriter.close();

 

readFile() 함수는 파일에서 내용을 읽어 옵니다.

InputStream iStream = openFileInput(fileName);

if(iStream != null) {

InputStreamReader iStreamReader = new InputStreamReader(iStream);

    BufferedReader bufferedReader = new BufferedReader(iStreamReader);

    String temp = "";

    StringBuffer sBuffer = new StringBuffer();

    while((temp = bufferedReader.readLine()) != null) {

         sBuffer.append(temp);

}

iStream.close();

fileContents = sBuffer.toString();

 

파일 읽기와 쓰기에 사용된 입출력 함수는 위에서 사용한 함수 이외에 FileInputStream, FileOutputStream 등 다른 함수를 이용해도 되고 본인이 알고 있는 함수를 사용하면 됩니다.

 

프로그램을 실행하고 BUTTON을 눌러서 결과를 확인 합니다.


android file write read



2. 생성 파일 확인

 

결과를 확인했으니 생성된 testFile.txt 파일도 확인해 봅니다파일 탐색기를 이용해 확인을 하려고 하면 파일을 제대로 확인 할 수 없습니다안드로이드 스튜디오에서 제공하는 Device File Explorer를 이용하면 쉽게 확인이 가능 합니다.

 

메뉴에서


View > Tool Windows > Device File Explorer


를 선택 하면 


android file write read


Android Studio 우측에 현재 연결된 핸드폰의 내용을 탐색할 수 있습니다.


android file write read


프로젝트와 관련된 파일들은 package 으로 폴더가 /data/data/ 아래 생성 되는데 현재 만든 프로젝트의 패키지가 copycoding.tistory.filetest 이니 좀더 아래에 생성되어 있겠네요. 스크롤을 아래로 내려서


android file write read


폴더가 생성되어 있으니 확장을 하고 msg를 저장한 testFile.txt 파일을 더블클릭해서 열어 봅니다.


android file write read

 

그러면 저장된 파일 내용을 확인할 수 있습니다.

 

 

3. 전체 소스


activity_main.xml


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

    <TextView
       
android:id="@+id/fileContents"
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="Hello World!"
       
app:layout_constraintBottom_toBottomOf="parent"
       
app:layout_constraintLeft_toLeftOf="parent"
       
app:layout_constraintRight_toRightOf="parent"
       
app:layout_constraintTop_toTopOf="parent" />

    <Button
       
android:id="@+id/button"
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:layout_marginStart="76dp"
       
android:layout_marginLeft="76dp"
       
android:layout_marginTop="108dp"
       
android:text="Button"
       
app:layout_constraintStart_toStartOf="parent"
       
app:layout_constraintTop_toTopOf="parent"
       
android:onClick="btnClick"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.java


package copycoding.tistory.filetest;
import
androidx.appcompat.app.AppCompatActivity;
import
android.content.Context;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.TextView;
import
java.io.BufferedReader;
import
java.io.FileNotFoundException;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.InputStreamReader;
import
java.io.OutputStreamWriter;

public class
MainActivity extends AppCompatActivity {
    TextView
textView;
   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
setContentView(R.layout.activity_main);
       
textView = (TextView) findViewById(R.id.fileContents);
   
}
   
private void writeFile(String fileName, String msg) {
       
try {
            OutputStreamWriter oStreamWriter =
new OutputStreamWriter(openFileOutput(fileName,
                   
Context.MODE_PRIVATE));
           
oStreamWriter.write(msg);
           
oStreamWriter.close();
       
} catch(FileNotFoundException e) {
            e.printStackTrace()
;
       
} catch (IOException e) {
            e.printStackTrace()
;
       
}
    }

   
private String readFile(String fileName) throws IOException {
        String fileContents =
"";
        try
{
            InputStream iStream = openFileInput(fileName)
;
            if
(iStream != null) {
                InputStreamReader iStreamReader =
new InputStreamReader(iStream);
                
BufferedReader bufferedReader = new BufferedReader(iStreamReader);
               
String temp = "";
               
StringBuffer sBuffer = new StringBuffer();
                while
((temp = bufferedReader.readLine()) != null) {
                    sBuffer.append(temp)
;
               
}
                iStream.close()
;
               
fileContents = sBuffer.toString();
           
}
        }
catch (FileNotFoundException e) {
            e.printStackTrace()
;
       
}
       
return fileContents;
    
}

    public void btnClick(View view) {
       
try {
            writeFile(
"testFile.txt", "testtttttttt");
           
String rFile = readFile("testFile.txt");
           
textView.setText(rFile);
       
} catch (IOException e) {
            e.printStackTrace()
;
       
}
    }
}

 

- copy coding -



1···28293031323334···118

+ Recent posts