공개 API를 작업하기위해서는 아래와 같은 작업을 해야만 합니다.
아래의 작업은 tat를 공개 Api로 작업 하는 내용입니다.

모듈별로 작업하기 위해서는 make update-api TARGET_PRODUCT=[모델명] 을 입력하시면 됩니다.

(1)  i add it to frameworks/base/tat. 


(2) modify build/core/pathmap.mk 


like this: 
FRAMEWORKS_BASE_SUBDIRS := \ 
        $(addsuffix /java, \ 
            core \ 
            graphics \ 
            im \ 
            location \ 
            media \ 
            opengl \ 
            sax \ 
            telephony \ 
            wifi \ 
            tat \ 
         ) 


(3) make and make sdk. (successfully). 

Unhanlded fault
커널 로그를 보면 보통 위의  로그를 찍어주면서 시작하게 됩니다. 
보통 커널 패닉 또는 다운로드 모드로 진입하게 됩니다.
아직 커널 관련해서 잘 몰라서 왜 위의 에러가 발생하는지에 대해서는 잘 모릅니다.
아래의 내용은 다른 웹사이트에서 발췌한 내용입니다.
참고하시길 바랍니다.

추후에 커널에 대해서 알게된다면 정리하도록 하겠습니다.
 
I found this error message when access memory io 

"Unhandled fault: external abort on non-linefetch"

1. I suspect memory io address then I access another memory io address but it was OK
2. I thought my driver has a some interface problem, because kernel has some changes for driver interface as like ioctl - but it's not
3. then I thought ioremap and request_mem_region usage are changing - but it's not
4. I tried this memory address at machine start but I got same message 
5. Finally I suspected memory IO Pheriperal device power status then I checked power manegement register 

I got a result 
when memory io device is not power up this message shows 

It took a few hours, finally I got new one today.

출처 : http://dokyunblog.blogspot.com/2011/04/unhandled-fault-external-abort-on-non.html 

1. layout/main.xml의 progress bar의 android:progressDrawable 속성을 설정한다.

 

<ProgressBar

android:progressDrawable="@drawable/new_drawable"

...

</ProgressBar>

 

2. res/drawable/new_drawable.xml 파일을 생성한다.

각 스타일을 설정한다.

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+android:id/background" android:drawable="@drawable/backgroundProgress" />
    <item android:id="@+android:id/secondaryProgress" android:drawable="@drawable/secondaryProgress" />
    <item android:id="@+android:id/progress" android:drawable="@drawable/progress" />
</layer-list>

3. res/drawable 디렉토리 밑에 다음의 파일을 생성한다.

 

backgroundProgress.png

secondaryProgress.png

progress.png

 

png파일을 생성하지 않고 drawable로 작성하려면

2b. res/drawable/new_drawable.xml 파일을 다음과 같이 설정한다.

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="0dip" />
            <gradient
                    android:startColor="#ff9d9e9d"
                    android:centerColor="#ff5a5d5a"
                    android:centerY="0.75"
                    android:endColor="#ff747674"
                    android:angle="270"
            />
        </shape>
    </item>
    
    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape>
                <corners android:radius="0dip" />
                <gradient
                        android:startColor="#80ffd300"
                        android:centerColor="#80ffb600"
                        android:centerY="0.75"
                        android:endColor="#a0ffcb00"
                        android:angle="270"
                />
            </shape>
        </clip>
    </item>
    
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <corners android:radius="0dip" />
                <gradient
                        android:startColor="#ff0000"
                        android:centerColor="#00ff00"
                        android:centerY="0.75"
                        android:endColor="#0000ff"
                        android:angle="0"
                />
                <stroke android:width="3dp" color="#000000"/> <- Progress 테두리 그려주는 곳.
                <padding android:left="10dp" android:top="10dp"         
      android:right="10dp" android:bottom="10dp" /> 
            </shape>     
        </clip>       
    </item>    
</layer-list>



LOCK이 걸려있는 상황에서 그 위로 어떤 화면인가를 보여주고 싶을때 사용할 수 있는 FLAG가 있습니다.


바로 FLAG_SHOW_WHEN_LOCK 


Android developer Reference에는 아래와 같이 설명되어있습니다.
Window flag : 
special flag to let windows be shown when the screen is locked. 
This will let application windows take precedence over key guard or any other lock screens. 
Can be used with FLAG_KEEP_SCREEN_ON to turn screen on and display windows directly before showing the key guard window. 
Can be used with FLAG_DISMISS_KEYGUARD to automatically fully dismisss non-secure keyguards. 
This flag only applies to the top-most full-screen window.
Constant Value: 524288 (0x00080000) 

중요한 내용은...
FLAG_KEEP_SCREEN_ON 플래그와 함께 사용해서 락 위에 직접적으로 창을 보여주고 싶을때 사용한다는 것.
FLAG_DISMISS_KEYGUARD 플래그와 함께 사용해 키가드의 보호를 자동으로 없앨수있다는 것.
그리고 이 flag는 full-screen의 창만을 지원한다는 것. 

코드는 간단합니다.
아래의 한줄을 onCreate에 추가해주면 됩니다.
 

getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
 

만약, 타이틀 바(Title bar)도 없애고 싶다면 아래의 코드를 추가해줍니다.
이때는 xml Layout을 setContent 하기 전에 코드를 추가해야 합니다. 아래와 같이 해주면 됩니다.
 

super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);

FLAG_KEEP_SCREEN_ON 또는 FLAG_DISMISS_KEYGUARD 역시 FLAG_SHOW_WHEN_LOCKED와 같이 추가해서 사용하면 됩니다.

출처 :  
http://croute.me/350
출처 : http://blog.daum.net/baramjin/16010959

Android Native Server 개념에 크게 Surface Flinger와 Audio Flinger란 것이 있다.

사실 Flinger란 단어가 참 낯설게 느껴지는데 사전적인 의미로는 투수(무언가 던지거나 차는 사람)란 의미가 가장 의미가 와 닿는 것 같은데 Application에서의 데이터를 한데 모아서 출력쪽으로 던져주는 역활을 한다는 의미로 사용한 것이 아닐까 싶다. (언제나 그렇듯이 추측이다)

 

Surface Flinger는 단연히 여러가지 Surface 데이터를 한데 모아서 Frame Buffer를 업데이트하는 것이고 Audio Flinger는 여러가지 오디오 샘플을 한데 모아서, 즉 Audio Mixing을 해서 ALSA 쪽으로 보내는 역활을 한다고 볼 수 있다.

 

Surface Flinger에 대한 가장 잘 표현된 그림은 다음과 같다.

 

Surface Flinger가 하는 기본적인 내용을 번역해 보면 다음과 같다.

 

  • Provides system-wide surface "composer", handling all surface rendering to frame buffer device

