반응형

네, AIDL을 이용해서 권한 문제를 해결할 수 있습니다. 소켓 통신의 권한 문제를 AIDL로 우회하는 방법을 알려드리겠습니다.
문제 상황 분석
Android 앱이 소켓을 열지 못하는 일반적인 원인:
SELinux 정책: 앱이 특정 소켓 파일에 접근 불가
파일 권한: /dev/socket/ 디렉토리 접근 권한 부족
Network Security Config: Android 9+ 에서 일반 소켓 제한
Targetless 소켓: Unix domain socket 생성/접근 권한 부족
해결 방안: AIDL 브리지 패턴
기존 C 프로그램은 그대로 두고, System Service를 중간 브리지로 사용합니다.
Android App → AIDL → System Service (권한 있음) → Socket → C Program
구현 예제
1. 아키텍처
[Android App]
     ↓ (AIDL - 권한 문제 없음)
[System Service]
     ↓ (Socket - System 권한으로 접근 가능)
[기존 C Program]
2. AIDL 인터페이스 정의
frameworks/base/core/java/android/os/ISocketBridge.aidl

package android.os;

/**
* 소켓 브리지 서비스
* @hide
*/
interface ISocketBridge {
    /**
     * C 프로그램으로 데이터 전송
     */
    boolean sendData(in byte[] data);
    
    /**
     * C 프로그램에서 데이터 수신
     */
    byte[] receiveData();
    
    /**
     * 요청-응답 패턴
     */
    byte[] sendAndReceive(in byte[] request);
    
    /**
     * 연결 상태 확인
     */
    boolean isConnected();
}


3. System Service 구현 (Java + JNI)
frameworks/base/services/core/java/com/android/server/SocketBridgeService.java

package com.android.server;

import android.content.Context;
import android.os.ISocketBridge;
import android.util.Slog;

public class SocketBridgeService extends ISocketBridge.Stub {
    private static final String TAG = "SocketBridgeService";
    private static final String SOCKET_PATH = "/dev/socket/my_c_program";
    
    private final Context mContext;
    
    // Native 메서드
    private native boolean native_init(String socketPath);
    private native boolean native_sendData(byte[] data);
    private native byte[] native_receiveData();
    private native byte[] native_sendAndReceive(byte[] request);
    private native boolean native_isConnected();
    private native void native_cleanup();
    
    static {
        System.loadLibrary("socketbridge_jni");
    }
    
    public SocketBridgeService(Context context) {
        mContext = context;
        
        // 소켓 연결 초기화
        if (!native_init(SOCKET_PATH)) {
            Slog.e(TAG, "Failed to initialize socket connection");
        } else {
            Slog.i(TAG, "Socket bridge initialized successfully");
        }
    }
    
    @Override
    public boolean sendData(byte[] data) {
        if (data == null || data.length == 0) {
            Slog.w(TAG, "Invalid data");
            return false;
        }
        
        // 권한 체크 (필요시)
        mContext.enforceCallingOrSelfPermission(
            android.Manifest.permission.INTERNET,
            "Need INTERNET permission"
        );
        
        return native_sendData(data);
    }
    
    @Override
    public byte[] receiveData() {
        return native_receiveData();
    }
    
    @Override
    public byte[] sendAndReceive(byte[] request) {
        if (request == null) {
            return null;
        }
        return native_sendAndReceive(request);
    }
    
    @Override
    public boolean isConnected() {
        return native_isConnected();
    }
    
    @Override
    protected void finalize() throws Throwable {
        native_cleanup();
        super.finalize();
    }
}


4. JNI 구현 (소켓 통신)

frameworks/base/services/core/jni/com_android_server_SocketBridgeService.cpp
#define LOG_TAG "SocketBridgeServiceJNI"

#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

