1. 디바이스 언락 방법
MTK 칩셋 기반 디바이스를 언락하기 위한 단계별 가이드입니다.
- 설정 → 시스템 → 개발자 옵션 → OEM 언락 활성화
adb reboot bootloader실행fastboot flashing unlock실행- 볼륨 업 키를 눌러 확인
fastboot reboot로 재부팅adb root권한 획득adb disable-verity로 verity 비활성화adb reboot재부팅adb root다시 실행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();
}