composer라고 하는 surface들을 합치는 기능을 제공한다는 의미인 것 같다. (비슷한 말들이 계속 나온다)

 

  • Can combine 2D and 3D surfaces and surfaces from multiple applications

2D, 3D surface를 결합하거나, 여러 응용들의 surface를 결합할 수 있다. 이 말이 결국 Suface Flinger의 역활을 한마디로 표현한 것이 아닌가 싶다.

 

  • Surfaces passed as buffers via Binder IPC calls

Binder IPC calls에 의하여 버퍼를 패스한다. (사실 아직 Binder란 개념이 낯설어서 이 말은 이해가 전혀 안된다.)

 

  • Can use OpenGL ES and 2D hardware accelerator for its compositions

Surface 들을 결합하기 위하여 OpenGL ES와 2D H/W 가속을 이용할 수 있다.

 

  • Double-buffering using page-flip

Page Flip을 위해서 더블 버퍼링을 사용한다.

 

결론적으로 OpenGL ES와 연동이 되면서 Surface들을 합쳐서 Frame Buffer로 전달할 수 있는 구조이며 page flip을 위해 double buffering을 사용한다는 것으로 요약할 수 있을 것이다.

 

OpenGL ES와 연동하기 위하여 Surface Flinger의 초기화 과정을 보면 EGL을 이용하여 Display와 Surface 등을 생성하는 과정을 확인할 수 있다.

 

관련 소스를 보면 다음과 같다.

\frameworks\base\libs\surfaceflinger\SurfaceFlinger.cpp

\frameworks\base\libs\surfaceflinger\displayhardware\DisplayHardware.cpp

\frameworks\base\libs\ui\EGLDisplaySurface.cpp

\frameworks\base\opengl\libagl\Egl.cpp

 

SurfaceFlinger.cpp를 보면 SurfaceFlinger 클래스는 여러 클래스로부터 다중 상속된 클래스이다. 특히 Thread 클래스로부터 상속된 클래스인테 이는 실행되면 하나의 threadLoop()가 동작함을 의미한다. 이 threadLoop가 동작하기 전에 readyToRun()으로 초기화 과정이 이루어 진다.

 

SurfaceFlinger의 readyToRun()을 보면 초기화 과정 중 main display를 초기화 하는 과정과 OpenGL ES를 초기화하는 과정이 있음을 알 수 있다. main display를 초기화 하는 과정을 보면 다음과 같다.

 

status_t SurfaceFlinger::readyToRun()

        ......

 

        // initialize the main display
        GraphicPlane& plane(graphicPlane(dpy));
        DisplayHardware* const hw = new DisplayHardware(this, dpy);
        plane.setDisplayHardware(hw);

 

 

즉 DisplayHardware를 생성하는 과정이 필요하다. 생성되는 DisplayHaraware는 GraphicPlane에 설정된다.

DisplayHardware 클래스를 보면 init 함수에서 EGL 관련된 내용들을 초기화 하고 있다.

DisplayHardware::DisplayHardware(
        const sp<SurfaceFlinger>& flinger,
        uint32_t dpy)
    : DisplayHardwareBase(flinger, dpy)
{
    init(dpy);
}

 

 

init() 함수에서 하는 내용을 순서대로 정리하면 다음과 같다.

 

1) Display 정보 얻어서 EGL 초기화

- EGL extenstion 정보 얻는 과정도 포함하며 이를 이용하여 mFlag에 조건 저장

2) Surface 생성

3) OpenGL ES의 Context 생성

4) OpenGL ES의 Extension 정보 얻음

- 이를 위해서 Display와 Surface, Context를 bind 하는 과정 있음, 모든 정보를 얻으면 다시 unbind

- Extension 정보를 저장하여 mFlag에 조건 저장

5) 생성된 내용을 저장

6) Hardware Module 초기화

- copybit

- overlay

 

init()에서 하는 과정은 대부분은 EGL 초기화 과정과 디바이스 드라이버(특히 fb, scaler 등)를 여는 것이라고 볼 수 있으며 이에 대한 내용은

http://www.khronos.org/registry/egl/specs/eglspec.1.3.pdf 을 참고하는 것이 좋다.

 

1) Display 정보 얻어서 EGL 초기화

 

void DisplayHardware::init(uint32_t dpy)

{

    ......

 

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, NULL, NULL);
    eglGetConfigs(display, NULL, 0, &numConfigs);
    eglChooseConfig(display, attribs, &config, 1, &n); 

}

 

2) Surface 생성

Surface 생성은 크게 EGLDisplaySurface 클래스를 생성하는 과정과 이로부터 실제 EGL의 windows surface를 생성하는 과정으로 나뉘어 진다.

EGL은 세종류의 surface type(windows, pbuffers, pixmaps)를 지원하는데 windows는 onscreen rendering에 사용되고, pbuffers는 offscreen rendering에 사용되며 pixmaps는 nativa API로 접근하는 버퍼들에 대한 offscreen rendering에 사용된다. 말이 좀 어려운데 결국 onscreen rendering은 우리가 보통 얘기하는 frame buffer와 직접적인 관련이 있다는 것을 알 수 있다.

결국 여기에서 frame buffer로의 기본 출력 (fb device와의 연결)을 위한 기본 동작이 수행된다.

 

void DisplayHardware::init(uint32_t dpy)

{

    ......

 


    /*
     * Create our main surface
     */
 

    mDisplaySurface = new EGLDisplaySurface();

    surface = eglCreateWindowSurface(display, config, mDisplaySurface.get(), NULL);
    //checkEGLErrors("eglCreateDisplaySurfaceANDROID");

 

    if (eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &dummy) == EGL_TRUE) {
        if (dummy == EGL_BUFFER_PRESERVED) {
            mFlags |= BUFFER_PRESERVED;
        }
    }
    
    GLint value = EGL_UNKNOWN;
    eglQuerySurface(display, surface, EGL_HORIZONTAL_RESOLUTION, &value);
    if (value == EGL_UNKNOWN) {
        mDpiX = 160.0f;
    } else {
        mDpiX = 25.4f * float(value)/EGL_DISPLAY_SCALING;
    }
    value = EGL_UNKNOWN;
    eglQuerySurface(display, surface, EGL_VERTICAL_RESOLUTION, &value);
    if (value == EGL_UNKNOWN) {
        mDpiY = 160.0f;
    } else {
        mDpiY = 25.4f * float(value)/EGL_DISPLAY_SCALING;
    }
    mRefreshRate = 60.f;

}

 

EGLDisplaySurface  클래스를 생성하는 것을 보면 mapFrameBuffer()를 호출하는데  

