(1) 앱 품질 관련 가이드라인

1. 앱 출시 이후 품질 개선하기
2. 핵심 앱 품질 가이드라인
3. 태블릿 앱 품질 체크리스트


자신이 개발한 앱이 많은 사람에게 오래도록 사랑받을 수 있도록 만드는 방법이 궁금하신가요? 효과적으로 사용자에게 ‘좋은 인상을 주고, 설치하고, 평가하게’ 하는 순환 고리를 만드는 방법은, 바로  “제품을 개선하는 것"입니다. 이는 오랜 시간에 걸쳐 입증된 확실한 방법입니다. 앱을 출시한 이후 어떻게 품질을 높일 수 있는지 앱 출시 이후 품질 개선하기에서 확인해 보세요!

앱의 품질은 설치 및 사용자 등급이나 평가, 참여, 사용자 보유 등의 측면에서 성공 여부에 장기적으로 직접적인 영향을 줍니다. 안드로이드 사용자는 고품질의 앱을 기대합니다. 핵심 앱 품질 가이드라인에서 다루는 핵심 앱 품질 기준 내용 및 관련 테스트를 통해서 기본적인 품질을 측정하실 수 있습니다. 모든 안드로이드 앱은 반드시 본 기준에 부합해야 합니다.

태블릿 앱은 강력한 기능은 물론, 직관적이고 우수한 UI 디자인을 제공하여 사용자들의 기본적인 기대에 부응하도록 해야합니다. 본인의 앱이 태블릿 사용자를 타겟으로 하고 있다면, 태블릿 앱 품질 체크리스트를 통해서 앱 성공 여부에 큰 영향을 주는 품질이나 기능, UI 등 주요 요소에 초점을 맞추어 품질을 확인할수 있습니다. 사용자에게 최고의 제품을 제공하기 위해서 체크리스트 권장 사항을 가능한 최대한으로 적용하시기 바랍니다.


(2) 안드로이드 디자인 관련 문서

1. 안드로이드 디자인 가이드 (외부 번역)
http://klutzy.github.com/android-design-ko/

*중요 문서 (위의 디자인가이드에 포함)
> Action Bar
> Navigation
> Pure Android
> Multi-pane Layouts


2. 태블릿용 앱을 만들 때 반드시 참고!! 
태블릿용 앱 디자인 도움말

3. 일관성있는 방향 탐색 기능을 제공하세요.
이전(Back) 버튼 및 상위(Up) 버튼 방향 탐색 가이드라인


(3) 안드로이드 앱 홍보 관련 문서

1. Play 스토어에서 자신의 앱을 홍보하기 위해서 반드시 “추천 이미지" 등록!! 
Play 스토어 추천 이미지 가이드라인

2. 전 세계 사용자를 타겟으로~! 국가별로 홍보 그래픽을 현지화하세요! 

Google Play에서 홍보 그래픽을 국가별로 현지화 

(4) 기타 문서


1. 안드로이드 애플리케이션 SD 카드에 설치하기
2. 안드로이드 사용자 데이터 처리


이번 GCi 2012에는 전년 대비 20배에 가까운 한국 학생들이 참여하여, 이를 기념하고자 지난 1월 25일 금요일, 구글 코리아에서는 참가자들을 오피스로 초청하여 점심 식사를 나누고 현직 엔지니어들과 함께 다양한 이야기를 나누는 뜻깊은 시간을 가졌습니다.


이번 행사에는 총 39명의 등록자 중에서, 초청 메일에 회신을 준 19명의 학생들이 참여하였고 오픈 소스 및 개발에 관련한 조언을 위해 5명의 현직 엔지니어분들이 함께 했습니다.

* 초청 엔지니어 소개*
김남형/김준수/김민찬 (LG전자/ 리눅스 커널 커미터)
홍영기 (LG전자/Chromium 커미터)
정재윤 (Google 엔지니어)




