달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

'카테고리'에 해당되는 글 19건

  1. 2018.01.08 mysql 문자열 갯수 세기
  2. 2017.12.14 MacBook WiFi 문제..
  3. 2017.12.14 WKWebVIew 에서 쿠키 2
  4. 2017.12.14 javascript로 웹에서 App 열기
  5. 2017.10.25 초기화에 대해서..
  6. 2017.09.19 WebView 새창 띄울 때 Issue 사항...
  7. 2016.11.27 가을
  8. 2016.05.18 Licenses 2
  9. 2016.05.11 Brand Guidelines 1
  10. 2016.05.04 Project Roles

살다보면 이런 저런 일이 많다. 쿼리도 짜다보면 이런저런 일이 많다.

이런저런일 하다가 group_concat 으로 묶은 문자열 중에, 특정 문자열이 몇 번인지 확인이 필요 했다.

디비에서는 주로 학급을 예로 많이 들던데 ㅋㅋㅋㅋ

예를 들어서 특정 학생이 100점을 몇번 받았는가를 확인하는 그런거!!를 짜보자.


테이블 셋팅은

idx - 학번

name - 이름

subject - 과목

score - 점수

testdate - 시험친 날


간단하게 이렇게만 하자!

간단하게 학생의 평균 점수는

SELECT AVG(score) FROM STUDENT GROUP BY idx

하면 된다.

자, 그런데 100점을 2번 받은 학생들만 뽑아보도록 해보자. 으트케? 이르케!


SELECT * FROM STUDENT t1 JOIN 
        (SELECT idx, GROUP_CONCAT(score) AS scores FROM STUDENT GROUP BY idx) t2 
        WHERE 
            ROUND ( ( CHAR_LENGTH( t2.scores ) - CHAR_LENGTH( REPLACE( t2.scores , '100', '' ) )
             / CHAR_LENGTH( '100' ) ) ) = 2


자 겁나 쉽지 않은가? 

겁나 간단한걸 stackoverflow에서 찾았다!! 갓플로우


다들 소스보시면 아하! 하겠지만, 모르는 사람을 위해 원문 링크는 아래에!!


https://stackoverflow.com/questions/12344795/count-the-number-of-occurrences-of-a-string-in-a-varchar-field

Posted by
|

MacBook WiFi 문제..

기타 2017. 12. 14. 17:55

음..어... 회사에서 아이폰 개발 하라고 맥북프로를 사줬따.

음... 그런데, 이게 너무 화면도 작고 키보드도 손가락 나갈거같이 키감이 거지같고 해서 집에 해킨토시를 설치했다. 2개월 정도 걸렸다....ㅎ 회사에서는 맥북으로 집에서는 집컴터로 그렇게 개발을 하는데,

집에서 해킨토시 설정 잡는데, 집에서 인터넷 선 좀 지저분해 보여서 usb 와이파이 모듈 사서 쓰는데, 와이파이가 검색이 안됨, 우리집꺼만. 옆집 뒷집 앞집 윗집 아래집꺼 다 뜨는데 우리집 꺼만 안뜸. window에서는 잘 쓰던건데... 설정 문제면 다 안떠야 하는데 우리집만 안뜸.. 알고보니까 채널 문제......

여러분 2.4G WiFi 는 1~9 채널까지만 사용이 가능합니다.... 근데 아이폰은 채널 상관없이되는 듯...???

그리고, 회사에 와이파이 기기를 바꿨다. 고장나거나 그래서도 아니고, 왜 바꾼 건진 모르겠다. 기기도 동일하다. 근데 맥북프로로 와이파이가 안잡힌다. 뭐지.... 하고 핫스팟을 켜봐도 연결이 안된다.. 갑자기....

그렇게 몇주 쌩쑈를 하고 서비스센터까지 갔다 오고, 초기화도 해보고 다 해봤는데, 140번대 채널을 150번대 채널로 변경을 한 번 해봤거든?? 잘돼...

여러분 혹시 애플에서 권장하는 5G WiFi가 안되면, 150번대 채널을 사용하세요.

아무리 검색으로 찾아봐도 서비스센터에가서 물어봐도 상담사한테 물어봐도 채널에 관한 말은 없었는데, 집에서 2.4G 와이파이가 10채널 이상을 찾지를 못하는 것이 생각나서 혹시나 해서 채널 바꿔봤는데.. 잘된다....

연결이 안될거 같으면 와이파이 목록에 뜨질 말든가 겁나 짱나네 진짜 시X 애플. 아오 빢쳐

어딜 검색해도 안뜬다. 다른 윈도우 노트북들은 다 잘 잡힌다. 핸드폰도 다 잘잡힌다. 아이폰도 다 잘 잡힌다. 근데 맥북만 안된다. 하면 WiFi 채널을 바꿔보자. X발.....

Posted by
|

일단, 나는 UIWebView 를 써보지 않았다. WKWebVIew만 써봤다. Android에 WebView랑 비슷한 듯.


하이브리드 앱에 홈페이지 로그인 할 때 자동 로그인 체크하고, 로그인을 하게 되면 앱이 종료되면 쿠키 삭제ㅋ 됨ㅋ 자동 로그인 안됌ㅋ 뭐지... 하고 이런저런 테스트를 해봤는데, WKWebView에는 php에 setcookie가 안 먹히드라. 결국 자동로그인 쿠키가 생성이 안됨ㅋ

안되면 되게 하라... 스크립트로 처리를 함.

AppDelegate에서


func applicationWillResignActive(_ application: UIApplication) {
    web.evaluateJavaScript("document.cookie", completionHandler: {(object, error) in
        if let string: String = object as? String {
            UserDefaults.standard.set(string, forKey: "cookie")
        }
    })
}

이렇게 앱이 foreground에서 벗어나게 되면 쿠키를 수시로 저장시키자. 아, 근데 php setcookie로 생성된 쿠키는 저기에서 확인이 안된다.ㅋ

 일단 WKWebView에 WKUserContentController() 를 통해서 자바스크립트 인터페이스(안드로이드에서는 이렇게 부른다 swift에서는 콘텐츠컨트롤러라고하는건가...?)를 등록하고, 로그인 처리하는 페이지에서 스크립트로 실행 한 후에 UserDefault에다가 자동로그인 체크를 했다는 정보를 넘겨주어서 앱에서 자동으로 쿠키를 생성시켜서 처리를 했다.



