MTK 기반 안드로이드 시스템 개발 가이드

1. 디바이스 언락 방법

MTK 칩셋 기반 디바이스를 언락하기 위한 단계별 가이드입니다.

  1. 설정 → 시스템 → 개발자 옵션 → OEM 언락 활성화
  2. adb reboot bootloader 실행
  3. fastboot flashing unlock 실행
  4. 볼륨 업 키를 눌러 확인
  5. fastboot reboot로 재부팅
  6. adb root 권한 획득
  7. adb disable-verity로 verity 비활성화
  8. adb reboot 재부팅
  9. adb root 다시 실행
  10. adb remount 마운트 재설정

2. 이중 컴파일 방식 (S+T)

MTK 플랫폼에서 벤더 이미지(system)와 커널 이미지를 분리하여 컴파일하는 방식입니다.

1단계: 벤더 빌드
source build/envsetup.sh
export OUT_DIR=out
lunch vnd_k62v1_64_bsp-usedebug
make vnd_images krn_images 2>&1 | tee build_vendor.log
2단계: 시스템 빌드
source build/envsetup.sh
export OUT_DIR=out_sys
lunch sys_mssi_64_cn-userdebug
make sys_images 2>&1 | tee build.log
3단계: 이미지 병합
python out_sys/target/product/mssi_64_cn/images/split_build.py \
  --system-dir out_sys/target/product/mssi_64_cn/images \
  --vendor-dir ../alps-mp-s0/out/target/product/k62v1_64_bsp/images \
  --kernel-dir ../alps-mp-s0/out/target/product/k62v1_64_bsp/images \
  --output-dir out_sys/target/product/mssi_64_cn/merged/ \
  2>&1 | tee build_merge.log

3. 언인스톱 가능 APK 목록 추가

ap/vendor/mediatek/proprietary/frameworks/base/data/etc/pms_sysapp_removable_system_list.txt

4. 시스템 프로퍼티 및 디바이스 정보 수정

ap/build/make/tools/buildinfo.sh

+echo "ro.build.id=M392"
+echo "ro.build.display.id=T04_50_2_16_3.99inch_7701S_M392_V1.0_`$DATE +%Y%m%d`"
+echo "ro.product.model=M392"
+echo "ro.product.brand=M392"
+echo "ro.product.name=M392"
+echo "ro.product.device=M392"

블루투스 이름 변경

ap/device/generic/common/bluetooth/bdroid_buildcfg.h

#define BTM_DEF_LOCAL_NAME "M392"

테더링 이름 변경

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiApConfigStore.java

ap/device/mediateksample/k50v1_64_bsp/system.prop

persist.sys.auto.sd.time=30

5. 브로드캐스트 수신 구현

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class BroadcastExample {
    private BroadcastReceiver mReceiver;
    private Context mContext;

    public void sendBroadcastMessage() {
        Intent intent = new Intent();
        intent.setAction("CUSTOM_ACTION_EVENT");
        mContext.sendBroadcast(intent);
    }

    public void registerReceiver() {
        mReceiver = new CustomReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("CUSTOM_ACTION_EVENT");
        mContext.registerReceiver(mReceiver, filter);
    }

    private final class CustomReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if ("CUSTOM_ACTION_EVENT".equals(action)) {
                // 커스텀 액션 처리 로직
            }
        }
    }

    public void unregisterReceiver() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
        }
    }
}

6. 백그라운드 스레드 실행

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 백그라운드 작업 실행
    }
}).start();

7. 토스트 메시지 다국어 처리

frameworks/base/core/java/android/widget/Toast.java 수정:

import java.util.Locale;

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
    android.util.Log.d("ToastDebug", "text=" + text.toString());
    
    Locale locale = context.getResources().getConfiguration().locale;
    android.util.Log.d("ToastDebug", "locale=" + locale.getLanguage());
    
    if (text.toString().contains("처리중")) {
        if (locale.getLanguage().contains("en")) {
            text = "processing";
        } else if (locale.getLanguage().contains("ja")) {
            text = "処理中";
        } else if (locale.getLanguage().contains("de")) {
            text = "verarbeitung";
        } else if (locale.getLanguage().contains("fr")) {
            text = "traitement";
        } else if (locale.getLanguage().contains("it")) {
            text = "elaborazione";
        } else if (locale.getLanguage().contains("es")) {
            text = "procesando";
        }
    }
    return makeText(context, null, text, duration);
}

8. 기능 제거 가이드

8.1 USB 기본 모드를 MTP로 설정

frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

// finishBoot() 또는 handleMessage(Message msg)에서
setEnabledFunctions(UsbManager.FUNCTION_MTP, false);

8.2 블루투스 연락처 공유 비활성화

frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java

public boolean isProfileReady() {
    return false;
}

8.3 잠금 화면에서 빠른解锁 비활성화

packages/apps/Settings/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java

return UNSUPPORTED_ON_DEVICE;

8.4 데이터 사용량 관련 설정 제거

network/NetworkProviderSettings.java

mDataUsagePreference.setVisible(false);

8.5 백업 및 다중 사용자 설정 제거

packages/apps/Settings/src/com/android/settings/SettingsActivity.java

import com.android.settings.backup.UserBackupSettingsActivity;

somethingChanged = setTileEnabled(changedList,
    new ComponentName(packageName, UserBackupSettingsActivity.class.getName()), 
    false, isAdmin) || somethingChanged;

somethingChanged = setTileEnabled(changedList,
    new ComponentName(packageName, Settings.UserSettingsActivity.class.getName()), 
    false, isAdmin) || somethingChanged;

9. 검색 결과 필터링

packages/apps/SettingsIntelligence/src/com/android/settings/intelligence/search/SearchResultsAdapter.java

import android.util.Log;
import android.text.TextUtils;