자기 소개를 시작으로, 오픈 소스의 장/단점과 현재 개발 관심 분야에 대해서 나누는 질의 응답시간을 가졌습니다. 학생들의 적극적인 질문과 다양한 분야에 대한 관심으로 엔지니어분들과 함께하는 Q&A 세션이 금세 지나가고, 다섯 개의 소그룹으로 나눠서 멘토와 함께 하는 점심 식사 시간을 가졌습니다.

*엔지니어 멘토들이 후배들에게 전해준  팁*
지금의 열정과 관심을 잃지 않고 지속적으로 자신의 관심분야를 찾아갈 것.
오픈 소스 커뮤니티에서 위축되지 말고 적극적으로 참여해 볼 것.
개발자로서의 기본적인 소양을 기르기 위한 노력을 할 것 - 소통 능력, 논리력, 수학 등
보다 깊이 있는 실력을 배양하기 위해서 기본에 충실할 것. 

실제로 등록 이후에 과제 제출을 하기까지가 쉽지 않아 많은 학생이 과제를 완료하지 못했지만, 이번 참가 자체에 큰 의미가 있었다고 봅니다. 2013년에 있을 GCi 2013에서는 많은 한국 학생들이 등록과 함께 과제 제출까지 완료하는 더욱 적극적인 참여를 기대해 봅니다!

참가자 및 초청 엔지니어 단체 사진



이를 위해서 여러 단계의 과정이 필요합니다. 전체 설명에 앞서, 짧은 버전으로 설명하자면 다음과 같습니다.
> Google Play 서비스를 통해 사용 가능한 GoogleAuthUtil 클래스로 “ID Token”이라는 스트링을 받습니다. 해당 토큰을 벡엔드로 보내고 벡엔드에서는 토큰을 사용하여 빠르고 간단하게 어떤 앱이 보냈으며 사용자가 누구인지를 검증합니다.



이러한 기능은 App Engine의 새로운 Cloud Endpoints 기능과 같은 Google facilities에 내장되어 있는데, 앱/백엔드 아이덴티티를 간단한 프로그래밍 모델로 만든 것입니다. 이제 세부사항을 살펴 봅시다. 



앱 등록



본 과정에서 Google API 콘솔을 꽤 많이 사용하게 될 것입니다. 이를 위해서 새로운 프로젝트를 만들어야 합니다. 하지만 읽기 편한 괜찮은 이름과 이미지로 브랜드화 한다고 해도, 해당 리소스는 본 시나리오 상에서는 사용되지 않습니다.
프로젝트가 다수의 다른 Google API에 접근할 수 있도록 승인할 수 있습니다. 하지만 해당 시나리오에서는 필요하지 않은 절차임을 거듭 말씀드립니다. 그리고 사람들을 프로젝트 멤버로 승인할 때, 관리자 기능을 부여하게 되기 때문에 신중하게 결정해야 합니다.



Client ID 만들기



프로젝트에 두 개의 서로 다른 OAuth 2.0 “Client ID”를 만들어야 합니다. 하나는 “웹 애플리케이션을 위한 Client ID”입니다. 이름 붙이기나 이미지 작업은 무시해도 됩니다. 필요한 것은 Client-ID입니다. Client ID는 9414861317621.apps.googleusercontent.com와 같은 형태를 가지고 있습니다.

그리고 다른 하나는 “안드로이드 앱을 위한 Client ID”입니다. 해당 Client ID를 만들기 위해서는 앱 패키지 이름과 인증 서명이라는 두 가지 정보를 제공해야 합니다. 패키지 이름은 AndroidManifest.xml에서 상위 “package” 속성에서 주어지는 대로 자바 스타일 리버스 DNS입니다. 예를 들면 com.example.identity와 같은 형태입니다.

앱의 인증 서명을 얻으려면, 다음과 같이 Shell 명령어를 사용하세요.
$ keytool -exportcert -alias <your-key-name> -keystore <your-key-store-file> -v -list
“SHA1”라고 표기된 부분을 복사해서, 개발자 콘솔 필드에 복사하고 Client ID를 생성합니다. 다시 말씀드리지만, 필요한 것은 Client ID 스크링 뿐입니다. 