let contentController = WKUserContentController();
let config = WKWebViewConfiguration();
contentController.add(self, name: "javascriptInterfaceName"); // name 에다가 원하는 자바스크립트인터페이스 이름을 입력하면 되시겠다.
config.userContentController = contentController;
let webview = WKWebView(frame: uiController.view.frame, configuration: configurate!)

그리고 웹페이지에서 어떻게 실행을 시키는 고하면

webkit.messageHandlers.javascriptInterfaceName.postMessage( { key1 : value1, key2 : value2, key3 : [ value3_1, value3_2 ] } );

위 소스를 실행시키면 된다.


위 소스를 실행시키게 되면, 

@available(iOS 8.0, *)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// message.name 이녀석이 javascriptInterfaceName 이랑 매칭이 된다. 요녀석으로 용도별로 처리하면된다.
// 보안 문제가 발생 할 경우가 있으니까 꼭 if message.name != "javascriptInterfaceName" {return} 이런 식으로 지정한  스크립트가 아니라면 막길 권한다.

    guard let mbody = message.body as? Dictionary<String,Any> else {return} // json 형식으로 파라미터 값을 넘겼으니까 Dictionary로 변환 가능하다.
    let value1 = mbody["key1"] as? Any
    let value2 = mbody["key2"] as? Any
    let value3 = mbody["key3"] as? Array<Any> // 마지막은 리스트형식이니까,
}

WKWebView 내의 이런 소스에서 실행이 된다. 참고로 iOS8 부터 사용 가능하다.

이렇게 아무튼 저기서 자동로그인 체크를 했다는 것을 앱으로 전달해서 UserDefault에 다가 저장했다가 앱 실행시마다 자동로그인 쿠키값을 생성해주면 되겠다. 어떻게?? 아래처럼 참고하자.


아래 setCookie에 해당하는 메소드는 구글링을 통해서 찾았으나, 출처를 따로 안적어놨네..........죄송합니다..

webview.evaluateJavaScript("document.setCookie = function (name, value, expiredays) {var cookie = name + \"=\" + escape(value) + \"; 
path=/;\"; if (typeof expiredays != 'undefined') {var todayDate = new Date(); todayDate.setDate(todayDate.getDate() + expiredays); cookie += \"expires=\" +todayDate.toGMTString() + \";\"; } 
document.cookie = cookie;}",completionHandler: nil)

WKWebView에다가 저렇게 하고

webview.evaluateJavaScript("document.setCookie('\(key)','\(value)', '\(expiredays)')", completionHandler: {
    (o,e) in web.reload()
})

이렇게 key(쿠키 명), value(쿠키 값), expiredays(저장 기간) 값을 넣어서 쿠키를 생성시키고, 페이지 새로고침을 해줘야 제대로 인식을 한다.

마지막으로 저기 document에 체이닝 되어진 setCookie 메소드를 제거해주자.

webview.evaluateJavaScript("delete document.setCookie", completionHandler: nil)


이렇게 하면 깔끔하게 처리가 된다.

'모바일 개발 > iOS-Swift' 카테고리의 다른 글

초기화에 대해서..  (0) 2017.10.25
Posted by
|

 ios 도 그렇고 android도 그렇고  url 형식으로 앱에 접근이 가능하다. 앱이 설치 되어 있다면 앱을 실행 시키고, 앱이 설치되어 있지 않다면 앱 설치 페이지로 전환 시켜주는게 가능하다. 그리고 앱에 특정 데이터를 전달 해서 원하는 기능을 제공 할 수 있다.


일단 기본 적인 android 와 ios 둘 다 메커니즘은 동일하다. user-action을 통한 이벤트여야지 동작을 한다는 것과, setTimeout을 이용해서 마켓으로 이동을 시켜야 한다는 것. 

소스로 확인 해보자.


    var androidPackage = "my.app.package";
    var appleAppId = ""; // id1401934913 형식으로 itunes url에 나오는 앱아이디.
    var androidMarketUrl = "market//detail?id="+androidPackage;
    var iosMarketUrl = "http://itunes.apple.com/kr/app/" + appleAppID;
    function goStore(){
        var startTime = +new Date();
        setTimeout(
            function() {
                var now = +new Date();
                if ( now - startTime < 1000){
                    var marketUrl = "";
                    if( isAndroid ){
                        marketUrl = androidMarketUrl;
                    }else if( isiOS ){
                        marketUrl = iosMarketUrl;
                    }
                    location.href = marketUrl;
            }
        , 500)
        if ( isAndroid ){
            openAndroid();
        }else if ( isiOS ){
            openiOS();
        }
    }

    function openAndroid(){
        var userAgent = navigator.userAgent.toLowerCase();
        if ( userAgent.match(/chrome/) ){
            location.href = "intent://hostName?param1=someValue1¶m2=someValue2/"
                                        +"#Intent;scheme=schemeName;action=android.intent.action.VIEW;"
                                        +"category=android.Intent.category.BROWSABLE;package="+androidPackage;
        }else{
            var iframe = document.createElement( 'iframe' );
            iframe.style.visivility = 'hidden';
            iframe.src = 'schemeName://hostName?param1=someValue1¶m2=someValue2';
            document.body.appendChild(iframe);
            document.body.removeChild(iframe);
        }
    }

    function openiOS(){
        location.href = "schemeName://hostName?param1=someValue1¶m2=someValue2";
    }

안드로이드는 버전에 따리 분기 처리해줘야 하는데 요즘은 schemeName 을 intent로 고정해서 사용한다고 한다. 이전 버전에서는 iframe을 이용하는게 제일 깔끔하다고 어디서 봐서 iframe으로 사용 했다. 

중요한 부분은 3 함수 모두 사용자 액션 기반으로 발동이 되어야 한다는 것이다. onload나 그런 것이 아닌 dom을 클릭해서 실행 하던지 comfirm() (confirm만으로는 안된다. dom을 클릭해야만 발동되더라..) 을 통해 처리 해야 한다는 것이다.

안드로이드에서는 schemeName 과 hostName은 Manifest파일에 실행 될 activity 안에 아래 내용을 붙여 주면 된다.


<activity android:name="name" android:label="label" android:theme="theme">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"> </action>
        <category android:name="android.intent.category.BROWSABLE"></category>
        <category ........=""> </category>
        <data android:host="hostName" android:scheme="schemeName"></data>
    </intent-filter>
</activity>

이렇게 넣어주면된다. 보면 action과 category도 맞춰줘야하는 건 쉽게 알 실듯 여러분들은 똑똑하니까

iOS 의 경우는 캡쳐로 때움.ㅋ

identifier랑 schemeName이랑 동일하게 하면 된다. 다르게 해도 되는 지는 모르겠다.ㅋ  iOS에서는 hostName은 따로 설정하지 않아도 된다.


자 이제 실행시킨 앱에서 파라미터를 받아서 어떻게 처리 하는지를 보자.

안드로이드 먼저

아까 실행될 acitivity의 onCreate에서 작업하면 된다.

    if( getIntent().getScheme() != null && getIntent.getScheme().equals( "schemeName" ) ){
        String someValue1 = getIntent().getData().getQueryParameter( "param1" );
        String someValue2 = getIntent().getData().getQUeryParameter( "param2" );
        //do....
    }

아래는 iOS 11 기준으로. iOS 는 버전별로 어디어디서 실행되는지는 모르겠다. iOS8 까지는 아래대로 하면 문제없을듯 하다. AppDelegate에 아래 함수를 수정해주자. 없으면 추가해주고.

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        guard let query = url.query else {return false}
        let querys = query.components(separatedBy: "&")
        var keyValue = Dictionary<string,string>()      
        for q in querys {
            guard let point = q.index(of: "=") else {continue}
            let keyEnd = q.index(before: point)
            let valueStart = q.index(after: point)
            
            let key = String(q[q.startIndex...keyEnd])
            var value = String(q[valueStart..< q.endIndex])  
            keyValue[key] = value
        }
        //dosomethings..
        return true
    }