EGLDisplaySurface::EGLDisplaySurface()
    : EGLNativeSurface<EGLDisplaySurface>()
{
    egl_native_window_t::version = sizeof(egl_native_window_t);
    egl_native_window_t::ident = 0;
    egl_native_window_t::incRef = &EGLDisplaySurface::hook_incRef;
    egl_native_window_t::decRef = &EGLDisplaySurface::hook_decRef;
    egl_native_window_t::swapBuffers = &EGLDisplaySurface::hook_swapBuffers;
    egl_native_window_t::connect = 0;
    egl_native_window_t::disconnect = 0;

    mFb[0].data = 0;
    mFb[1].data = 0;
    mBlitEngine = 0;
    egl_native_window_t::fd = mapFrameBuffer();

 

    ......

 

status_t EGLDisplaySurface::mapFrameBuffer()
{
    char const * const device_template[] = {
            "/dev/graphics/fb%u",
            "/dev/fb%u",

            0 };
    int fd = -1;
    int i=0;
    char name[64];
    while ((fd==-1) && device_template[i]) {
        snprintf(name, 64, device_template[i], 0);
        fd = open(name, O_RDWR, 0);
        i++;
    }
     if (fd < 0)
        return -errno;

 

    struct fb_fix_screeninfo finfo;
    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
        return -errno;

 

    struct fb_var_screeninfo info;
    if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
        return -errno;

 

   ......

 

    /*
     * Open and map the display.
     */

 

    void* buffer  = (uint16_t*) mmap(
            0, finfo.smem_len,
            PROT_READ | PROT_WRITE,
            MAP_SHARED,
            fd, 0);

 

    if (buffer == MAP_FAILED)
        return -errno;

 

    // at least for now, always clear the fb
    memset(buffer, 0, finfo.smem_len);

 

    uint8_t* offscreen[2];
    offscreen[0] = (uint8_t*)buffer;
    if (flags & PAGE_FLIP) {
        offscreen[1] = (uint8_t*)buffer + finfo.line_length*info.yres;
    } else {
        offscreen[1] = (uint8_t*)malloc(finfo.smem_len);
        if (offscreen[1] == 0) {
            munmap(buffer, finfo.smem_len);
            return NO_MEMORY;
        }
    }

 

    mFlags = flags;
    mInfo = info;
    mFinfo = finfo;
    mSize = finfo.smem_len;
    mIndex = 0;
    for (int i=0 ; i<2 ; i++) {
        mFb[i].version = sizeof(GGLSurface);
        mFb[i].width   = info.xres;
        mFb[i].height  = info.yres;
        mFb[i].stride  = finfo.line_length / (info.bits_per_pixel >> 3);
        mFb[i].data    = (GGLubyte*)(offscreen[i]);
        mFb[i].format  = GGL_PIXEL_FORMAT_RGB_565;
    }
    return fd; 


}

 

간단히 정리하면 여기에서는

1) fb device를 찾아서 열고

2) FSCREEN_INFO와 VSCREEN_INFO를 얻은 후,

3) mmap()으로 fb device에 직접 억세스할 수 있는 버퍼 포인터를 얻고 (mmap은 리눅스의 char형 Device Driver를 File I/O 형태로 접근하지 않고 pointer 형태로 접근하도록 해준다. 이와 관련된 내용은 추후 다시 정리)

4) 이를 mFb에 저장한다.

 

즉 EGLDisplaySurface 클래스를 만듬으로써 SurfaceFlinger가 실제 Fb Devie Driver로 접근하기 위한 준비를 하게 되는 것이다. eglCreateWindowSurface를 호출하여 window surface를 만드는 것은 EGL을 사용하기 위하여 반드시 필요한 과정이다. 또한 이 과정이 EGLDisplaySurface 클래스로 생성된 내용을 실제 연결하는 과정이라고 생각해도 될 것 같다.

 

 

3) OpenGL ES의 Context 생성은 원래 EGL의 초기화 과정 중의 하나이다. 부담없이 보면 다음과 같다. context를 생성한 후, width와 height 정보를 얻어서 저장한다.

 

void DisplayHardware::init(uint32_t dpy)

{

    ......

 

    /*
     * Create our OpenGL ES context
     */
    
    context = eglCreateContext(display, config, NULL, NULL);
    //checkEGLErrors("eglCreateContext");
    
    eglQuerySurface(display, surface, EGL_WIDTH, &mWidth);
    eglQuerySurface(display, surface, EGL_HEIGHT, &mHeight); 

}

 

4)의 과정은 정보를 얻기 위하여 display, surface, context를 bind, unbind 하는 과정을 볼 수 있다는 것 외에 특별히 유의 사항은 없을 것 같다. 5)의 과정도 생성된 내용을 저장해 둔다는 것에만 주의하면 된다.

 

6) Hardware Module 초기화 과정에서는 2개의 중요한 모듈 copybit과 overlay를 초기화 한다. 6의 과정은 여기에서 하드웨어 모듈이 초기화 된다는 것이 중요하다. 이러한 하드웨어 모듈은 다시 말하면 GPU가 제공해주는 기능이다. 안드로이드의 경우 다양한 CPU Platform을 지원할 수 있고, 이는 같은 ARM 코어를 사용하는 경우라도 GPU 인터페이스는 다르다는 것을 의미한다. 즉 copybit 과 overlay 엔진에 대한 코드는 Chip vendor에서 만들어 두고 제공해 주어야만 바로 여기에서 그 장치를 열고 추후에 Surface Flinger를 사용할 때, CPU Platform의 고유한 성능을 낼 수 있는 것이다.

 

void DisplayHardware::init(uint32_t dpy)

{

    ......

 

    hw_module_t const* module;

    mBlitEngine = NULL;
    if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) {
        copybit_open(module, &mBlitEngine);

    }

    mOverlayEngine = NULL;
    if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {
        overlay_control_open(module, &mOverlayEngine);

    } 

}

 

copybit에 대해서는 추후에 더 정리해야 겠지만 bitblt와 stretchbitblt를 지원하는 모듈이다. 여기에서 무슨 기능인지 알 수 있는 사람도 있겠지만 다시 설명하면 이미지 드로잉에서 많이 사용하는 복사와 Scaleing을 지원하는 장치라고 생각하면 될 것 같다. 즉 fb device 외에도 GPU의 scaler와 copy 기능을 위한 장치도 여기에서 열리게 된다.

 

초기화 과정에서 얻은 EGL의 정보로부터 mFlag에 특정 조건을 저장하는데 이는 나중에 Surface Flinger의 더블 버퍼 스왑 등의 처리 조건으로 사용된다. 이는 추후 다시 정리한다.

 