안드로이드 앱에서



Google Play 서비스 GoogleAuthUtil 클래스를 호출하여 ID 토큰을 받아와야 합니다. 해당 절차는 Access Token 토큰 받아오기에 설명되어 있습니다. 추가적으로 좋은 방법이 하나 있습니다. 바로 getToken(email, scope) 메소드에 scope 인자 값입니다. 해당 값은 스트링으로 audience:server:client_id:X 으로 표기되는데, X가 웹 앱을 위한 위에서 설명한 Client ID 값입니다. Client ID가 위에서 주어진 예제 값이라면 scope 인자 값은 audience:server:client_id:9414861317621.apps.googleusercontent.com가 되겠죠. 



문제 해결



보통 OAuth을 요청할 때, 디바이스 사용자는 본인의 신원을 사용하여 어떤 리소스 등을 접근해도 괜찮은지 확인하는 절차를 접하게 됩니다. 하지만 이 경우에 해당 프로젝트를 제어하는 개발자와 관련 사항에 대해 사용자가 이미 동의한 상태이기 때문에 시스템에서는 scope 인자 안에 있는 서버측 Client ID를 보고, 안드로이드 앱과 동일한 프로젝트라는 것을 확인하여 사용자를 번거롭게 할 필요 없이 토큰을 주게 됩니다. 



토큰 보내기



서버와 백엔드와 통신할 준비가 되면 토큰 스트링을 보내야 합니다. 가장 좋은 방법은 POST 메세지 안에 포함시켜서 보내는 것입니다. URL 파라미터로 넣을 수 있지만, 이 경우에 종종 느리게 동작할 수 있습니다. 중간 침입자가 토큰을 가로채지 못하도록 하기 위해서 반드시 HTTP 연결을 사용해야 합니다. 추가적인 통신을 주고 받을 필요가 없습니다. 백엔드에 게임 최고점을 보내는 것이라면, 추가 인자로 ID 토큰 스트링을 넣으면 됩니다.  

토큰 사용하기


서버가 안드로이드 앱으로부터 토큰을 받을 때, 이를 검증하는 것이 매우 중요합니다. 이를 위해서 다음과 같은 두 단계가 필요로 합니다.

1. 실제로 Google에서 서명을 받았는지 검증한다
2. 실제로 의도된 것인지 검증한다.


서명 검증하기

Google의 공개/개인 키 쌍을 사용하여 서명되고, www.googleapis.com/oauth2/v1/certs에서 Google이 공개 키를 공개한다고 (주기적으로 변경) 밝혀졌습니다. 그렇다면 가서 확인해 보겠습니다.

ID 토큰(실제로 JSON 웹 토큰)이 앞서 말한 인증 중 하나로 서명되었는지 확인해야 합니다. 다행히 이를 위한 괜찮은 라이브러리들이 있습니다. 본 포스팅에서 Java, Ruby, PHP를 위한 설명을 해드리겠습니다. 라이브러리들은 Google 인증을 캐쉬하고 필요할 때만 리프레시할 수 있기 때문에 검증이 (거의 항상) 빠른 정적 호출입니다.  



토큰 필드 검증하기



ID 토큰이 JSON 페이로드를 가지고 있으며, 서명을 인증하는 대부분의 라이브러리도 해시나 딕셔너리 등으로 전달해 준다고 밝혀졌습니다. 따라서 aud나 cid, email과 같은 지정된 필드를 검색할 수 있습니다.

첫째로, aud 필드를 보고 안드로이드 앱의 scope 인자에 포함된 스트링인 Client ID와 동일한지 확인해야 합니다. 이 단계를 절대 생략해서는 안됩니다. ID 토큰을 검증하지 않으면, 다른 개발자가 여러분의 서비스로 요청을 도용할 수도 있습니다.

선택적으로, cid라는 필드를 보고 안드로이드 앱 Client ID와 동일한지 확인할 수 있습니다. 참고로, 최상위 프로젝트에서 고유의 Client ID를 가진 여러개의 서로 다른 안드로이드 클라이언트 앱을 가질 수 있습니다. 