나의 경우는 http url 을 파라미터로 넘겨서 앱에 있는 webview를 해당 url 로 넘겨주는 것을 구현했는데, iOS에서 병신같은 일이 일어났다.

파라미터로 넘긴 url 이  http://www.someDomain.co.kr/folder1/page.php/ 였다고 가정을 하면 끝에 /를 붙였다고 페이지 로딩 직후 바로 이전 url로 넘겨버리는 엄청난....병신같은...................ㅡㅡ 첨엔 내가 소스를 잘못 짠줄 알았는데 알고보니 / 때문이였다. 안드로이드는 상관없이 잘된다... WKWebView 버그인지는 잘 모르겠다. 근데, ㅋ 웃긴게 openurl로 넘겨온게 아니면 슬러쉬 있든 없든 잘된다는 거다.ㅋ


    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        guard let query = url.query else {return false}
        let querys = query.components(separatedBy: "&")
        var keyValue = Dictionary<string,string>()
        
        for q in querys {
            guard let point = q.index(of: "=") else {continue}
            let keyEnd = q.index(before: point)
            let valueStart = q.index(after: point)
            
            let key = String(q[q.startIndex...keyEnd])
            var value = String(q[valueStart..< q.endIndex])  
            
            //url 끝에 슬러쉬 붙었다고 페이지 이동안됌. 이뭐병.... 끝에 슬러쉬를 없애주자...
            while value.hasSuffix("/"){
                let suffixIndex = value.index(value.endIndex, offsetBy: -1)
                value = String(value[value.startIndex..< suffixIndex])
            }
            keyValue[key] = value
        }
        //dosomethings..
        return true
    }

이러니 잘되네............. 결국 저 슬러쉬 문제를 정리하기 위해서 겸사겸사 쓴 포스트.

Posted by
|

class 초기화만 해당된다.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;
    init( _ someParam : TempObject){
        someValue1 = someParam;
        someValue2 = TempObject2();
        super.init();
        someValue3 = 5;
    }
}


일단 지정 초기화, 편의 초기화 설명 보다 먼저,
let 으로 선언된 변수는 super.init() 이전에 초기화가 이루어 져야한다.
그리고 나중에 설명 하겠지만 let 변수는 꼭 (designed)정의 초기화에서 초기화가 되어야 한다.
일단 옵셔널로 정의된 건 nil 로 초기화가 가능한데, let 변수이니, nil로 선언되면 변경이 안되니 nil로 초기화 할 거면 let 이 아니라 var 로 선언 하는게 좋을듯 한다.

편의 초기화 접두어는 convenience 를 사용 하면 된다. 뭣 하는 녀석인고 하면

class SomeClass {
    init(v1 : Int, v2 : Int, v3 Int){
        //someCode
    }
    convenience init(v1 : Int){
        self.init(v1 : v1, v2 : 5, v3 : 3)
    }
}

요런거에 사용 되는 녀석이다. 딸랑 요것만 있음 뭔지 모르지.. 아래에 있는 걸 다시 보자.

class SomeClass {
    init(v1 : Int, v2 : Int? = nil, v3 : Int? = nil){ //지정 초기화
        //someCode
    }
    convenience init(v1 : Int){ // 편의 초기화
        self.init(v1 : v1, v2 : nil, v3 : nil)
    }
}

//초기화
let someClass : SomeClass = SomeClass(1);

v1 파라미터만 넘기고 싶을때 사용하면 된다.
내부적으로 v2, v3 에는 nil 이 지정 초기화로 넘어가게 된다.

여기서 조금 생소한 부분이 나온다.
편의 초기화는 내부에 꼭 다른 초기화 내용이 있어야 한다. 위의 self.init(v1 : v1, v2 : nil, v3 : nil) 과 같이 말이다.
여기서 편의 초기화에서 편의 초기화를 호출 해도 된다.

class SomeClass {
    init(v1 : Int, v2 : Int? = nil, v3 : Int? = nil){ // 마지막 호출
        //someCode
    }
    convenience init(v1 : Int){ //최초 호출
        self.init(v1 : v1, v2 : nil)
    }
    convenience init(v1 : Int , v2 : Int){ //두번쨰 호출
        self.init(v1 : v1, v2 : v2, v3 : nil);
    }
}
// 초기화
let someClass : SomeClass = SomeClass(v1 : 3);