대략적인 초기화 과정에 대한 설명은 이것으로 끝이다. 그런데 왜 Surface Flinger의 초기화 과정 대부분이 EGL 초기화 과정일까?

모 이에 대한 답은 추론할 수 밖에 없다. 안드로이는 처음부터 2D Drawing은 Skia를 이용하면서 OpenGL ES를 지원하도록 설계되어 있다. OpenGL ES를 지원하려면 EGL를 이용하여 Native Window와 연결할 수 있는 구조를 만들어 주어야 한다. 따라서 일반적인 Embedded System의 단순한 GDI 함수와 Screen Buffer (Frame Buffer)의 개념이 아닌 EGL를 통한 display, surface, context를 이용한 개념으로 부터 출발할 수 밖에 없다. 만약 OpenGL ES를 지원하지 않도록 설계되었다면 Surface Flinger라고 하지 않고 Display Flinger나 Screen Flinger라고 했을 수도 있을 것 같다.

 

이런 개념 때문에 OpenGL 등을 잘 모르는 Embedded Programer 입장에서는 단순히 GDI 개념을 이해하기에도 머리 복잡한 내용이 너무 많아진다. 당연히 fb 디바이스 드라이버만 오픈하고 이렇게 오픈된 디바이스로부터 mmap으로 메모리 포인터를 얻어와서 GDI 함수를 구현하는 것에 익숙해져 있는데 (사실 이것도 리눅스 하는 사람 아니면 익숙하지 않다) 여기에 한가지 개념이 더 포함된 것이다.

 

하여간 추측으로 분석하고 적당히 마무리하지만 안드로이드 시스템에서 표준 방식으로 fb 디바이스 드라이버를 여는 곳은 여기이고 이의 구조를 이해해야만 middleware의 접근이 쉬워진다.

 

 

기타 참고로 부팅시 로그를 받아보면 다음과 같다.

 

I/SurfaceFlinger(  739): SurfaceFlinger is starting
I/SurfaceFlinger(  739): showcpu enabled
I/SurfaceFlinger(  739): showfps enabled
I/SurfaceFlinger(  739): SurfaceFlinger's main thread ready to run. Initializing graphics H/W...
I/SurfaceFlinger(  739): EGL informations:
I/SurfaceFlinger(  739): # of configs : 6
I/SurfaceFlinger(  739): vendor    : Android
I/SurfaceFlinger(  739): version   : 1.31 Android META-EGL
I/SurfaceFlinger(  739): extensions:
I/SurfaceFlinger(  739): Client API: OpenGL ES
I/EGLDisplaySurface(  739): using (fd=21)
I/EGLDisplaySurface(  739): id           = xxx_fb
I/EGLDisplaySurface(  739): xres         = 800 px
I/EGLDisplaySurface(  739): yres         = 480 px
I/EGLDisplaySurface(  739): xres_virtual = 800 px
I/EGLDisplaySurface(  739): yres_virtual = 960 px
I/EGLDisplaySurface(  739): bpp          = 16
I/EGLDisplaySurface(  739): r            = 11:5
I/EGLDisplaySurface(  739): g            =  5:6
I/EGLDisplaySurface(  739): b            =  0:5
I/EGLDisplaySurface(  739): width        = 800 mm (25.400000 dpi)
I/EGLDisplaySurface(  739): height       = 480 mm (25.400000 dpi)
I/EGLDisplaySurface(  739): refresh rate = 60.00 Hz
W/SurfaceFlinger(  739): ro.sf.lcd_density not defined, using 160 dpi by default.
I/SurfaceFlinger(  739): OpenGL informations:
I/SurfaceFlinger(  739): vendor    : Android
I/SurfaceFlinger(  739): renderer  : Android PixelFlinger 1.0
I/SurfaceFlinger(  739): version   : OpenGL ES-CM 1.0
I/SurfaceFlinger(  739): extensions: GL_OES_byte_coordinates GL_OES_fixed_point GL_OES_single_precision GL_OES_read_format G
L_OES_compressed_paletted_texture GL_OES_draw_texture GL_OES_matrix_get GL_OES_query_matrix GL_ARB_texture_compression GL_AR
B_texture_non_power_of_two GL_ANDROID_direct_texture GL_ANDROID_user_clip_plane GL_ANDROID_vertex_buffer_object GL_ANDROID_g
enerate_mipmap

 


안드로이드는 기본적으로 사용자가 촬영하는 사진이나 음악, 비디오가 모두 SD/MMC 메모리 카드에 들어가게 된다. 그래서 SD/MMC 카드를 마운트해야 정상적인 멀티미디어 안드로이드 환경을 느낄 수 있다. 안드로이드 1.5 Cupcake 이전 버전에서는 mountd 라는 장치 마운트 데몬이 있었는데, 이것이 버전업이 되면서 vold라는 장치 마운트 데몬으로 바뀌게 되었다.

 VOLD::uevent 처리>

부팅 후 추가되는 장치는 event에 의해 처리되지만 부팅 전에 연결되어 있는 장치는 coldboot()함수에서 처리한다.
 coldboot("/sys/block") 함수를 호출하여 모든 장치에 대해 uevent를 이용해 add event를 발생시킨다. 
uevent에 대한 메시지는 NetlinkManager가 담당하기 때문에 coldboot()함수를 호출하기 전에 NetlinkManager를 create하고 start하는 과정이 필요하다. 
물론 실질적인 처리는 NetlinkManager가 아니라 NetlinkManager의 start함수를 호출할 때 생성되는 NetlinkHandler에서 처리한다. 
부팅 후 추가되는 장치도 uevent를 발생하고 NetlinkHandler가 처리하기 때문에 이후 동작은 동일하다.

0. coldboot함수에서 모든 장치에 대해 uevent의 add event 발생
1. NetlinkHandler의 onEvent함수 호출
2. VolumeManager의 handleBlockEvent함수 호출
3. process_config 과정에서 등록었던 모든 volume의 DirectVolume::handleBlockEvent함수 호출
4. sysfs_path가 일치하는 DirectVolume에 대해 이벤트 처리
5. action에 따라 다음과 같이 처리
case 1: add
 1. createDeviceNode 호출
 2. devtype에 따라 handleDiskAdded or handlePartitionAdded 호출
case 2: remove
 1. devtype에 따라 handleDiskRemoved or handlePartitionRemoved 호출
case 3: change
 1. devtype에 따라 handleDiskChanged or handlePartitionChanged 호출

<VOLD::설정하기>
초기화 과정에서 vold가 실행되면 실질적으로 가장 먼저 하는 일은 process_config()함수이다. 
그 전에 하는 일은 create와 start 함수이므로 크게 신경쓰지 않아도 된다. 
proces_config()는 '/system/etc/vold.fstab' 파일을 읽고 VolumeManager에 volume을 등록한다. 
그러므로 vold.fstab파일의 내용을 알아둬야 한다. 
vold 소소를 잘 정리해두면 vold.fstab을 수정하는 것 만으로도 새로운 장치를 적용할 수 있다