세 가지 과정을 모두 완료했다고 가정해 봅시다. 그렇다면 다음과 같은 사항을 알 수 있습니다.
1. 토큰이 Google에서 발행되었다.
2. 페이로드의 이메일 필드에서 확인된 사용자의 디바이스로 토큰이 보내졌다.

그리고 다음 사항에 대해서 "고신뢰(high confidence)"를 가질 수 있습니다.
3. 페이로드의 cid 필드에서 Client ID로 확인된 안드로이드 앱으로 토큰을 얻었다. 

비호환 및 루팅된 안드로이드 디바이스는 해당 정보를 조작할 수가 있기 때문에 Client ID만이 “고신뢰(high confidence)”를 가지고 있다. 하지만 해당 디바이스들이 가짜 Google 서명이나 디바이스 사용자 인증을 조작할 수 없다.



다음 단계는?


다음은 여러분의 손에 달려 있습니다. 어떤 사용자, 어떤 앱과 통신해야 하는지 알고 있기 때문에 해당 정보로 무엇을 해야할지는 본인에게 달려 있습니다.



코드 예제



다음은 Google 자바 라이브러리를 사용하여 ID 토큰 체커를 구현하는 자바 클래스입니다.

import java.io.IOException;
import java.security.GeneralSecurityException;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
public class Checker {

    private final List mClientIDs;
    private final String mAudience;
    private final GoogleIdTokenVerifier mVerifier;
    private final JsonFactory mJFactory;
    private String mProblem = "Verification failed. (Time-out?)";

    public Checker(String[] clientIDs, String audience) {
        mClientIDs = Arrays.asList(clientIDs);
        mAudience = audience;
        NetHttpTransport transport = new NetHttpTransport();
        mJFactory = new GsonFactory();
        mVerifier = new GoogleIdTokenVerifier(transport, mJFactory);
    }

    public GoogleIdToken.Payload check(String tokenString) {
        GoogleIdToken.Payload payload = null;
        try {
            GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
            if (mVerifier.verify(token)) {
                GoogleIdToken.Payload tempPayload = token.getPayload();
                if (!tempPayload.getAudience().equals(mAudience))
                    mProblem = "Audience mismatch";
                else if (!mClientIDs.contains(tempPayload.getIssuee()))
                    mProblem = "Client ID mismatch";
                else
                    payload = tempPayload;
            }
        } catch (GeneralSecurityException e) {
            mProblem = "Security issue: " + e.getLocalizedMessage();
        } catch (IOException e) {
            mProblem = "Network problem: " + e.getLocalizedMessage();
        }
        return payload;
    }

    public String problem() {
        return mProblem;
    }
}

Ruby로 잡업하려면 google-id-token Ruby gem을 인스톨해서 아래와 같이 작업하세요.

require 'google-id-token'
validator = GoogleIDToken::Validator.new
jwt = validator.check(token, required_audience, required_client_id)
if jwt
  email = jwt['email']
else
  report "Cannot validate: #{validator.problem}"
end


PHP 프로그래머는 PHP를 위한 Google API 클라이언트 라이브러리를 확인하세요. 특히 apiOAuth2.php에서 verifyIdToken 함수를 확인하세요.


Daydream을 지원하는 앱은 어트랙트 모드에서 안드로이드 UI 툴킷 전체를 사용할 수 있습니다. 즉, 레이아웃, 애니메이션, 3D, 사용자 지정 뷰 등을 포함하여 앱이 현재 가지고 있는 컴포넌트를 쉽게 사용할 수 있고, 보다 잔잔한 프리젠테이션을 위해서 해당 컴포넌트들을 쉽게 조합할 수 있습니다. 또한 터치스크린 입력을 사용할 수 있기 때문에 원한다면 여러가지 인터랙티브한 경험을 제공할 수도 있습니다.