여기서 상속 받을 경우는 어떻게 되는가를 궁금해 하는 사람이 있을 것 같아 제일 위에 있는 소스를 참고해서 보자.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;

    override init(_ someParam : TempObject , someParam2 : TempObject2){
        someValue1 = someParam;
        someValue2 = someParam2;
    }

    convenience init(_ someParam : TempObject, someParam2 : TempObject2){
        self.init(someParam, someParam);
        someValue3 = 5;
    }

    convenience init( _ someParam : TempObject){
        let someParam2 = TempObject2();
        self.init(someParam, someParam2);
    }
}
//초기화
let someParam : TempObject = TempObject();
let someClass : SomeClass = SomeClass(someParam);

위에서 설명했듯이 let 변수는 정의 초기화 함수 내에서 초기화가 이루어져야 한다.
위에 정의 초기화를 super class의 init을 오버라이드 했다.
오버라이드 하지 않고 쓰기 위해서는 아래 처럼 간단하게 사용 하면된다.

class SomeClass : NSObject{
    let someValue1 : TempObject;
    let someValue2 : TempObject2?
    var someValue3 : Int;

    init(_ someParam : TempObject, someParam2 : TempObject2){
        someValue1 = someParam;
        someValue2 = someParam2;
        super.init();
        someValue3 = 5;
    }

    convenience init( _ someParam : TempObject){
        let someParam2 = TempObject2();
        self.init(someParam, someParam2);
    }
}
//초기화
let someParam : TempObject = TempObject();
let someClass : SomeClass = SomeClass(someParam);

마찬가지로 정의 초기화에서 let을 초기화 시켜주어야 한다.

첫 초기화로 호출 되어 초기화가 이루어진다. 두번째 호출이라고 주석 단 부분에 또한 편의 초기화이다.
ㅇㅇ 쉽게 convenience -> convenience 가 된다. 그리고 그 끝은 designed(지정) 초기화가 있어야만 한다.
구글링 해보면 몇 가지 법칙이라고 정리 되어 있던데, 난 그런 법칙은 모르겠구, 내 수준으로 정리를 하면
1. let 변수는 super.init() 호출 전에 초기화가 되어야 하며, 정의(Designed) 초기화내부에서만 초기화가 가능하다.
2. 편의->편의->지정 같이 초기화의 마무리는 항상 지정 초기화가 되어야 한다.

Java 스타일의 초기화랑 비교를 하면, Java 의 경우 초기화 함수를 여럿 만들어 주면 된다. 그냥 입맛에 맞게 만들어서 사용하면된다. 그리고, super 클래스 초기화를 부르고 싶으면 쓰고, 안쓰고 싶으면 안 쓰면 된다.
super class의 초기화도 오버라이드 하든 말든 마음대로 내키는 대로 하면 된다. 초기화 선언이 없으면 고냥 자동으로 만들어 준다.
Swift는 조금 색다르게 표현했다. 편의(convenience) 초기화는 말 그대로 편의를 위해서 만들어지는 정식적인 초기화는 아니고, 정의(Designed)초기화를 통해서만 모든 초기화가 이루어진다고 보면 된다.

'모바일 개발 > iOS-Swift' 카테고리의 다른 글

WKWebVIew 에서 쿠키  (2) 2017.12.14
Posted by
|

안드로이드 개발 하다가 빡치는 상황 발생..

webview에서 처리하는 것중에, 새창 띄우는 것을 처리해줘야 하는데, 이게, 새창으로 띄우는게 여간 귀찮은게 아니다. 새창만 띄우는 것이면, 좀 덜 귀찮은데, 이게, 새로 뜨는 webview의 user-agent를 기존 부모뻘이 되는 webview와 동일하게 변경하니까, 서버에서 이걸 못 받는건지... php5 버전대 버그인지,,, 안드로이드 버그인지............

간략하게 정리하면 

<a target="_blank"> 또는 javascript:window.open(); 를 처리하려면, WebChromeClient를 설정해서, onCreateWindow를 재정의를 해줘야 한다.

근데, onCreateWindow를 통해서 만들어지는 새창 WebView의 user-agent를 서버에서 인식을 못한다!

구글링구글링을 통해서 찾은 소스 하나(https://stackoverflow.com/questions/9654529/handle-url-from-oncreatewindow-web-view)

        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            Message href = view.getHandler().obtainMessage();
            view.requestFocusNodeHref(href);

            String url = href.getData().getString("url");
            handleUrl(url); // handle the url here

            return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
        }


이얏호! 찾았따, 히히,

근데 스크립트로 새창을 열때는 안된다. <a href=""> 만 가능하다.

아오........................

그래서 결국 꼼수로


        @Override
        public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg) {
            WebView temp = new WebView(context);
            temp.setWebViewClient(new WebViewClient(){
                @Override
                public void onPageStarted(WebView view, String url, Bitmap favicon) {
                    //여기서 새로운 커스텀된 webview 생성후, 처리를 해주면 된다.useragent를 처리해주면 된다.
                    // webview 생성후 부모레이아웃에 addView로 생성된 뷰를 추가해주자,
                    // 생성하면서 기본 설정도 여기서 다 처리해주면 된다.
                    //super.onPageStarted(view, url, favicon); 이건 주석처리 해놓고,
                }
            });
            WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
            transport.setWebView(temp);
            resultMsg.sendToTarget();

            return true;
        }


얏호, 완료.

파라미터로 넘어오는 resultMsg에 분명 링크되는 정보들이 들어가 있는거는 분명한데,, 이걸 빼먹질 못하게 되어 있다. 빼먹을려면 WebViewProvider부터 해서 새로 작업을 해야하는 것 같다.. 제조사마다 WebViewProvider를 재정의해서 컴파일하는 듯 하다. 안드로이드 레퍼런스에 참고할만한 소스가 하나도 없다. 크롬소스를 토대로 만들라고했는데, 너무 배보다 큰 배꼽이래서 포기...

resultMsg의 오브젝트로 포함되는 WebViewTransport에 웹뷰를 설정해주고, resultMsg를 이미 정의되어져있는, 그러나 우리는 알지 못하는, 타겟으로 보낸다. 그럼 자동으로 WebViewTransPort에 설정된 웹뷰에 이런저런 짓거리를 해서 페이지 전환이된다. 이런저런 짓거리를 우리는 알지 못한다!! 아마 webviewProvider에 있을 것같은 느낌이 든다. 

