안드로이드는 기본적으로 사용자가 촬영하는 사진이나 음악, 비디오가 모두 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

 

+ Recent posts