1. vold.fstab 파일 open한 후 값을 읽어 다음 순으로 저장
2. type : type에 따라 동작을 달리할 수 있으나 기본적으로 'dev_mount'일 경우만 동작
3. label : volume을 구분하기 위한 고유값. 실제로 대부분이 mount_point를 이용해서 구분.
4. mount_point : mount할 directory로 여러 부분에서 사용
5. part : device의 partition. auto일 경우 순서대로 mount를 시도하여 가장 먼저 성공한 partition을 사용.
6. sysfs_path : device의 path. 실제로 가장 중요. 4개까지 설정 가능

<VOLD::시작>

안드로이드가 froyo로 버전업이 되면서 vold도 많이 달라졌다. 여기서는 froyo 소스로 분석을 할 것이다. 
그러니 froyo로 버전업되면서 vold의 위치가 '/system/core/vold'에서 '/system/vold'로 수정되었다는 사실을 알아두자. 
정확히 말하면 위치가 바꼈다기 보다는 froyo에서는 VOLD2를 사용하는데 VOLD2의 위치가 '/system/vold'이다.

1. init.rc에서 vold 실행
2. Create VolumeManager
3. Create NetlinkManager
4. Create CommandListener
5. VolumeManager::start()
6. process_config()
7. NetlinkManager::start()
8. coldboot("/sys/block")
9. VolumeManager::notifyUmsConnected()
10. coldboot("/sys/class/switch");
11. CommandListener::startListener()

 

MMC Card를 2개의 partition으로 나눈 상태에서 아래와 같이 vold.fstab을 작성할 수 있다.

dev_mount   sdcard   quto  /devices/platform/s3c-sdhci.0/mmc_host/mmc0/mmc0:0001/block/mmcblk0

 

Android OS가 올라간 이후로 mmc card가 자동으로 인식 안되는 문제가 있다. 이때 수동으로는 아래와 같이 할 수 있다.

# mount -t vfat /dev/block/mmcblk0p1 /mnt/sdcard

# vdc volume list

# vdc volume mount /mnt/sdcard

자동으로 인식하게 하려면 init.rc에 아래와 같이 내용을 추가해준다.

mount vfat /dev/block/mmcblk0p1 /mnt/sdcard

이렇게 해서 mp3 play가 가능해진다. 그런데, sound는 stub로 잡히면서 소리가 나질 않는다. sound는 따로 잡아줘야한다.

출처 : http://blog.naver.com/PostView.nhn?blogId=essenlee&logNo=70103708811

 

fullScreenIntent 에 대해서 정리 할것.

안드로이드에서 USB 또는 SD 카드 등의 Mount 를 체크하고 동작하는 서버 서비스이다.

관련된 내용에 대한 설명을 인터넷으로 찾은 곳은 다음과 같다.

 

http://letsgoustc.spaces.live.com/?_c11_BlogPart_pagedir=Last&_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DGoogle%2520Android

 

 대략적으로 번역하면 다음과 같다.

 

실제 mount 관련 작업은 mountd에서 이루어진다. 시스템 서버 어플리케이션인 MountService는 JAVA 레이어이며 "/dev/socket/mountd를 통해서 mountd와 통신한다.

 

mountd는 "/system/etc/mountd.conf"로부터 해석된 mount point 리스트를 생성하고 이 설정 파일로부터 정의된 장치를 mount 시도한다.

이는 자동적으로 USB mass storage의 uevent와 "/dev/block"를 위한 inotify event를 감시하는  thread를 연다. 즉 이 thread는 SD card의 mount와 USB의 plug in/out를 체크한다. mountd는 MountService로부터 명령(command)을 받고, 이벤트(event)를 보내기 위하여 socket을 생성하고 동작한다.

 

MountService dhk mountd 사이의 명령과 이벤트는 텍스트로 이루어져 있기 때문에 이해하기 쉽다.

USB를 위한 uevent는 두개의 문자열 "POWER_SUPPLY_TYPE=USB"와 “POWER_SUPPLY_ONLINE=”+[“1” | “0”]을 포함하면 감시된다. (무슨 소리인지)

 

만약 MountService가 mountd로부터 어떤 이벤트를 받았다면 다른 클라이언트에서 알려주기 위하여 다음의 intents를 브로드 캐스팅한다.

 

ACTION_UMS_CONNECTED,

ACTION_UMS_DISCONNECTED,

ACTION_MEDIA_REMOVED,

ACTION_MEDIA_UNMOUNTED,

ACTION_MEDIA_MOUNTED,

ACTION_MEDIA_SHARED,

ACTION_MEDIA_BAD_REMOVAL,

ACTION_MEDIA_UNMOUNTABLE,

ACTION_MEDIA_EJECT.

 

  

날림 번역이지만 대강의 동작은 설명이 될 것이다.

위의 내용은 안드로이드 cupcake 버젼이 초기 발표되었을 때의 내용인 것 같다. 최근에는 mountd 대신에 vold로 바뀌었다.

mountd와 vold에 대한 것은 다음을 참조하면 된다.

 

/system/core/mountd

/system/core/vold

 

mountd가 동작하기 위해서는 실제 "/system/etc/mountd.conf"를 읽어들여야 하는데 안드로이드를 동작시켜 보면 이 파일이 없는 것 같다. 그리고 mountd의 make 파일을 보면 주석으로 mountd 대신에 vold를 사용한다고 언급되어 있다.

 

 

안드로이드의 기본적인 서버 서비스는 다음 폴더에 소스가 있다.

 

/frameworks/base/services/java/com/android/server

 

위의 설명과 관련된 소스는 다음 파일이다.

 

/frameworks/base/services/java/com/android/server/MountService.java

/frameworks/base/services/java/com/android/server/MountListener.java

 

위의 설명처럼 MountListener과 vold와 텍스트로 통신하기 때문에 다음과 같은 명령어와 이벤트에 대한 정의가 있다. 이벤트와 명령어도 VOLD로 시작한다. 역시 같은 정의가 /system/core/vold 의 소스 파일과 헤더 파일 등에 정의되어 있다.

 

final class MountListener implements Runnable {

 

    ......

 

    // vold commands
    private static final String VOLD_CMD_ENABLE_UMS = "enable_ums";
    private static final String VOLD_CMD_DISABLE_UMS = "disable_ums";
    private static final String VOLD_CMD_SEND_UMS_STATUS = "send_ums_status";
    private static final String VOLD_CMD_MOUNT_VOLUME = "mount_volume:";
    private static final String VOLD_CMD_EJECT_MEDIA = "eject_media:";
    private static final String VOLD_CMD_FORMAT_MEDIA = "format_media:";