이런저런 짓거리 중에 user-agent를 기본 시스템 user-agent로 되돌리는 것도 아닌 것 같은데, 왜 그런지 모르겠다. 로그를 찍으면 설정한 user-agent값이 제대로 찍히긴 하는데, 서버측에서는 제대로 인식을 못하는건지 아오 생각 할 수록 짜증....

처음에는 WebChromeClient의 onProgressChange(WebView view, int newProgress) 를 통해서 프로그레스가 30이상 되었을때, reload하면서 useragent를 다시 설정해주는 방식으로 하다가, 이건 좀 아닌 거 같잖아? 페이지가 살짝 뜨다가 지 혼자서 페이지를 새로고침을 하다니. 이건 좀 아니잖아.....  깔끔하지 못해! 

그래서 위의 방식대로, 사용자는 모르게 처리하게끔 처리 했다. 맘에 들진 않는데, 현실과 타협해버렸다.

Posted by
|

가을

일상/뭐 2016. 11. 27. 01:55

가을 끝물
겨울 시작

하나 둘 준비하고 생각처럼 잘되진않는다

한 사람 마저 내맘 같이 않고 아쉬움에 토라지고

모든걸 지켜내고 싶은데. 나도 알고 있다. 양자택일이 최선이라는거.

근데 용기가안난다. 내 자신이 싫다. 다 갖고싶다. 다 하고 싶다. 다 지키고싶다.

부족하다. 내가 품을 수 있는건 큰데 가진게 부족하다. 내 품속에 공간들이 너무 커서 빈 공간들 사이로 체온이 빠르게 빠져나간다. 하나의 체온을 다시 지키면 다른 것의 체온이 또 빠져나간다.

욕심일까. 애초에 나 품에 담을 수 있는건 하나 였을까. 지금도 하나만 품고 다 품은거라 착각하는 걸까.


옛 추억에 울적하다. 그리운데 그리운거 말고는 할 수 있는게 없다.

여유로운 삶을 살고 뒤를 돌아보고 싶은데 운전하는 것 마냥 앞만 볼수밖에없다.

오랜만에 가난햇던 기억에 아팠다. 가질수 없었던 당연함이 문득 아팠다. 자식을 위해 참고 희생했을 쓸쓸함에 아팠다. 지금이라도 행복했으면 좋겠다.

근데, 그게 잘 안된다.

가을 끝물 손이 시려워지니 아련한 기억이 아픔이 된다.

추억은 옛 나의 철없던 즐거움이였고 다시는 누릴 수 없는 사치가되었다. 하나 둘 떠내려가는데로 놔두고 있어야하는데, 하나 둘 모두 쉽사리 떠내려가게 놔두질 못하고 다시 주워 담는다.

'일상 > ' 카테고리의 다른 글

Never Never Give Up  (0) 2016.05.03
네번째  (0) 2016.04.20
  (0) 2016.02.08
두번째  (0) 2016.01.28
처음  (0) 2016.01.25
Posted by
|

Licenses

라이센스

The Android Open Source Project uses a few open source initiative approved open source licenses for our software.

안드로이드 오픈소스 프로젝트는 오픈소스 이니셔티브에 인증된 오픈소스 라이센스를 사용한다.


Android Open Source Project Licenses

안드로이드 오픈소스 라이센스

The preferred license for the Android Open Source Project is the Apache Software License, Version 2.0 ("Apache 2.0"), and the majority of the Android software is licensed with Apache 2.0. While the project will strive to adhere to the preferred license, there may be exceptions that will be handled on a case-by-case basis. For example, the Linux kernel patches are under the GPLv2 license with system exceptions, which can be found on kernel.org.

안드로이드 오픈소스 프로젝트는 아파치 소프트웨어 라이센스, 버전2.0 ("아파치 2.0")을 따른다. 그리고 많은 안드로이드 소프트웨어는 아파지 2.0에 허가된다. 프로젝트가 라이센스에 따르도록 하는 동안, 각 상황별로 다루어지게 될 것이다. 예를 들어 리눅스 커널 패치는 system exceptions(시스템 익셉션)과 GPLv2 라이센스를 따른다. kernel.org에서 찾을 수 있다.


Contributor License Agreement

컨티리뷰터 라이센스 동의

All individual contributors (that is, contributors making contributions only on their own behalf) of ideas, code, or documentation to the Android Open Source Project will be required to complete, sign, and submit an Individual Contributor License Agreement. The agreement can be executed online through the code review tool. The agreement clearly defines the terms under which intellectual property has been contributed to the Android Open Source Project. This license is for your protection as a contributor as well as the protection of the project; it does not change your rights to use your own contributions for any other purpose.

모든 개별적인 기여자(그들의 이익을 위한 기여자)의 안드로이드 오픈소스 프로젝트를 위한 아이디어, 코드, 또는 문서는 개별 컨트리뷰터 라이센스 동의서 작성하고, 서명이 되어 제출되어야 한다. 동의서는 코드 리뷰 툴를 통해 온라인상에서 실행된다. 동의서는 지적인 재산을 안드로이드 오픈소스 프로젝트에 기여한다는 조건을 정의한다. 이 라이센스는 당신을 컨트리뷰터로써 보호하고, 프로젝트의 보호를 위한 것이지, 당신이 기여한 당신의 권리를 어느 목적으로든 바꾸겠다는 것이 아니다.

For a corporation (or other entity) that has assigned employees to work on the Android Open Source Project, a Corporate Contributor License Agreement is available. This version of the agreement allows a corporation to authorize contributions submitted by its designated employees and to grant copyright and patent licenses. Note that a Corporate Contributor License Agreement does not remove the need for any developer to sign their own Individual Contributor License Agreement as an individual. The individual agreement is needed to cover any of their contributions that are not owned by the corporation signing the Corporate Contributor License Agreement.