Daydream은 본인의 앱을 홍보할 수 있는 기회를 다소 제공합니다. 앱의 복잡한 면을 감추고 시각적으로 흥미있는 부분을 살려서 사용자가 앱 전체에 흥미를 느낄 수 있도록 합니다. 비디오 게임의 시선 끌기 모드 (attract mode) 처럼 말이죠. 

그림 1. Google 세상보기(Google Currents)에서는 부드럽고 지속적으로 움직이는 뉴스 화면에서 화제거리를 스크롤한다.


구글 세상보기(Google Currents)는 이러한 방법으로 접근한 좋은 예입니다. Daydream으로서 시각적으로 흥미있는 화제거리를 선택하여 화면 슬라이딩으로 보여 줍니다. 화제거리를 터치하면 세상보기는 화면 전체로 해당 내용을 보여주며, 다시 터치하면 전체 실행 중인 앱에서 읽을 수 있습니다.  


Daydream 구조



각 Daydream 구현은 android.service.dreams.DreamService의 서브클래스입니다. DreamService를 확장하면, 간단한 액티비티같은 라이프사이클 API에 접근할 수 있습니다.

서브 클래스에서 오버라이드하기 위한 DreamService 주요 메소드 
(서브클래스 구현 호출을 잊지 말 것)




DreamService 에서 사용하는 주요 메소드
  • setContentView(): Daydream을 위한 배경 설정. XML 리소스 ID나 뷰 인스턴스, 자신이 구현한 사용자 지정 뷰일 수도 있음.
  •  
  • setInteractive(boolean):디폴트로 Daydream은 사용자가 스크린을 터치하면 종료됨. 전형적인 화면 보호기처럼. 사용자가 터치하고 뷰에서 상호 작용하기 원하면 setInteractive(true)를 호출
  • setScreenBright(boolean): 디폴트로 Daydream에서는 가장 밝은 화면으로 설정되기에, 적절치 않은 경우도 있음 (예: 어두운 방). 따라서 false로 설정하면 밝기가 아주 낮은 수준으로 설정됨

마지막으로, 시스템에 Daydream을 홍보하려면, AndroidManifest.xml에 <service>를 생성하세요.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.app">
   <uses-sdk android:targetSdkVersion="17" android:minSdkVersion="17" />

   <application>
       <service
           android:name=".ExampleDaydream"
           android:exported="true"
           android:label="@string/my_daydream_name">
           <intent-filter>
               <action android:name="android.service.dreams.DreamService" />
               <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
           <meta-data
               android:name="android.service.dream"
               android:resource="@xml/dream_info" />
       </service>
   </application>
</manifest>


<meta-data> 태그는 선택사항으로, Daydream에 대한 Activity 설정을 명시하는 XML 리소스를 가리킵니다. 사용자는 Settings 앱에서 해당 Daydream 이름 옆에 있는 설정(settings) 아이콘을 눌러서 확인할 수 있습니다. 

<!-- res/xml/dream_info.xml -->
<?xml version="1.0" encoding="utf-8"?>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
   android:settingsActivity="com.example.app/.ExampleDreamSettingsActivity" />


다음은 여러분이 시작해 볼 수 있는 전형적인 화면 보호기 예제로, 움직이는 로고를 보여줍니다. TimeAnimator 를 사용하여 구현한 매끄럽고 부드러운 60Hz 애니메이션을 제공합니다.


그림 2. Will one of them hit the corner?

public class BouncerDaydream extends DreamService {
   @Override
   public void onDreamingStarted() {
       super.onDreamingStarted();

       // Our content view will take care of animating its children.
       final Bouncer bouncer = new Bouncer(this);
       bouncer.setLayoutParams(new
           ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       bouncer.setSpeed(200); // pixels/sec

       // Add some views that will be bounced around.
       // Here I'm using ImageViews but they could be any kind of
       // View or ViewGroup, constructed in Java or inflated from
       // resources.
       for (int i=0; i<5; i++) {
           final FrameLayout.LayoutParams lp
               = new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
           final ImageView image = new ImageView(this);
           image.setImageResource(R.drawable.android);
           image.setBackgroundColor(0xFF004000);
           bouncer.addView(image, lp);
       }

       setContentView(bouncer);
   }
}

public class Bouncer extends FrameLayout implements TimeAnimator.TimeListener {
   private float mMaxSpeed;
   private final TimeAnimator mAnimator;
   private int mWidth, mHeight;