    // vold events
    private static final String VOLD_EVT_UMS_ENABLED = "ums_enabled";
    private static final String VOLD_EVT_UMS_DISABLED = "ums_disabled";
    private static final String VOLD_EVT_UMS_CONNECTED = "ums_connected";
    private static final String VOLD_EVT_UMS_DISCONNECTED = "ums_disconnected";

    private static final String VOLD_EVT_NOMEDIA = "volume_nomedia:";
    private static final String VOLD_EVT_UNMOUNTED = "volume_unmounted:";
    private static final String VOLD_EVT_MOUNTED = "volume_mounted:";
    private static final String VOLD_EVT_MOUNTED_RO = "volume_mounted_ro:";
    private static final String VOLD_EVT_UMS = "volume_ums";
    private static final String VOLD_EVT_BAD_REMOVAL = "volume_badremoval:";
    private static final String VOLD_EVT_DAMAGED = "volume_damaged:";
    private static final String VOLD_EVT_CHECKING = "volume_checking:";
    private static final String VOLD_EVT_NOFS = "volume_nofs:";
    private static final String VOLD_EVT_EJECTING = "volume_ejecting:";

 

mountd와 소켓으로 통신하고 MountService에 상태 정보를 알려주는 것은 하나의 thread를 이용한다.

 

final class MountListener implements Runnable {

 

    ......

  

    public void run() {
        // ugly hack for the simulator.
        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
            SystemProperties.set("EXTERNAL_STORAGE_STATE", Environment.MEDIA_MOUNTED);
            // usbd does not run in the simulator, so send a fake device mounted event to trigger the Media Scanner

            mService.notifyMediaMounted(Environment.getExternalStorageDirectory().getPath(), false);
            
            // no usbd in the simulator, so no point in hanging around.
            return;
        }
    
        try {  
            while (true) {
                listenToSocket();
            }
        } catch (Throwable t) {
            // catch all Throwables so we don't bring down the system process
            Log.e(TAG, "Fatal error " + t + " in MountListener thread!");
        }
    }

 

 listenToSocket() 은 통신을 위한 새로운 소켓을 생성하고 inputStream을 읽어서 이벤트를 처리한다. inputStream에서 에러가 발생하는 경우에만 소켓을 닫는데 먼저 synchronized()를 호출하고 outputStream이 있는지 없는지를 체크한 후, outputstrem을 닫고, 소켓을 닫는다.

 

(소켓 관련해서는 다음 폴더 참조 : /frameworks/base/core/java/android/net

 

final class MountListener implements Runnable {

 

    ......

 

    private void listenToSocket() {
       LocalSocket socket = null;

        try {
            socket = new LocalSocket();
            LocalSocketAddress address = new LocalSocketAddress(VOLD_SOCKET, 
                    LocalSocketAddress.Namespace.RESERVED);

            socket.connect(address);

            InputStream inputStream = socket.getInputStream();
            mOutputStream = socket.getOutputStream();

            byte[] buffer = new byte[100];

            writeCommand(VOLD_CMD_SEND_UMS_STATUS);
            
            while (true) {
                int count = inputStream.read(buffer);
                if (count < 0) break;

                int start = 0;
                for (int i = 0; i < count; i++) {
                    if (buffer[i] == 0) {
                        String event = new String(buffer, start, i - start);
                        handleEvent(event);
                        start = i + 1;
                    }                   
                }
            }                
        } catch (IOException ex) {
            // This exception is normal when running in desktop simulator 
            // where there is no mount daemon to talk to

            // log("IOException in listenToSocket");
        }
        
        synchronized (this) {
            if (mOutputStream != null) {
                try {
                    mOutputStream.close();
                } catch (IOException e) {
                    Log.w(TAG, "IOException closing output stream");
                }
                
                mOutputStream = null;
            }
        }
        
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException ex) {
            Log.w(TAG, "IOException closing socket");
        }
       
        /*
         * Sleep before trying again.
         * This should not happen except while debugging.
         * Without this sleep, the emulator will spin and
         * create tons of throwaway LocalSockets, making
         * system_server GC constantly.
         */
        Log.e(TAG, "Failed to connect to vold", new IllegalStateException());
        SystemClock.sleep(2000);
    }

 

 listenToSocket에서 이벤트를 처리하는 과정을 확인하기 위해서 handleEvent()를 다시 보면 다음과 같다. 대부분의 동작은 이벤트의 종류를 확인하고 이를 다시 MountService로 전달(Notify)하는 역활을 한다. 전달이란 표현을 썼지만 notifyUmsXXX()나 notifyMediaXXX() 함수는 모두 MountService 클래스에 정의되어 있다. 즉 MountLister에서 MountService 콜백 함수를 호출하는 것이다.

 

final class MountListener implements Runnable {

 

    ......

 

    private void handleEvent(String event) {
        if (Config.LOGD) Log.d(TAG, "handleEvent " + event);
    
        int colonIndex = event.indexOf(':');
        String path = (colonIndex > 0 ? event.substring(colonIndex + 1) : null);
        
        if (event.equals(VOLD_EVT_UMS_ENABLED)) {
            mUmsEnabled = true;
        } else if (event.equals(VOLD_EVT_UMS_DISABLED)) {
            mUmsEnabled = false;
        } else if (event.equals(VOLD_EVT_UMS_CONNECTED)) {
            mUmsConnected = true;
            mService.notifyUmsConnected();
        } else if (event.equals(VOLD_EVT_UMS_DISCONNECTED)) {
            mUmsConnected = false;        
            mService.notifyUmsDisconnected();
        } else if (event.startsWith(VOLD_EVT_NOMEDIA)) {
            mService.notifyMediaRemoved(path);
        } else if (event.startsWith(VOLD_EVT_UNMOUNTED)) {
            mService.notifyMediaUnmounted(path);
        } else if (event.startsWith(VOLD_EVT_CHECKING)) {
            mService.notifyMediaChecking(path);
        } else if (event.startsWith(VOLD_EVT_NOFS)) {
            mService.notifyMediaNoFs(path);
        } else if (event.startsWith(VOLD_EVT_MOUNTED)) {
            mService.notifyMediaMounted(path, false);
        } else if (event.startsWith(VOLD_EVT_MOUNTED_RO)) {
            mService.notifyMediaMounted(path, true);
        } else if (event.startsWith(VOLD_EVT_UMS)) {
            mService.notifyMediaShared(path);
        } else if (event.startsWith(VOLD_EVT_BAD_REMOVAL)) {
            mService.notifyMediaBadRemoval(path);
            // also send media eject intent, to notify apps to close any open
            // files on the media.
            mService.notifyMediaEject(path);
        } else if (event.startsWith(VOLD_EVT_DAMAGED)) {
            mService.notifyMediaUnmountable(path);
        } else if (event.startsWith(VOLD_EVT_EJECTING)) {
            mService.notifyMediaEject(path);
        }   
 
    }

}

 

MountService와 MountListener는 어떻게 연결될까? MountService에서 MountListener을 생성하게 된다. 즉 MountListerer의 mService는 생성자에서 전달받은 값으로 설정된다. 관련된 코드는 MountService 코드에서 확인할 수 있다.

 

class MountService extends IMountService.Stub {

 

    ......

 

    public MountService(Context context) {
        mContext = context;

        // Register a BOOT_COMPLETED handler so that we can start
        // MountListener. We defer the startup so that we don't
        // start processing events before we ought-to
        mContext.registerReceiver(mBroadcastReceiver,
                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);

        mListener =  new MountListener(this);       
        mShowSafeUnmountNotificationWhenUnmounted = false;

        mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");

        mAutoStartUms = SystemProperties.get("persist.service.mount.umsauto", "0").equals("1");
    }

    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
                Thread thread = new Thread(mListener, MountListener.class.getName());
                thread.start();
            }
        }
    };

 

}

 