안드로이드 오픈소스 프로젝트에 일을 할 직원을 할당한 기업(또는 다른 독립체)은 기업 컨트리뷰터 라이센스 동의서를 작성해야한다. 이 버전의 동의서는 기업의 지정된 직원이 기여(코드, 문서 기타 등등) 제출을 허락하고, 저작권과 특허권을 허가한다. 기업 컨트리뷰터 라이센스 동의서는 모든 개발자들의 개별 컨트리뷰터 라이센스 동의서를 개인적으로 서명할 필요를 제거하지 않는다. 개별 동의서는 기업에 의한 기여를 그들이 포함하도록 필요하다.

Please note we based our agreements on the ones the Apache Software Foundation uses, which can be found on the Apache web site.

우리들의 동의서는 아파치 웹사이트에서 찾을수 있는 아파치 소프트웨어 재단을 사용한다.  


Why Apache Software License?

왜 아파치 소프트웨어 라이센스인가?

We are sometimes asked why Apache Software License 2.0 is the preferred license for Android. For userspace (that is, non-kernel) software, we do in fact prefer ASL2.0 (and similar licenses like BSD, MIT, etc.) over other licenses such as LGPL.

우리는 종종 왜 아파치 소프트웨어 라이센스 2.0이 안드로이드 라이센스에 우선시 되는 라이센스인지 질문을 받는다. 사용자공간(커널이 아닌) 소프트웨어에서, 우리는 ASL2.0(그리고 BSD, MIT 등과 같은 비슷한 라이센스)를 LGPL과 같은 다른 라이센스보다 선호한다.

Android is about freedom and choice. The purpose of Android is promote openness in the mobile world, and we don't believe it's possible to predict or dictate all the uses to which people will want to put our software. So, while we encourage everyone to make devices that are open and modifiable, we don't believe it is our place to force them to do so. Using LGPL libraries would often force them to do just that.

안드로이드는 자유와 선택에 관한 것이다. 안드로이드의 목적은 모바일 세계에서의 솔직함을 지향하는 것이다. 우리는 사용자들이 우리의 소프트웨어를 사용하는 것을 예측하고, 영향을 미치는 것을 불가능 하다고 믿는다. 즉, 우리가 모두에게 공개되고 변경가능한 기기들을 만들도록 하는 동안 그들을 그렇게 하도록 하는 것이 우리들의 역활이라고 생각하지 않는다. LGPL 라이브러리를 사용하는 것은 종종 그들을 단지 그것을 하게 만든다.

Here are some of our specific concerns:

아래는 우리가 걱정하는 것들이다 :

  • LGPL (in simplified terms) requires either: shipping of source to the application; a written offer for source; or linking the LGPL-ed library dynamically and allowing users to manually upgrade or replace the library. Since Android software is typically shipped in the form of a static system image, complying with these requirements ends up restricting OEMs' designs. (For instance, it's difficult for a user to replace a library on read-only flash storage.)
  • LGPL requires allowance of customer modification and reverse engineering for debugging those modifications. Most device makers do not want to have to be bound by these terms. So to minimize the burden on these companies, we minimize usage of LGPL software in userspace.
  • Historically, LGPL libraries have been the source of a large number of compliance problems for downstream device makers and application developers. Educating engineers on these issues is difficult and slow-going, unfortunately. It's critical to Android's success that it be as easy as possible for device makers to comply with the licenses. Given the difficulties with complying with LGPL in the past, it is most prudent to simply not use LGPL libraries if we can avoid it.
  • 간단히 LGPL는 : 어플리케이션으로 소스코드 배송, 제공하기 위해 작성된 소스코드, 또는 동적으로 LGPL라이브러리에 연결하고, 유저가 수동으로 업그레이드 또는 라이브러리를 바꾸는 것을 허락하는 것중 하나를 요구한다. 안드로이드 소프트웨어가 일반적으로 고정된 시스템 이미지로부터 배송되었을 때 부터, OEM업체의 제한된 디자인 자격요건을 따른다.(대신, 읽기만 가능한 플레시 저장소에 라이브러리를 교체하기 어렵다.)
  • LGPL은 사용자 수정사항과 리버스엔지니어링을 그들의 수정사항의 디버깅을 위해 필요로한다. 대부분의 기기 제조사들은 이러한 제약에 구속되기를 원치 않는다. 그래서 기업들의 짐을 최소화하고, 우리는 사용자공간의 LGPL소프트웨어 사용을 최소화한다.
  • 지금까지 LGPL 라이브러리는 소규모기기 제조사와 어플리케이션 개발자들의 많은 수의 규정 준수 문제의 원인이 되었다. 불행하게도, 엔지니어링교육을 하기에는 이런 이슈들은 어렵고 느리다. 이것은 가능한 쉽게 기기 제조사들이 라이센스를 따르도록 할려는 안드로이드 성공의 위협적이다. 과거의 LGPL를 따르는 것이 어려운 것은, 만약 그것을 피할 수 있다면, 가장 신중하게 간단히 LGPL 라이브러리를 사용하지 않는 것이다.
The issues discussed above are our reasons for preferring ASL2.0 for our own code. They aren't criticisms of LGPL or other licenses. We are passionate about this topic, even to the point where we've gone out of our way to make sure as much code as possible is ASL2.0 licensed. However, we love all free and open source licenses, and respect others' opinions and preferences. We've simply decided ASL2.0 is the right license for our goals.

위에 논의된 이슈는 우리가 우리의 코드를 위해 ASL2.0을 선호하는 이유이다. 그들은 LGPL 또는 다른 라이센스를 비판하지 않는다. 우리가 이 논쟁에 대해 열정적인것은, 우리방식에서 이 요점을 제외시켰지만, ASL2.0라이센스가 많은 코드를 생산이 가능한 것은 확실하다. 그러나 우리는 모든 자유와 오픈소스 라이센스, 그리고 다른 이들의 의견과 우선권을 지지한다. 우리는 간단히 ASL2.0을 결정한 것은 라이센스 권한이 우리의 목표에 적합하기 때문이다.


Posted by
|

Brand Guidelines

브랜드 가이드라인


The "Android" name, the Android logo, the "Google Play" brand, and other trademarks are property of Google Inc. and not part of the assets available through the Android Open Source Project.

"Android" 명칭, Android 로고, "Google Play" 브랜드, 그리고 다른 트레이드마크(상표)는 구글의 재산이다. 재산의 일부는 안드로이드 오픈소스 프로젝트에서 사용가능하다.