   public Bouncer(Context context) {
       this(context, null);
   }

   public Bouncer(Context context, AttributeSet attrs) {
       this(context, attrs, 0);
   }

   public Bouncer(Context context, AttributeSet attrs, int flags) {
       super(context, attrs, flags);
       mAnimator = new TimeAnimator();
       mAnimator.setTimeListener(this);
   }

   /**
    * Start the bouncing as soon as we’re on screen.
    */
   @Override
   public void onAttachedToWindow() {
       super.onAttachedToWindow();
       mAnimator.start();
   }

   /**
    * Stop animations when the view hierarchy is torn down.
    */
   @Override
   public void onDetachedFromWindow() {
       mAnimator.cancel();
       super.onDetachedFromWindow();
   }

   /**
    * Whenever a view is added, place it randomly.
    */
   @Override
   public void addView(View v, ViewGroup.LayoutParams lp) {
       super.addView(v, lp);
       setupView(v);
   }

   /**
    * Reposition all children when the container size changes.
    */
   @Override
   protected void onSizeChanged (int w, int h, int oldw, int oldh) {
       super.onSizeChanged(w, h, oldw, oldh);
       mWidth = w;
       mHeight = h;
       for (int i=0; i<getChildCount(); i++) {
           setupView(getChildAt(i));
       }
   }

   /**
    * Bouncing view setup: random placement, random velocity.
    */
   private void setupView(View v) {
       final PointF p = new PointF();
       final float a = (float) (Math.random()*360);
       p.x = mMaxSpeed * (float)(Math.cos(a));
       p.y = mMaxSpeed * (float)(Math.sin(a));
       v.setTag(p);
       v.setX((float) (Math.random() * (mWidth - v.getWidth())));
       v.setY((float) (Math.random() * (mHeight - v.getHeight())));
   }

   /**
    * Every TimeAnimator frame, nudge each bouncing view along.
    */
   public void onTimeUpdate(TimeAnimator animation, long elapsed, long dt_ms) {
       final float dt = dt_ms / 1000f; // seconds
       for (int i=0; i<getChildCount(); i++) {
           final View view = getChildAt(i);
           final PointF v = (PointF) view.getTag();

           // step view for velocity * time
           view.setX(view.getX() + v.x * dt);
           view.setY(view.getY() + v.y * dt);

           // handle reflections
           final float l = view.getX();
           final float t = view.getY();
           final float r = l + view.getWidth();
           final float b = t + view.getHeight();
           boolean flipX = false, flipY = false;
           if (r > mWidth) {
               view.setX(view.getX() - 2 * (r - mWidth));
               flipX = true;
           } else if (l < 0) {
               view.setX(-l);
               flipX = true;
           }
           if (b > mHeight) {
               view.setY(view.getY() - 2 * (b - mHeight));
               flipY = true;
           } else if (t < 0) {
               view.setY(-t);
               flipY = true;
           }
           if (flipX) v.x *= -1;
           if (flipY) v.y *= -1;
       }
   }