public void postSearchResults(List<? extends SearchResult> newSearchResults) {
    final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
        new SearchResultDiffCallback(mSearchResults, newSearchResults));
    mSearchResults.clear();
    notifyDataSetChanged();
    
    int removeIndex = -1;
    if (newSearchResults.size() > 0) {
        for (int i = 0; i < newSearchResults.size(); i++) {
            SearchResult result = newSearchResults.get(i);
            String title = "";
            String summary = "";
            String dataKey = "";
            
            if (!TextUtils.isEmpty(result.title)) {
                title = result.title.toString();
            }
            if (!TextUtils.isEmpty(result.summary)) {
                summary = result.summary.toString();
            }
            if (!TextUtils.isEmpty(result.dataKey)) {
                dataKey = result.dataKey.toString();
            }
            
            // 필터링 조건
            if ("Cast".equalsIgnoreCase(title) || "cast".equalsIgnoreCase(summary) 
                || "cast".equalsIgnoreCase(dataKey) || "Cast options".equalsIgnoreCase(title)
                || "Wi-Fi calling".equalsIgnoreCase(title)
                || title.contains("Wireless") || summary.contains("Receive wireless")
                || summary.contains("Wireless") || dataKey.contains("Wireless")
                || title.contains("Face") || summary.contains("Face")) {
                removeIndex = i;
                newSearchResults.remove(removeIndex);
                notifyDataSetChanged();
                i--;
            }
        }
    }
}

10. 클리어 버튼 아이콘과 텍스트 중앙 정렬

// packages/apps/Launcher3/quickstep/src/com/sprd/ext/clearall/ClearAllButton.java
@Override
protected void onDraw(Canvas canvas) {
    Drawable[] compoundDrawables = getCompoundDrawables();
    if (compoundDrawables != null) {
        Drawable leftDrawable = compoundDrawables[0];
        if (leftDrawable != null) {
            float textSize = getPaint().measureText(getText().toString());
            int drawablePadding = getCompoundDrawablePadding();
            int drawableSize = leftDrawable.getIntrinsicWidth();
            float totalWidth = textSize + drawableSize + drawablePadding;
            canvas.translate((getWidth() - totalWidth) / 2, 0);
        }
    }
    super.onDraw(canvas);
}

11. 구글 검색 위젯을 이동 가능한 앱 위젯으로 변경

packages/apps/Launcher3/src/com/android/launcher3/config/FeatureFlags.java

- public static final boolean QSB_ON_FIRST_SCREEN = true;
+ public static final boolean QSB_ON_FIRST_SCREEN = false;

vendor/partner_gms/apps/GmsSampleIntegration/res_dhs_full/xml/partner_default_layout.xml

+ <appwidget screen="0" x="0" y="0" spanX="5" spanY="1" 
+     packageName="com.google.android.googlequicksearchbox" 
+     className="com.google.android.googlequicksearchbox.SearchWidgetProvider" />

12. 시뮬레이션 키 이벤트 전송

public static void simulateKeyPress(final int keyCode) {
    new Thread(new Runnable() {
        public void run() {
            try {
                Instrumentation instrumentation = new Instrumentation();
                instrumentation.sendKeyDownUpSync(keyCode);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

13. 화면 회전 상태 확인

import android.content.SharedPreferences;
import com.android.launcher3.Utilities;

private boolean isRotationEnabled() {
    SharedPreferences prefs = Utilities.getPrefs(getContext());
    boolean rotationEnabled = prefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
    Log.d("HomeScreen", "Rotation enabled: " + rotationEnabled);
    return rotationEnabled;
}

14. 엣지 패널 스와이프-back 커스터마이징

// frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java

// 설정 값
navigation_edge_panel_width = 110
navigation_edge_panel_height = 200

private final Paint edgePaint = new Paint();
Path swipePath = new Path();

@Override
protected void onDraw(Canvas canvas) {
    float arrowPosition = mCurrentTranslation - mArrowThickness / 2.0f;
    swipePath.reset();
    
    int direction = mIsLeftPanel ? 1 : -1;
    int centerY = getHeight() / 2;
    int startY = 0;
    int endY = getHeight();
    int baseX = mIsLeftPanel ? 0 : getWidth();
    int curveX = (int)(baseX + direction * (mIsLeftPanel ? arrowPosition : arrowPosition - getStaticArrowWidth()) / 2);
    
    swipePath.moveTo(baseX, startY);
    swipePath.cubicTo(baseX, startY + 75, curveX, startY + 100, curveX, centerY);
    swipePath.cubicTo(curveX, endY - 100, baseX, endY - 75, baseX, endY);
    swipePath.close();
    
    canvas.drawPath(swipePath, edgePaint);
    
    mArrowPath.reset();
    mArrowPath.moveTo(mIsLeftPanel ? (curveX - 40 + dp(3.5f)) : (curveX + 40 + dp(3.5f)), getHeight() / 2 - dp(5.5f));
    mArrowPath.lineTo(mIsLeftPanel ? (curveX - 40 - dp(4f)) : (curveX + 40 - dp(4f)), getHeight() / 2);
    mArrowPath.lineTo(mIsLeftPanel ? (curveX - 40 + dp(3.5f)) : (curveX + 40 + dp(3.5f)), getHeight() / 2 + dp(5.5f));
    canvas.drawPath(mArrowPath, mArrowPaint);
}

15. 네트워크 연결 상태 확인

public boolean isWifiConnected() {
    ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo wifiNetwork = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
    return wifiNetwork != null && wifiNetwork.isConnected();
}

public boolean isMobileDataEnabled() {
    ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo mobileNetwork = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
    return mobileNetwork != null && mobileNetwork.isConnected();
}

태그: Android mtk system-development Bootloader ADB

7월 10일 05:56에 게시됨