If you are interested in using these brands to indicate their association with your device, adhere to the guidelines on this page. These guidelines correspond to and complement the Brand Guidelines for Android App Developers and Google Brand Permissions.

만약 이 브랜드들을 당신의 장치나 협회에서 사용하고 싶다면, 가이드라인에 이 페이지를 추가시켜야 한다. 그 가이드라인들은 Brand Guidelines for Android App Developers와 Google Brand Permissions에 부합하고, 일치한다.


Android

안드로이드

Here are manufacturer guidelines for the Android brand and related assets.

아래에는 안드로이드 브랜드와 자산을 위한 제조사 가이드 라인이다.


Android in text

안드로이드 글자(안드로이드로 적힌 문자, 텍스트)

  • Android™ should have a trademark symbol the first time it appears in a creative.
  • "Android" should always be capitalized and is never plural or possessive.
  • The use of “Android” on hardware, packaging or marketing materials of device is restricted to Android-compatible devices only.
  • "Android” should be used only as a term to refer to the operating system (OS) of your device. If you are unsure whether your use meets our guidelines, follow this simple test: If you can replace "Android" with "the Android platform" and the text still makes sense, then you may use this term.
    • Incorrect: "Android XBrand Phone"
    • Correct: "XBrand phone on Android"
  • You may use “with Android” in plain black text with your logo. If used with your logo, "with Android" should be no larger than 90% of your logo’s size. First or most prominent instance of this use should be followed by a ™ symbol.
  • Android may be used only as a descriptor, as long as it is followed by a proper generic term. It cannot be framed as the product name or brand of your device.
    • Incorrect: "Android XBrand Phone"
    • Correct: "Android mobile device"
  • Android™은 최초로 만들어진 상표이다.
  • "Android"는 항상 대문자여야 하고, 결코 복수형이 될수 없고 소유할수 없다.
  • "Android"를 기기의 하드웨어, 포장지, 마켓팅 자료에 사용하기 위해서는 안드로이드에 호환되는 기기로 제한한다.
  • "Android"라는 용어는 장비의 운영체제(OS)를 제공함으로써만 사용되어져야한다. 만약 가이드라인에 적절한지 확실할 수 없다면 다음의 간단한 테스트를 해보아라 : 만약 "Android"를 "the Android platform"으로 바꾸어도 말이 된다면, 이 용어를 사용 해도 될 것이다.
    • 불가능: "Android XBrand Phone"
    • 가능: "XBrand phone on Android"
  • 만약 "with Android"를 검은 글자로 당신의 로고와 사용할수 있다. 만약 당신의 로고와 사용 할 경우 "with Android"는 당신의 로고 90%보다 크면 안된다. 가장 중요한 사항은 ™ 바로 뒤에 사용해야 한다.
  • 안드로이드는 완벽한 명칭 뒤에 기술자로써만 사용가능하다. 기기의 브랜드나 제품 명칭의 틀로써 사용할 수 없다.
    • 불가능: "Android XBrand Phone"
    • 가능: "Android mobile device"

Any use of the Android name must include this attribution in your communication:

Android is a trademark of Google Inc.

모든 커뮤니티에서는 무조건 안드로이드 이름을 아래의 문구와 같이 명시하여야 한다.

안드로이드는 구글의 상표이다.


Acceptable example

사용가능 예

Jelly Bean trademark example

8100 series trademark example

Unacceptable example

사용 불가능 예

XBrand trademark example


Android logo

안드로이드 로고

Unless expressly authorized by Google through written agreement, the Android logo and custom typeface may not be used (with or without the Android robot).

구글에 의해서 허가를 받지 않은 개인적인 형식의 (안드로이드 로봇이 있던지 없던지) 안드로이드 로고는 사용할 수 없다. 

No LogoNo Logo


Android Robot

안드로이드 로봇

 android-robot

100x118

200x237

Illustrator

 The Android robot can be used, reproduced, and modified freely in marketing communications with proper attribution. For details, refer to App Developers Brand Guidelines and the Creative Commons license.


 android-robot

100x118

200x237

Illustrator

 안드로이드 로봇은 자유롭게 마켓팅 커뮤니케이션에서 적절한 형태의 재활용, 재생산이 가능하다. 자세한 사항은 App Developers Brand Guidelines(앱 개발자 브랜드 가이드라인)와 the Creative Commons license(일반적인 제작 저작권)을 참조하면 된다.




no-peace-robot 

 The Android Peace Robot or any variation of the Android Peace Robot (such as the Android robot with a peace sign) may not be used in partner marketing.

 no-peace-robot

 안드로이드 피스로봇(평화로봇, 손모양 V자인걸 말하는듯..)이나 다양한 안드로이드 피스로봇(예를 들어 평화를 상지하는 V손모양)은 파트너마켓팅으로 사용 할 수 없다.



Google Play

구글 플레이


Use of the “Google Play” name and the Google Play Store icon on the packaging of the hardware, marketing materials of the hardware, or the hardware itself is allowed only on devices licensed to access Google Play. For a list of devices licensed to use Google Play, refer to Supported devices.

하드웨어 패키징, 하드웨어 마켓팅 자료, 또는 하드웨어 자체에 "Google Play" 이름과 구글 프레이 스토어 아이콘을 사용하기 위해서는 구글 플레이에 접근 허가를 받은 디바이스여아 한다. 구글플레이를 사용 할 수 있는 기기 리스트는 지원되는 기기지원되는 기기를 참조 하면 된다.


Other Brand

다른 브랜드

Android Auto, Android TV, and Android Wear are brands owned by Google. These brands require Google proprietary software that runs on top of Android and is available only through a license with Google. For information on how to request a license, see Contact Us.

안드로이드 오토, 안드로이드 티비 그리고 안드로이드 웨어는 구글의 브랜드이다. 이 브랜드들은 안드로이드 위에서 실행되는 구글의 소프트웨어를 요구하고, 구글의 허가를 통해 사용 가능하다. 사용허가 신청 정보는 다음 링크를 통해 확인 할 수 있다.


Questions

질문

For additional brand usage information, contact the Android Partner Marketing team by submitting the Partner Brand Inquiry Form.