UMS의 연결 과정을 잠깐 보면 handleEvent()에서 VOLD_EVT_UMS_CONNECTED를 확인하면 mService.notifyUmsConnected()를 호출한다. 이를 보면 Storage 상태를 체크해서 조건이 맞는 경우에만 setMassStorageEnabled()를 호출하게 되어 있다. 안드로이드의 경우 SD 카드가 삽입되어 있지 않으면 UMS 연결이 안되는데 (안드로이드는 Default Storage로 SD 카드를 우선 지원한다. 내장 NAND 메모리는 바로 사용할 수 없다) 바로 이 조건 비교문 때문에 그렇것 같다.

 

class MountService extends IMountService.Stub {

 

    ......

  

    void notifyUmsConnected() {
        String storageState = Environment.getExternalStorageState();
        if (!storageState.equals(Environment.MEDIA_REMOVED) &&
            !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
            !storageState.equals(Environment.MEDIA_CHECKING)) {

            if (mAutoStartUms) {
                try {
                    setMassStorageEnabled(true);
                } catch (RemoteException e) {
                }
            } else {
                updateUsbMassStorageNotification(false, true);
            }
        }

        Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
        mContext.sendBroadcast(intent); 
    }

 

    public void setMassStorageEnabled(boolean enable) throws RemoteException {
        mListener.setMassStorageEnabled(enable);
    }

}

 

 setMassStorageEnabled()는 결국 MountListener의 setMassStorageEnabled()를 호출하는데 연결된 소켓을 이용하여 mountd에 명령어를 전송한다. (실제 동작은 mountd가 한다는 의미다.)

 

final class MountListener implements Runnable {

 

    ...... 

 

    void setMassStorageEnabled(boolean enable) {
        writeCommand(enable ? VOLD_CMD_ENABLE_UMS : VOLD_CMD_DISABLE_UMS);
    }
}

 

다시 notifyUmsConnected()를 보면 intent를 생성해서 브로드 캐스팅하는 과정이 있다. 원래 안드로이드 응용에서 intent는 새로운 Activity로 이동시 인자를 전달하기 위해 많이 사용되나, 여기서는 서비스에서 이벤트를 얻은 후, 여러 클라이언트들이 알아서 동작하도록 정보를 브로드 캐스팅하고 있다.

class MountService extends IMountService.Stub {

 

    ......

  

    void notifyUmsConnected() {
        String storageState = Environment.getExternalStorageState();
        if (!storageState.equals(Environment.MEDIA_REMOVED) &&
            !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
            !storageState.equals(Environment.MEDIA_CHECKING)) {

            if (mAutoStartUms) {
                try {
                    setMassStorageEnabled(true);
                } catch (RemoteException e) {
                }
            } else {
                updateUsbMassStorageNotification(false, true);
            }
        }

        Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
        mContext.sendBroadcast(intent);

    }

 

notifyUmsConnected()에서 UMS 관련해서 주의깊게 봐야 하는 내용은 mAutoStartUms 설정관련된 내용과 UpdateUsbMassStorageNotification() 이다.

 

안드로이드에서 UMS 관련 기본 동작 시나리오는 USB 케이블이 연결되면 연결 상태를 알린다. 이 경우 PC에서도 이동식 디스크는 보이지만 실제 이동식 디스크로 연결되지는 않는다.(연결 시도시 미디어를 넣어달라고 한다) 이동식 디스크로 동작 시키려면 USB 연결 아이콘 쪽을 드래그 하여 "USB Connected"란 항목이 활성화 되게 하고 이를 다시 터치하여 다이얼로그 박스를 띄운 후, UMS 연결을 선택해야 한다.

mAutoStartUms는 이와 같은 동작을 처리하는 변수이다. 따라서 MountService가 초기화 될때 관련값을 읽어오게 된다.

이를 다르게 설정하기 위한 함수로 setAutoStartUms()도 제공한다. 이를 보면 안드로이드의 환경 설정 정보를 얻어오거나 설정하는 방법을 확인할 수 있다. 또한 커스터 마이징을 위해서 위의 시나리오를 변경할 방법도 알 수 있다.

 

class MountService extends IMountService.Stub {

 

    ......

 

    public MountService(Context context) {
        mContext = context;

        // Register a BOOT_COMPLETED handler so that we can start
        // MountListener. We defer the startup so that we don't
        // start processing events before we ought-to
        mContext.registerReceiver(mBroadcastReceiver,
                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);

        mListener =  new MountListener(this);       
        mShowSafeUnmountNotificationWhenUnmounted = false;

        mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");

        mAutoStartUms = SystemProperties.get("persist.service.mount.umsauto", "0").equals("1");
    }

 

    public void setAutoStartUms(boolean enabled) {
        if (mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.WRITE_SETTINGS) 
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires WRITE_SETTINGS permission");
        }
        mAutoStartUms = enabled;
        SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0"));
    } 

}

 

원래 안드로이드 시나리오에서는 UpdateUsbMassStorageNotification()를 이용해서 UMS 연결 여부를 사용자가 선택할 수 있게 한다.