   public void setSpeed(float s) {
       mMaxSpeed = s;
   }
}


이 예제 코드로 본인이 원하는 것을 작업해서 화면에 넣어 줄 필요(간단한 그래픽이나 에러 메시지처럼) 없이 사용자에게 손쉽게 보여 줄 수 있습니다. 또한 더욱 복잡한 Daydream 프로젝트를 시작하기에도 좋은 샘플입니다. 


기타 주의 사항


> 우선, 시스템에 피해를 주지 말라 
Daydream은 디바이스가 충전될 때 실행된다. 하지만 Daydream이 지나치게 많은 CPU를 소모하게 되는 경우 충전이 매우 느리거나 전혀 진행되지 않을 수도 있다. 디바이스가 충전되고 있지 않음을 감지하면 시스템은 Daydream을 멈추게 만든다. 따라서 적절한 시간에 충전할수 있도록 코드에서 충분한 전력을 남겨 두도록 한다.

> 화면 잠금을 준수하라
Daydream은 키가드(keyguard) 작동상에서 실행된다. 다시 말해, 민감한 콘텐츠를 보여줄 수 있는 경우라면, 사용자가 해당 콘텐츠를 제어할 수 있는 사용자 툴을 제공할 필요가 있다. 예를 들어, 포토 테이블과 포토 프레임에서는 사용자가 디스플레이될 사진을 앨범에서 선택할 수 있도록 해야한다. (당황스러운 슬라이드쇼를 피함)

> 화면 밝기
Daydream에서 사용되는 장소를 판단하고 이에 따라 setScreenBright()를 사용하여 화면 밝기를 조정하라. 그리고 필요에 따라서 어둡거나 밟은 색상을 사용하라. 침대 머리맡 시계는 탁상 시계보다 어두워야 한다. 두 가지 모두에 쓰일 것이라고 판단되면 사용자에게 선택권을 주어야 한다.

> 상태바 숨김 여부
많은 사용자들이 배터리 충전이나 날짜/시간 정보에 즉각적으로 접근할 수 있기를 원할 것이다. 따라서 setFullscreen() 사용을 피해야 한다. 특히 Daydream이 예술적인 측면보다 정보를 더 많이 가지고 있을 때 더욱 그렇다. Daydream은 요란스럽지 않으면서도 시간 정보와 충전 상태를 보여 줄 수 있게 “lights out”모드로 상태바를 시작할 것이다 (View.SYSTEM_UI_FLAG_LOW_PROFILE).

> 언제 설정을 사용하나
일반적으로 어느정도 재량권을 가지고 Daydream 설정에 제어도구나 다이얼을 추가할 수 있다. 사실 이것은 개인화 기능이기에 사용자 편의대로 수정할 수 있도록 권장되어야 한다. 하지만 때로 심미적인 환경에서 더욱 설득력있는 경험이 나올 수 있기 때문에 소수의 세련되고 아름다운 구성 중에서 선택할 수 있도록 사용자에게 선택권을 주도록 하라 (비행기 조종석의 제어권을 모두 내어주기 보다).

> 하나 이상이 될 수도 있다
설정에서 사용자에게 완전히 상이한 디스플레이 모드 사이에서 선택하도록 한다면, Daydream을 다중 DreamService 구현으로 나누라. 예를 들어 안드로이드 4.2에서 포토 갤러리는 포토 테이블과 포토 프레임 DayDream을 모두 제공한다.

> 개발용 액티비티를 사용하라
대부분 안드로이드 개발 툴은 기존 안드로이드 앱 개발 및 디버깅에 최적화되어 있다. DreamService와 액티비티가 매우 흡사하기 때문에 DreamService과 동일한 콘텐츠 뷰를 호스트하는 테스트용 액티비티를 만드는 것이 유용할 수 있다. 다른 안드로이드 프로젝트처럼 IDE에서 코드를 런치하여 쉽게 테스트 할 수 있다.

자, 이제 충분히 설명 드린 것 같습니다. 여러분의 앱에 Daydream 지원을 개발할 도구가 있습니다. 여러분이 즐겁게 개발하면 사용자들도 즐길 수 있을 것입니다. 그리고 Google Play에 반짝이는 새 APK를 업로드할 때, 앱 설명을 추가하여 Daydream을 찾는 사용자들이 검색할 수 있도록 해주세요!

기타 문서 및 샘플 예제
DreamService API 문서
샘플 코드: BouncerDaydream (본 포스팅 코드 예제를 위해 완성된 프로젝트)
샘플 코드: WebView (HTML 페이지를 보여주는 Daydream)
샘플 코드: Colors (OpenGL ES 2.0 및 TextureView를 보여주는 Daydream)