추가적인 브랜드 사용 정보를 위해서는 안드로이드 파트너 마켓팅 팀에 파트너 브랜드 질문서를 제출하면 된다.

Posted by
|

원문 : https://source.android.com/source/roles.html


Project Roles

프로젝트 역할


The Android Open Source Project (AOSP) includes individuals working in a variety of roles. Google is responsible for Android product management and the engineering process for the core framework and platform; however, the project considers contributions from any source, not just Google. This page describes the kinds of roles that interested parties can take on.

안드로이드 오픈소스 프로젝트에 각양각색의 역활을 개개인이 담당해 왔다. 구글은 안드로이드 제품 관리와 중요 프레임워크와 플랫폼의 프로세스 인지니어링을 책임진다. 그러나 구글 뿐만 아니라 많은 참여가 모든 소스에서 있었다. 이 페이지는 참여 가능한 흥미로운 역활의 종류 부분을 설명 한다.


Contributor

컨트리뷰터

"Contributors" are those making contributions to the AOSP source code, including both employees of Google or other companies, as well as individual developers who are contributing to Android on their own behalf. There is no distinction between contributors who are employed by Google and those who are not; all engineers use the same tools (git, repo, and gerrit), follow the same code review process, and are subject to the same requirements on code style and so on.

"컨트리뷰터"는 구글 또는 다른 기업 직원과 자신의 이익을 위한 개인적인 개발자들로 AOSP 소스 코드에 기여하는 사람들이다. 구글 직원가 그렇지 않은 사람들의 차이는 없다. 모든 인지니어들은 같은 툴(git, repo와 gerrit)을 사용하고, 같은 코드 리뷰 절차와 같은 코드 스타일을 따른다.


Developer

디벨로퍼

"Developers" are engineers writing applications that run on Android devices. There is often little difference in skillset between a developer and a contributor. But AOSP uses "developer" to distinguish between engineers using the platform and those contributing to it. Developers (along with users) are the "customers" of the platform the contributors create. As such, we talk about developers a lot, though this isn't technically a separate role in the AOSP per se.

"디벨로퍼"는 안드로이드 기기에서 실행되는 어플리케이션을 작성하는 엔지니어들이다. 디벨로퍼와 컨트리뷰터 사이에는 스킬셋에서 약간의 다른점이 종종있다. 하지만 AOSP에서는 "디벨로퍼"를 플랫폼을 사용하는 엔지니어 인지 기여하는 엔지니어인지로 구분 짓는다. 디벨로퍼(사용자와 같이)는 컨트리뷰터가 만든 플랫폼의 "커스터머(소비자)"이다.  우리는 개발자를 많이 말하지만, AOSP 자체의 기술적으로 나눈 것은 아니다.


Verfier

버리피어

"Verifiers" are responsible for testing change requests. After individuals have submitted a significant amount of high-quality code to the project, the project leads might invite them to become verifiers.

Note: At this time, verifiers act similarly to approvers.

"버리피어"는 변화된 요청을 테스트한다. 개인이 중요한 고난이도 코드를 제출을 하면, 프로젝트 리더는 그들을 버리피어로 초대 한다.

추신 : 버리피어는 어프로버와 비슷하다.


Approver

어프로버

"Approvers" are experienced members of the project who have demonstrated their design skills and have made significant technical contributions to the project. In the code-review process, an approver decides whether to include or exclude a change. Project leads (who are typically employed by Google) choose the approvers, sometimes promoting to this position verifiers who have demonstrated their expertise within a specific project.

"어프로버"는 프로젝트에서 디자인 스킬을 입증했거나, 중요한 기술적 기여자들이다. 코드 검토 과정에서 어프로버는 변화된 코드를 포함 시킬지 제외시킬지 결정을 한다. 프로젝트 리더(구글 직원)가 어프로버를 선택을 한다. 종종, 프로젝트의 전문지식을 입증받은 버리피어들이 홍보 하기도 한다.


Project Lead

프로젝트 리드

Android consists of a number of sub-projects; you can see these in the git repository as individual .git files. "Project leads" are senior contributors who oversee the engineering for individual Android projects. Typically these project leads are Google employees. A project lead for an individual project is responsible for the following:

안드로이드는 많은 서브 프로젝트들로 구성되어 있다. 깃 저장소에서 개별적으로 확인 할 수 있다. "프로젝트 리더"는 개별적인 안드로이드 엔지니어링을 총괄하는 중요한 콘트리뷰터이다. 특별히 안드로이드 프로젝트에서는 구글 직원들이다. 개별적인 프로젝트의 프로젝트 리더는 아래의 책임이 있다.

  • Lead all technical aspects of the project, including the project roadmap, development, release cycles, versioning, and quality assurance (QA).

  • Ensure the project is tested by QA in time for scheduled Android platform releases.

  • Designate Verifiers and Approvers for submitted patches

  • Be fair and unbiased while reviewing changes. Accept or reject patches based on technical merit and alignment with the Android strategy.

  • Review changes in a timely manner and make best efforts to communicate when changes are not accepted.

  • Optionally maintain a web site for the project for information and documents specific to the project.

  • Act as a facilitator in resolving technical conflicts.

  • Be a public face for the project and the go-to person for questions related to the project.


  • 프로젝트 로드맵, 개발, 릴리즈 사이클, 버전 관리, 품질 관리(QA)을 포함한 프로젝트의 기술적인 측면을 이끌어야한다.
  • 정해진 안드로이드 플랫폼 릴리즈를 위해 품질 관리로써 프로젝트 테스트를 확실히 해야한다.
  • 제출된 부분들을 위해 버리피어와 어프로버를 지정해야 한다.
  • 코드 검토는 공평하고 편견없이 한다. 패치 내용의 인정과  거절은 기술적인 이익과 안드로이드 정책의 일정함을 기준으로 한다.
  • 코드 검토는 일정한 방식으로 하고, 거절된 코드는 정중히 전달한다.
  • 프로젝트 정보가 있는 웹사이트와 프로젝트에 관한 문서는 선택적으로 유지시킨다.
  • 기술적 문제를 풀수 있게 협력한다.
  • 프로젝트에 대해 일반적인 입장을 취하고, 프로젝트에 대한 질문은 인간적으로 대한다.


Posted by
|