이를 보면 다음과 같다.  

    void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) {

        try {

            if (getMassStorageConnected() && !suppressIfConnected) {
                Intent intent = new Intent();
                intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);

                PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
                setUsbStorageNotification(
                        com.android.internal.R.string.usb_storage_notification_title,
                        com.android.internal.R.string.usb_storage_notification_message,
                        com.android.internal.R.drawable.stat_sys_data_usb,
                        sound, true, pi);
            } else {
                setUsbStorageNotification(0, 0, 0, false, false, null);
            }
        } catch (RemoteException e) {
            // Nothing to do
        }
    }

 

UsbStorageActivity Activity로 Intent를 전달하는 구조로 되어 있다. UsbStorageActiviy는 다음 위치에서 확인할 수 있다.

 

/frameworks/base/core/java/com/android/internal/apps/UsbStorageActivity.java

 

이 파일의 주된 동작은 다이얼로그를 사용해서 사용자의 입력을 받도록 되어 있다. 이에 대한 내용은 추후 다시 정리한다.
출처 : http://blog.daum.net/_blog/BlogTypeView.do?blogid=0CNH3&articleno=16010974#ajax_history_home

 

Settings>Wireless Settings>WiFi Setting>WiFi Enable 에 대해서 분석을 해보았습니다.
Settings App 에서보면, WiFi Enable 버튼이 보일 것입니다.
이 버튼에 대한 소스를 보면 어떤식으로 Wifi가 Enable이 되는지 Disable이 되는지에 대해서 궁굼할 것입니다.
Source를 보면  다음과 같습니다.
WiFiSettings.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

    if (getIntent().getBooleanExtra("only_access_points", false)) {
        addPreferencesFromResource(R.xml.wifi_access_points);
    } else {
        addPreferencesFromResource(R.xml.wifi_settings);
        mWifiEnabler = new WifiEnabler(this,
                (CheckBoxPreference) findPreference("enable_wifi"));
        mNotifyOpenNetworks =
                (CheckBoxPreference) findPreference("notify_open_networks");
        mNotifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(),
                Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
    }

    mAccessPoints = (ProgressCategory) findPreference("access_points");
    mAccessPoints.setOrderingAsAdded(false);
    mAddNetwork = findPreference("add_network");

    registerForContextMenu(getListView());
 

위의 소스를 보면 
WifiEnablerprivate final BroadcastReceiver mReceiver = new BroadcastReceiver() { 란 생성자를 확인할 수 있습니다.
이 생성자를 통해서 Wifi Enable CheckBoxPreference가 Check가 되는지 안되는지를 처리를 해줍니다.
그러면 WifiEnabler란 생성자를 소스로 보면 CheckBoxPreference를 전달 받아서 이를 이용해 Wifi의 상태값에 의해서 Enable인지 Disable인지를 그려주는 것을 확인할 수 있습니다.

그런데 여기서 의문점이 하나 생기게됩니다.
어떻게 CheckBoxPreference에 Create해 주었을때, 바로 Wifi의 Enable을 설정해주는지에 대해서 의문을 가지게 될것입니다.

소스상으로 보게되면 다음과 같이 receiver로 WiFi상태 값을 받아서 Check해주는 법 외에는 처리를 하지 않을 것입니다.
 
 @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
            handleWifiStateChanged(intent.getIntExtra(
                    WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
            handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState)
                    intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            handleStateChanged(((NetworkInfo) intent.getParcelableExtra(
                    WifiManager.EXTRA_NETWORK_INFO)).getDetailedState());
        }
    }
};
  
 private void registerForBroadcasts() {소스상에서 보여지는 것과 같이 Receiver로 받아서 처리해주는 방법외에는 없습니다. 그러면 여기서 또다른 의문이 생길것입니다. 도대체 어디서 Wifi의 상태값을 Broadcast로 보내주는 것일까....
그리고 Wifi Settings에 진입할 때 마다 정확하게 팍!팍! 보내주는 것일까...

그래서 이 부분에 대해서 소스를 분석한 결과 그 답은 WifiService에서 확인할 수 있었습니다.
WifiService.java의 위치는 다음과 같습니다.

frameworks/base/services/java/com/android/server/WifiService.java
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(Intent.ACTION_SCREEN_ON);
    intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
    intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
    intentFilter.addAction(ACTION_DEVICE_IDLE);
    intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
    mContext.registerReceiver(mReceiver, intentFilter);
}


    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            ...
            updateWifiState();
        }

위의 소스를 보면  ACTION_BATTERY_CHANGED 를 받아서 updateWifiState()함수를 호출 하는 것을 확인 할 수 있습니다.

여기서 updateWifiState() 함수를 따라가다보면  doUpdateWifiState 함수까지 따라 갈 수 있습니다. 이 함수를 보면, sendEnableMessage 함수를 호출하게 됩니다.

private void doUpdateWifiState() {
  ...
        if (wifiShouldBeEnabled) {
            if (wifiShouldBeStarted) {
                sWakeLock.acquire();
                sendEnableMessage(true, false, mLastEnableUid);
                sWakeLock.acquire();
                sendStartMessage(strongestLockMode);
          ...
}

sendEnableMessage 함수를 따라가다 보면, setWifiEnabledBlocking 이란 함수를 호출하게 되며, 결국은 setWifiEnabledState 함수를 호출하게 됩니다.

private void setWifiEnabledState(int wifiState, int uid) {
    final int previousWifiState = mWifiStateTracker.getWifiState();

    long ident = Binder.clearCallingIdentity();
    try {
        if (wifiState == WIFI_STATE_ENABLED) {
            mBatteryStats.noteWifiOn();
        } else if (wifiState == WIFI_STATE_DISABLED) {
            mBatteryStats.noteWifiOff();
        }
    } catch (RemoteException e) {
    } finally {
        Binder.restoreCallingIdentity(ident);
    }

    // Update state
    mWifiStateTracker.setWifiState(wifiState);

    // Broadcast
    final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
    intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
    mContext.sendStickyBroadcast(intent);
}

결국은 setWifiEnabledState란 함수를 통해서  WiFi의 상태 변화 값을 전달하게 됩니다.

위의 내용을 정리하자면,  ACTION_BATTERY_CHANGED란 BroadCast를 통해서 Wifi의 상태값을 Broadcast로 전달하게 되며, Settings App에서는 Wifi의 상태값을 전달 받아서 CheckBoxPreference의 Check값을 설정하게 되는 것입니다.

ACTION_BATTERY_CHANGED란 Intent는 주기적으로 호출이 되기는 하지만, Activity가 생성되거나, Rotation될때, 그리고 기타적인 요인에 의해서 호출되게 됩니다.
Battery Changed가 호출되는 시점에 대해서는  다음에 설명하도록 하겠습니다.

 
 
Android/frameworks/base/data/sounds/ 폴더에 위치하고 있습니다.

이곳에서 ogg Data들을 관리하며, 파일 변경시 이곳에서 변경하시면됩니다. 

+ Recent posts