namespace android {

static int gSocketFd = -1;
static const int MAX_BUFFER_SIZE = 4096;

// 소켓 초기화
static jboolean native_init(JNIEnv* env, jobject clazz, jstring socketPath) {
    const char* path = env->GetStringUTFChars(socketPath, nullptr);
    
    ALOGI("Initializing socket connection to: %s", path);
    
    // 기존 연결 정리
    if (gSocketFd >= 0) {
        close(gSocketFd);
        gSocketFd = -1;
    }
    
    // Unix domain socket 생성
    gSocketFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (gSocketFd < 0) {
        ALOGE("Failed to create socket: %s", strerror(errno));
        env->ReleaseStringUTFChars(socketPath, path);
        return JNI_FALSE;
    }
    
    // 소켓 주소 설정
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
    
    // C 프로그램에 연결
    if (connect(gSocketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        ALOGE("Failed to connect to socket %s: %s", path, strerror(errno));
        close(gSocketFd);
        gSocketFd = -1;
        env->ReleaseStringUTFChars(socketPath, path);
        return JNI_FALSE;
    }
    
    ALOGI("Socket connected successfully");
    env->ReleaseStringUTFChars(socketPath, path);
    return JNI_TRUE;
}

// 데이터 전송
static jboolean native_sendData(JNIEnv* env, jobject clazz, jbyteArray data) {
    if (gSocketFd < 0) {
        ALOGE("Socket not connected");
        return JNI_FALSE;
    }
    
    jsize len = env->GetArrayLength(data);
    jbyte* bytes = env->GetByteArrayElements(data, nullptr);
    
    // 데이터 크기 먼저 전송 (프로토콜)
    uint32_t dataLen = htonl(len);
    if (send(gSocketFd, &dataLen, sizeof(dataLen), 0) != sizeof(dataLen)) {
        ALOGE("Failed to send data length: %s", strerror(errno));
        env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
        return JNI_FALSE;
    }
    
    // 실제 데이터 전송
    ssize_t sent = send(gSocketFd, bytes, len, 0);
    env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
    
    if (sent != len) {
        ALOGE("Failed to send data: %s", strerror(errno));
        return JNI_FALSE;
    }
    
    ALOGD("Sent %d bytes", len);
    return JNI_TRUE;
}

// 데이터 수신
static jbyteArray native_receiveData(JNIEnv* env, jobject clazz) {
    if (gSocketFd < 0) {
        ALOGE("Socket not connected");
        return nullptr;
    }
    
    // 데이터 크기 수신
    uint32_t dataLen;
    if (recv(gSocketFd, &dataLen, sizeof(dataLen), 0) != sizeof(dataLen)) {
        ALOGE("Failed to receive data length: %s", strerror(errno));
        return nullptr;
    }
    dataLen = ntohl(dataLen);
    
    if (dataLen > MAX_BUFFER_SIZE) {
        ALOGE("Data too large: %u bytes", dataLen);
        return nullptr;
    }
    
    // 데이터 수신
    uint8_t* buffer = new uint8_t[dataLen];
    ssize_t received = recv(gSocketFd, buffer, dataLen, 0);
    
    if (received != (ssize_t)dataLen) {
        ALOGE("Failed to receive data: %s", strerror(errno));
        delete[] buffer;
        return nullptr;
    }
    
    // Java byte array로 변환
    jbyteArray result = env->NewByteArray(dataLen);
    env->SetByteArrayRegion(result, 0, dataLen, (jbyte*)buffer);
    
    delete[] buffer;
    
    ALOGD("Received %u bytes", dataLen);
    return result;
}

// 요청-응답
static jbyteArray native_sendAndReceive(JNIEnv* env, jobject clazz,
                                        jbyteArray request) {
    // 데이터 전송
    if (!native_sendData(env, clazz, request)) {
        return nullptr;
    }
    
    // 응답 수신
    return native_receiveData(env, clazz);
}

// 연결 상태 확인
static jboolean native_isConnected(JNIEnv* env, jobject clazz) {
    if (gSocketFd < 0) {
        return JNI_FALSE;
    }
    
    // 소켓이 유효한지 확인
    int error = 0;
    socklen_t len = sizeof(error);
    if (getsockopt(gSocketFd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
        return JNI_FALSE;
    }
    
    return error == 0 ? JNI_TRUE : JNI_FALSE;
}

// 정리
static void native_cleanup(JNIEnv* env, jobject clazz) {
    if (gSocketFd >= 0) {
        close(gSocketFd);
        gSocketFd = -1;
        ALOGI("Socket connection closed");
    }
}

// JNI 메서드 매핑
static const JNINativeMethod gMethods[] = {
    {"native_init", "(Ljava/lang/String;)Z", (void*)native_init},
    {"native_sendData", "([B)Z", (void*)native_sendData},
    {"native_receiveData", "()[B", (void*)native_receiveData},
    {"native_sendAndReceive", "([B)[B", (void*)native_sendAndReceive},
    {"native_isConnected", "()Z", (void*)native_isConnected},
    {"native_cleanup", "()V", (void*)native_cleanup},
};

int register_android_server_SocketBridgeService(JNIEnv* env) {
    return AndroidRuntime::registerNativeMethods(env,
            "com/android/server/SocketBridgeService",
            gMethods, NELEM(gMethods));
}

} // namespace android


5. SystemServer에 등록
frameworks/base/services/java/com/android/server/SystemServer.java

import com.android.server.SocketBridgeService;

private void startOtherServices() {
    // ... 기존 코드 ...
    
    try {
        Slog.i(TAG, "Socket Bridge Service");
        ServiceManager.addService("socket_bridge",
            new SocketBridgeService(context));
    } catch (Throwable e) {
        reportWtf("starting Socket Bridge Service", e);
    }
    
    // ... 기존 코드 ...
}
6. Android App에서 사용
MyApplication.java
package com.example.myapp;

import android.app.Application;
import android.os.IBinder;
import android.os.ISocketBridge;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.util.Log;

public class MyApplication extends Application {
    private static final String TAG = "MyApp";
    private ISocketBridge mSocketBridge;
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        // Socket Bridge 서비스에 연결
        IBinder binder = ServiceManager.getService("socket_bridge");
        mSocketBridge = ISocketBridge.Stub.asInterface(binder);
        
        if (mSocketBridge == null) {
            Log.e(TAG, "Failed to get socket bridge service");
        }
    }
    
    // 데이터 전송 헬퍼
    public boolean sendToC(byte[] data) {
        try {
            if (mSocketBridge != null) {
                return mSocketBridge.sendData(data);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to send data", e);
        }
        return false;
    }
    
    // 데이터 수신 헬퍼
    public byte[] receiveFromC() {
        try {
            if (mSocketBridge != null) {
                return mSocketBridge.receiveData();
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to receive data", e);
        }
        return null;
    }
    
    // 요청-응답 헬퍼
    public byte[] sendAndReceive(byte[] request) {
        try {
            if (mSocketBridge != null) {
                return mSocketBridge.sendAndReceive(request);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to send and receive", e);
        }
        return null;
    }
}


MainActivity.java

package com.example.myapp;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.util.Log;
import java.nio.charset.StandardCharsets;

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private MyApplication mApp;
    private TextView mResponseText;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mApp = (MyApplication) getApplication();
        mResponseText = findViewById(R.id.responseText);
        
        Button btnSend = findViewById(R.id.btnSend);
        btnSend.setOnClickListener(v -> sendMessage());
    }
    
    private void sendMessage() {
        new Thread(() -> {
            try {
                // C 프로그램에 메시지 전송
                String message = "Hello from Android!";
                byte[] request = message.getBytes(StandardCharsets.UTF_8);
                
                Log.d(TAG, "Sending: " + message);
                
                // 요청 전송 및 응답 수신
                byte[] response = mApp.sendAndReceive(request);
                
                if (response != null) {
                    String responseStr = new String(response,
                        StandardCharsets.UTF_8);
                    Log.d(TAG, "Received: " + responseStr);
                    
                    runOnUiThread(() -> {
                        mResponseText.setText("Response: " + responseStr);
                    });
                } else {
                    Log.e(TAG, "No response received");
                    runOnUiThread(() -> {
                        mResponseText.setText("Error: No response");
                    });
                }
                
            } catch (Exception e) {
                Log.e(TAG, "Communication error", e);
                runOnUiThread(() -> {
                    mResponseText.setText("Error: " + e.getMessage());
                });
            }
        }).start();
    }
}


7. SELinux 정책

system/sepolicy/private/system_server.te
# System server가 C 프로그램 소켓에 접근
allow system_server my_c_program_socket:sock_file write;
allow system_server my_c_program:unix_stream_socket connectto;
system/sepolicy/public/app.te
# 앱이 socket_bridge 서비스 사용
allow { appdomain -isolated_app } socket_bridge_service:service_manager find;
binder_call({ appdomain -isolated_app }, system_server)
system/sepolicy/private/service_contexts
socket_bridge    u:object_r:socket_bridge_service:s0


8. C 프로그램 (기존 코드 - 변경 최소화)
기존 C 프로그램의 소켓 코드 예시

// 기존 코드는 그대로 유지
// 다만 SELinux 레이블만 조정

int create_server_socket(const char* socket_path) {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
    
    unlink(socket_path);  // 기존 소켓 제거
    
    bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    
    // 권한 설정 (system_server가 접근 가능하도록)
    chmod(socket_path, 0666);
    
    listen(sockfd, 5);
    
    return sockfd;
}


핵심 포인트
권한 문제 해결: System Service는 높은 권한을 가지므로 소켓 접근 가능
앱 코드 수정 최소화: 소켓 코드 대신 AIDL 호출로 간단히 변경
C 프로그램 수정 불필요: 기존 소켓 서버 코드 그대로 사용
SELinux 관리 용이: System Service 정책만 설정하면 됨
추가 권장 사항
재연결 로직 추가:
public void reconnect() {
    try {
        if (mSocketBridge != null && !mSocketBridge.isConnected()) {
            // 재연결 로직
        }
    } catch (RemoteException e) {
        Log.e(TAG, "Reconnect failed", e);
    }
}
이 방식을 사용하면 앱의 권한 문제 없이 System Service의 권한으로 C 프로그램과 통신할 수 있습니다.




네, 같은 Android.bp 파일에 Java App과 C++ 서비스 데몬을 함께 정의할 수 있습니다. 실제로 이런 패턴은 Android에서 자주 사용됩니다.

Android.bp에서 여러 타입 모듈 정의
1. 통합 Android.bp 예제
packages/apps/MyApp/Android.bp

// ============================================
// AIDL 인터페이스 정의
// ============================================
aidl_interface {
    name: "com.mycompany.myapp.aidl",
    local_include_dir: "aidl",
    srcs: [
        "aidl/com/mycompany/myapp/*.aidl",
    ],
    //stability: "vintf",
    backend: {
        java: {
            sdk_version: "system_current",
            enabled: true,
        },
        cpp: {
            enabled: false,
        },
        ndk: {
            enabled: true,  // C++ 서비스용
        },
    },
    // unstable로 설정하면 버전관리 안 함
    unstable: true,
//    versions_with_info: [
//        {
//            version: "1",
//            imports: [],
//        },
//    ],
}

// ============================================
// C++ 네이티브 데몬 서비스
// ============================================
cc_binary {
    name: "myapp_service_daemon",
    
    // 설치 위치
    vendor: true,  // vendor 파티션
    // 또는
    // system_ext: true,  // system_ext 파티션
    
    relative_install_path: "hw",  // /vendor/bin/hw/ 또는 /system_ext/bin/hw/
    
    // 소스 파일
    srcs: [
        "native/src/main.cpp",
        "native/src/MyAppService.cpp",
        "native/src/DataProcessor.cpp",
    ],
    
    // 헤더 경로
    local_include_dirs: [
        "native/include",
    ],
    
    // 컴파일 플래그
    cflags: [
        "-Wall",
        "-Werror",
        "-Wextra",
    ],
    
    // C++ 표준
    cpp_std: "c++17",
    
    // 공유 라이브러리 의존성
    shared_libs: [
        "libbase",
        "libbinder_ndk",
        "liblog",
        "libutils",
    ],
    
    // 정적 라이브러리 의존성
    static_libs: [
        "com.mycompany.myapp.aidl-V1-ndk",  // AIDL 생성 라이브러리
    ],
    
    // Init RC 스크립트
    init_rc: ["native/myapp_service_daemon.rc"],
    
    // VINTF manifest (HAL인 경우)
    vintf_fragments: ["native/myapp_service_daemon.xml"],
}

// ============================================
// Java 라이브러리 (앱에서 사용할 클라이언트)
// ============================================
java_library {
    name: "myapp-service-client",
    
    srcs: [
        "src/com/mycompany/myapp/client/**/*.java",
    ],
    
    static_libs: [
        "com.mycompany.myapp.aidl-V1-java",  // AIDL Java 바인딩
    ],
    
    sdk_version: "system_current",
}

// ============================================
// Android App (APK)
// ============================================
android_app {
    name: "MyApp",
    
    // 앱 타입
    platform_apis: true,
    privileged: true,  // system/priv-app에 설치
    certificate: "platform",  // 플랫폼 서명
    
    // 소스 파일
    srcs: [
        "src/**/*.java",
    ],
    
    // 리소스
    resource_dirs: ["res"],
    
    // Manifest
    manifest: "AndroidManifest.xml",
    
    // 의존성
    static_libs: [
        "androidx.appcompat_appcompat",
        "com.google.android.material_material",
        "myapp-service-client",  // 위에서 정의한 클라이언트 라이브러리
    ],
    
    // JNI 라이브러리 (필요시)
    jni_libs: [
        // "libmyapp_jni",
    ],
    
    optimize: {
        enabled: false,
    },
    
    dex_preopt: {
        enabled: false,
    },
}

// ============================================
// JNI 라이브러리 (필요한 경우)
// ============================================
cc_library_shared {
    name: "libmyapp_jni",
    
    srcs: [
        "jni/MyAppJNI.cpp",
    ],
    
    shared_libs: [
        "liblog",
        "libnativehelper",
    ],
    
    static_libs: [
        "com.mycompany.myapp.aidl-V1-ndk",
    ],
    
    cflags: [
        "-Wall",
        "-Werror",
    ],
    
    sdk_version: "current",
}

// ============================================
// 선택사항: 네이티브 테스트
// ============================================
cc_test {
    name: "myapp_service_test",
    
    srcs: [
        "native/test/MyAppServiceTest.cpp",
    ],
    
    shared_libs: [
        "libbase",
        "libbinder_ndk",
        "liblog",
    ],
    
    static_libs: [
        "com.mycompany.myapp.aidl-V1-ndk",
        "libgmock",
        "libgtest",
    ],
    
    test_suites: ["device-tests"],
}


2. 디렉토리 구조

packages/apps/MyApp/
├── Android.bp                          # 통합 빌드 파일
├── AndroidManifest.xml                 # Java App Manifest
│
├── aidl/                              # AIDL 인터페이스
│   └── com/mycompany/myapp/
│       ├── IMyAppService.aidl
│       └── IMyAppCallback.aidl
│
├── src/                               # Java App 소스
│   └── com/mycompany/myapp/
│       ├── MainActivity.java
│       ├── MyApplication.java
│       └── client/
│           └── ServiceClient.java
│
├── res/                               # Android 리소스
│   ├── layout/
│   ├── values/
│   └── drawable/
│
├── native/                            # C++ 데몬 소스
│   ├── include/
│   │   ├── MyAppService.h
│   │   └── DataProcessor.h
│   │
│   ├── src/
│   │   ├── main.cpp
│   │   ├── MyAppService.cpp
│   │   └── DataProcessor.cpp
│   │
│   ├── myapp_service_daemon.rc        # Init RC
│   └── myapp_service_daemon.xml       # VINTF manifest
│
├── jni/                               # JNI (필요시)
│   └── MyAppJNI.cpp
│
└── test/                              # 테스트
    └── MyAppServiceTest.cpp


3. AIDL 인터페이스

aidl/com/mycompany/myapp/IMyAppService.aidl
package com.mycompany.myapp;

import com.mycompany.myapp.IMyAppCallback;

interface IMyAppService {
    /**
     * 데이터 처리 요청
     */
    String processData(String input);
    
    /**
     * 설정 변경
     */
    boolean setConfig(in byte[] config);
    
    /**
     * 콜백 등록
     */
    void registerCallback(IMyAppCallback callback);
    
    /**
     * 상태 조회
     */
    int getStatus();
}
aidl/com/mycompany/myapp/IMyAppCallback.aidl
package com.mycompany.myapp;

oneway interface IMyAppCallback {
    void onDataChanged(String newData);
    void onStatusChanged(int status);
}


4. C++ 데몬 구현
native/include/MyAppService.h

#pragma once

#include <aidl/com/mycompany/myapp/BnMyAppService.h>
#include <aidl/com/mycompany/myapp/IMyAppCallback.h>
#include <memory>
#include <mutex>
#include <vector>

namespace aidl {
namespace com {
namespace mycompany {
namespace myapp {

class MyAppService : public BnMyAppService {
public:
    MyAppService();
    virtual ~MyAppService();
    
    // IMyAppService 인터페이스 구현
    ndk::ScopedAStatus processData(
        const std::string& input,
        std::string* _aidl_return) override;
    
    ndk::ScopedAStatus setConfig(
        const std::vector<uint8_t>& config,
        bool* _aidl_return) override;
    
    ndk::ScopedAStatus registerCallback(
        const std::shared_ptr<IMyAppCallback>& callback) override;
    
    ndk::ScopedAStatus getStatus(int32_t* _aidl_return) override;

private:
    std::mutex mMutex;
    std::vector<std::shared_ptr<IMyAppCallback>> mCallbacks;
    int32_t mStatus;
    
    void notifyDataChanged(const std::string& data);
    void notifyStatusChanged(int32_t status);
};

} // namespace myapp
} // namespace mycompany
} // namespace com
} // namespace aidl
native/src/MyAppService.cpp
#define LOG_TAG "MyAppService"

#include "MyAppService.h"
#include <android-base/logging.h>

namespace aidl {
namespace com {
namespace mycompany {
namespace myapp {

MyAppService::MyAppService() : mStatus(0) {
    LOG(INFO) << "MyAppService created";
}

MyAppService::~MyAppService() {
    LOG(INFO) << "MyAppService destroyed";
}

ndk::ScopedAStatus MyAppService::processData(
    const std::string& input,
    std::string* _aidl_return) {
    
    LOG(DEBUG) << "processData: " << input;
    
    std::lock_guard<std::mutex> lock(mMutex);
    
    // 실제 데이터 처리 로직
    std::string result = "Processed: " + input;
    
    // 콜백 알림
    notifyDataChanged(result);
    
    *_aidl_return = result;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus MyAppService::setConfig(
    const std::vector<uint8_t>& config,
    bool* _aidl_return) {
    
    LOG(DEBUG) << "setConfig: " << config.size() << " bytes";
    
    std::lock_guard<std::mutex> lock(mMutex);
    
    // 설정 적용 로직
    // ...
    
    *_aidl_return = true;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus MyAppService::registerCallback(
    const std::shared_ptr<IMyAppCallback>& callback) {
    
    if (!callback) {
        return ndk::ScopedAStatus::fromServiceSpecificError(
            -1, "Callback is null");
    }
    
    std::lock_guard<std::mutex> lock(mMutex);
    
    // 중복 체크
    for (const auto& cb : mCallbacks) {
        if (cb->asBinder() == callback->asBinder()) {
            LOG(WARNING) << "Callback already registered";
            return ndk::ScopedAStatus::ok();
        }
    }
    
    mCallbacks.push_back(callback);
    LOG(INFO) << "Callback registered, total: " << mCallbacks.size();
    
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus MyAppService::getStatus(int32_t* _aidl_return) {
    std::lock_guard<std::mutex> lock(mMutex);
    *_aidl_return = mStatus;
    return ndk::ScopedAStatus::ok();
}

void MyAppService::notifyDataChanged(const std::string& data) {
    for (auto it = mCallbacks.begin(); it != mCallbacks.end(); ) {
        auto status = (*it)->onDataChanged(data);
        if (!status.isOk()) {
            LOG(WARNING) << "Callback failed, removing";
            it = mCallbacks.erase(it);
        } else {
            ++it;
        }
    }
}

void MyAppService::notifyStatusChanged(int32_t status) {
    mStatus = status;
    for (auto it = mCallbacks.begin(); it != mCallbacks.end(); ) {
        auto st = (*it)->onStatusChanged(status);
        if (!st.isOk()) {
            it = mCallbacks.erase(it);
        } else {
            ++it;
        }
    }
}

} // namespace myapp
} // namespace mycompany
} // namespace com
} // namespace aidl
native/src/main.cpp
#define LOG_TAG "MyAppServiceDaemon"

#include "MyAppService.h"
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>

using aidl::com::mycompany::myapp::MyAppService;

int main(int argc, char** argv) {
    // 로깅 초기화
    android::base::InitLogging(argv, android::base::LogdLogger());
    
    LOG(INFO) << "Starting MyApp Service Daemon";
    
    // Binder 스레드 풀 설정
    ABinderProcess_setThreadPoolMaxThreadCount(4);
    ABinderProcess_startThreadPool();
    
    // 서비스 생성
    std::shared_ptr<MyAppService> service =
        ndk::SharedRefBase::make<MyAppService>();
    
    // 서비스 등록
    const std::string instance = std::string() +
        MyAppService::descriptor + "/default";
    
    binder_status_t status = AServiceManager_addService(
        service->asBinder().get(), instance.c_str());
    
    if (status != STATUS_OK) {
        LOG(ERROR) << "Failed to register service: " << status;
        return 1;
    }
    
    LOG(INFO) << "Service registered: " << instance;
    
    // 메인 스레드를 Binder 스레드로 전환
    ABinderProcess_joinThreadPool();
    
    return 0;
}


5. Init RC 파일
native/myapp_service_daemon.rc

service myapp_service_daemon /vendor/bin/hw/myapp_service_daemon
    class hal
    user system
    group system
    disabled

on boot
    start myapp_service_daemon

on property:sys.boot_completed=1
    start myapp_service_daemon


6. Java App에서 사용
src/com/mycompany/myapp/client/ServiceClient.java

package com.mycompany.myapp.client;

import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import com.mycompany.myapp.IMyAppService;
import com.mycompany.myapp.IMyAppCallback;

public class ServiceClient {
    private static final String TAG = "ServiceClient";
    private static final String SERVICE_NAME =
        "com.mycompany.myapp.IMyAppService/default";
    
    private IMyAppService mService;
    
    private final IMyAppCallback mCallback = new IMyAppCallback.Stub() {
        @Override
        public void onDataChanged(String newData) {
            Log.d(TAG, "Data changed: " + newData);
            // UI 업데이트 등
        }
        
        @Override
        public void onStatusChanged(int status) {
            Log.d(TAG, "Status changed: " + status);
        }
        
        @Override
        public int getInterfaceVersion() {
            return IMyAppCallback.VERSION;
        }
        
        @Override
        public String getInterfaceHash() {
            return IMyAppCallback.HASH;
        }
    };
    
    public boolean connect() {
        try {
            IBinder binder = ServiceManager.waitForService(SERVICE_NAME);
            if (binder == null) {
                Log.e(TAG, "Service not found");
                return false;
            }
            
            mService = IMyAppService.Stub.asInterface(binder);
            mService.registerCallback(mCallback);
            
            Log.d(TAG, "Connected to service");
            return true;
            
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to connect", e);
            return false;
        }
    }
    
    public String processData(String input) {
        if (mService == null) {
            Log.e(TAG, "Service not connected");
            return null;
        }
        
        try {
            return mService.processData(input);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to process data", e);
            return null;
        }
    }
    
    public boolean setConfig(byte[] config) {
        if (mService == null) return false;
        
        try {
            return mService.setConfig(config);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to set config", e);
            return false;
        }
    }
    
    public int getStatus() {
        if (mService == null) return -1;
        
        try {
            return mService.getStatus();
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get status", e);
            return -1;
        }
    }
}


src/com/mycompany/myapp/MainActivity.java

package com.mycompany.myapp;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.mycompany.myapp.client.ServiceClient;

public class MainActivity extends Activity {
    private ServiceClient mServiceClient;
    private EditText mInputText;
    private TextView mResultText;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mInputText = findViewById(R.id.inputText);
        mResultText = findViewById(R.id.resultText);
        Button btnProcess = findViewById(R.id.btnProcess);
        
        // 서비스 연결
        mServiceClient = new ServiceClient();
        new Thread(() -> mServiceClient.connect()).start();
        
        // 데이터 처리 버튼
        btnProcess.setOnClickListener(v -> {
            String input = mInputText.getText().toString();
            new Thread(() -> {
                String result = mServiceClient.processData(input);
                runOnUiThread(() -> {
                    if (result != null) {
                        mResultText.setText(result);
                    }
                });
            }).start();
        });
    }
}


7. SELinux 정책
sepolicy/myapp_service.te

type myapp_service, domain;
type myapp_service_exec, exec_type, vendor_file_type, file_type;

init_daemon_domain(myapp_service)

binder_use(myapp_service)
binder_call(myapp_service, servicemanager)

add_service(myapp_service, myapp_service_service)



# 필요한 권한 추가

핵심 포인트
하나의 Android.bp: 여러 타입의 모듈을 하나의 파일에 정의 가능
AIDL 공유: Java와 C++ 모두 같은 AIDL 인터페이스 사용
독립적 빌드: 각 모듈은 독립적으로 빌드되지만 의존성 관리됨
IPC 통신: Binder 기반 AIDL로 프로세스 간 통신
모듈화: 클라이언트 라이브러리를 분리하여 재사용 가능
이 구조를 사용하면 Java 앱과 C++ 데몬을 효율적으로 관리하고 IPC를 통해 통신할 수 있습니다.

반응형