Здравствуй Хабр!
В прошлой статье мы обсудили некоторые нюансы касательно интерфейса, а сегодня попробуем разобрать детально каждый случай в отдельности.
На прошлых скриншотах были следующие меню в моем самодельном твикере и вызвало множество приватных вопросов о реализации.
Предпочтительный слот
Выберите SIM карту на которой использовать передачу данных
Уведомление о соединении
Запретить оповещение об интернет подключении
Автоматическая запись звонков
Все звонки будут записаны стандартным диктофоном согласно его настройкам
Запретить энергосбережение
Запретить иконку энергосбережения в слайдере и статус баре
Запретить выключатели
Отключение в слайдере статус бара
Предпочтительный слот
Так как я являюсь ярым поклонником двухсимочных телефонов, данная функция мне нужна для того, чтобы иметь возможность использовать интернет от любого из операторов, где есть покрытие. 3G/GPRS/EDGE покрытие у всех разное, а необходимость быть действительно мобильным — для меня задача первостепенная. По умолчанию интернет работает на первой основной сим карте, но в некоторых местах оператор не имеет 3G и предоставляет слабую пропускную способность, урезая EDGE тайм слоты на канале передачи данных, соответственно передача идет по GPRS. Имя такой твикер я могу легко переключиться на второго оператора и иметь подключение по крайней мере под EDGE.
Модифицировать прошивку для этого не обязательно, а достаточно вызвать диалог и указать что вам необходимо. Сразу отмечу, что данный код применим к телефонам HTC и был написан согласно библиотеке android.net.HtcIfConnectivityManager.
String slot1 = Settings.System.getString(getContentResolver(), "slot_1_user_text") != null ? Settings.System.getString(getContentResolver(), "slot_1_user_text") : "SIM 1";
String slot2 = Settings.System.getString(getContentResolver(), "slot_2_user_text") != null ? Settings.System.getString(getContentResolver(), "slot_2_user_text") : "SIM 2";
CharSequence[] slots = { slot1, slot2 };
new HtcAlertDialog.Builder(this).setTitle(R.string.type_title).setSingleChoiceItems(slots, -1, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
try
{
HtcIfConnectivityManager localHtcIfConnectivityManager = (HtcIfConnectivityManager) main.this.getApplicationContext().getSystemService("connectivity");
Integer type = 1;
switch (which)
{
default:
case 0:
type = 1;
break;
case 1:
type = 5;
}
localHtcIfConnectivityManager.setMobileDataPhoneType(type);
dialog.dismiss();
return;
}
catch (Exception localException1)
{
Log.d("Falseclock", "type change:" + localException1);
}
}
}).show();
Уведомление о соединении
В прошлой статье я писал о ненужном уведомлении, что мой телефон в данный момент использует передачу данных и показывает какой APN используется. Честно говоря, мне это не то что нужно, а раздражало, что и послужило поводом отключения данной функции. Польностью вырезать из прошивки я не стал, так как публикую свои работы для общего пользования, а при создании модифицированных прошивок хорошим тоном считается оставлять конечному пользователю выбирать что ему нравится, а что нет.
Осталось только найти в каком месте данный функционал срабатывает. Надо отдать должное, программисты HTC хорошо оптимизировали код, его приятно читать и легко находить нужное место. У ООП есть конечно и свои минусы, так как порой необходимый фрагмент кода нужно искать по целой цепочке методов. Еще одно преимущество, HTC Sense создан на шаблонах, которые по прохождению кода собираются как конструктор Lego, в оконцовке превращаясь в полноценный графический интерфейс. В стандартной документации исходного кода Android предлагается для каждого вызова (intent или dialog) рисовать отдельный шаблон (layout) и первое время искать приходилось очень долго, так как я искал интерфейс оболочки в самой XML разметке, а не в коде программы.
И так, в 4-ом Аднроиде есть замечательная функция, которая позволят узнать кто родитель уведомления. Достаточно долго нажать на уведомление и появится меню, в котором можно посмотреть приложение, которое является инициатором. В моем случае оказалось, что это приложение Телефон (Phone.apk).
Потрошим приложение
Распаковываем и декомпилируем приложение с помощью APK-Multi-Tool. Для этого предварительно надо скачать, установить и настроить его. Все описано в документации.
1. Кладем Phone.apk в папку place-apk-here-for-modding
2. Открываем любим архиватором и удаляем от туда файл classes.dex. Это ускорит работу и избавит вас от ошибок декомпилятора.
3. Запускаем скрипт Script.bat и выбираем 9-ый пункт Decompile apk. Нам нужно распаковать приложение и покопаться в файлах res/values. После распаковки исходники будут лежать в папке .projectsPhone.apk
Поиски кода
1. Так как у меня интерфейс русский, то мне нужна папка с русскими словами .resvalues-ru.
2. На скриншоте из прошлой статьи видим, что у нас есть слово «Подключено» и оно явно находится в нашей локализации.
3. Ищем по всем файлам наше слово… и не находим :-(
4. У нас есть еще иконка в виде двух стрелок, поищем ее. Идем в папку projectsPhone.apkresdrawable-hdpi и видим ее stat_sys_apn.png.
5. Ищем идентификатор картинки по ее названию.
TOTAL: 2 matches in 2 files (13 other files without matches are not listed)
1 match in S:devAndroidAPK-Multi-ToolprojectsPhone.apkresvaluesdrawables.xml
49 <item type="drawable" name="stat_sys_apn">@drawable/zero_dummy_asset</item>
1 match in S:devAndroidAPK-Multi-ToolprojectsPhone.apkresvaluespublic.xml
60 <public type="drawable" name="stat_sys_apn" id="0x7f02007f" />
6. Мы нашли шестнадцатиричный ID картинки 0x7f02007f, что в десятичном у нас 2130837631 (переводится в виндовом калькуляторе).
7. Теперь у нас есть два пути:
а) взять classes.dex, сконвертировать его в jar и открыть в gd-gui;
b) воспользоваться baksmali.jar и распотрошить Dalvik код (описывалось в первой части статей).
Я предпочитаю первый вариант, так как читать удобней (описывалось в первой статье, я главе «Распаковка и анализ оригинального файла»).
8. Открыв сконвертированный classes.dex в gd-gui, сохраним наш исходный код.
9. Сделаем поиск 2130837631 в наших исходниках:
TOTAL: 3 matches in 2 files (326 other files without matches are not listed)
2 matches in D:Desktopclasses_dex2jar.srccomandroidphoneNotificationMgr.java
1237 HtcWrapNotification localHtcWrapNotification = new HtcWrapNotification(this.mContext, 2130837631, null, System.currentTimeMillis(), paramString, this.mContext.getString(2131624179), localIntent);
1282 HtcWrapNotification localHtcWrapNotification = new HtcWrapNotification(this.mContext, 2130837631, null, System.currentTimeMillis(), paramString, this.mContext.getString(2131624179), localIntent);
1 match in D:Desktopclasses_dex2jar.srccomandroidphoneR.java
834 public static final int stat_sys_apn = 2130837631;
10. там же в gd-gui идем смотреть что это за код.
void showMobileDataConnected(String paramString)
{
if (DBG)
log("showMobileDataConnected()...");
Intent localIntent = new Intent("android.intent.action.MAIN");
if (PhoneApp.MODE_DUAL)
if (PhoneUtils.getMobileDataPhoneType() == 1)
localIntent.setComponent(new ComponentName("com.android.settings", "com.android.settings.ApnSettings"));
while (true)
{
HtcWrapNotification localHtcWrapNotification = new HtcWrapNotification(this.mContext, 2130837631, null, System.currentTimeMillis(), paramString, this.mContext.getString(2131624179), localIntent);
localHtcWrapNotification.flags = (0x2 | localHtcWrapNotification.flags);
this.mNotificationManager.notify(12, localHtcWrapNotification);
return;
localIntent.setComponent(new ComponentName("com.android.settings", "com.android.settings.CdmaApnSettings"));
continue;
localIntent.setComponent(new ComponentName("com.android.settings", "com.android.settings.ApnSettings"));
}
}
void showMobileDataConnected(String paramString, int paramInt)
{
if (DBG)
log("showMobileDataConnected---->>phoneType=" + paramInt + ", APN Name= " + paramString);
String str = "";
int i = -1;
Intent localIntent = new Intent("android.intent.action.MAIN");
if (paramInt == 2)
{
str = "com.android.settings.CdmaApnSettings";
i = 13;
}
while (true)
{
VLog.logd("NotificationMgr", "notificationId = " + i);
if (i != -1)
break;
VLog.logd("NotificationMgr", "notificationId is wrong!");
return;
if (paramInt == 1)
{
str = "com.android.settings.ApnSettings";
i = 14;
localIntent.putExtra("phone_type", paramInt);
if (PhoneApp.MODE_CG)
localIntent.putExtra("isSettings", 1);
}
else if (paramInt == 5)
{
str = "com.android.settings.ApnSettings";
i = 15;
localIntent.putExtra("phone_type", paramInt);
}
}
localIntent.setComponent(new ComponentName("com.android.settings", str));
HtcWrapNotification localHtcWrapNotification = new HtcWrapNotification(this.mContext, 2130837631, null, System.currentTimeMillis(), paramString, this.mContext.getString(2131624179), localIntent);
localHtcWrapNotification.flags = (0x2 | localHtcWrapNotification.flags);
localHtcWrapNotification.contentIntent = PendingIntent.getActivity(this.mContext, paramInt, localIntent, 134217728);
this.mNotificationManager.notify(i, localHtcWrapNotification);
}
11. Так как это просто метод, то значит он от куда-то вызывается. Давайте поищем.
TOTAL: 9 matches in 2 files (326 other files without matches are not listed)
4 matches in D:Desktopclasses_dex2jar.srccomandroidphoneNotificationMgr.java
1227 void showMobileDataConnected(String paramString)
1230 log("showMobileDataConnected()...");
1247 void showMobileDataConnected(String paramString, int paramInt)
1250 log("showMobileDataConnected---->>phoneType=" + paramInt + ", APN Name= " + paramString);
5 matches in D:Desktopclasses_dex2jar.srccomandroidphonePhoneApp.java
914 NotificationMgr.getDefault().showMobileDataConnected(str4, i3);
917 NotificationMgr.getDefault().showMobileDataConnected(str4);
920 NotificationMgr.getDefault().showMobileDataConnected(str3);
5407 NotificationMgr.getDefault().showMobileDataConnected(PhoneApp.APNQueryThread.this.apnCarrier, PhoneApp.APNQueryThread.this.phoneType);
5412 NotificationMgr.getDefault().showMobileDataConnected(PhoneApp.APNQueryThread.this.apnCarrier);
12. Открываем в jd-gui файл comandroidphonePhoneApp.java и понимаем что вызов у нас срабатывает в следующем блоке
if (HtcFeatureList.FEATURE_APN_CONNECTION_NOTIFICATION)
{
if (str4 == null)
{
String str5 = "apn = '" + str3 + "' AND current IS NOT NULL";
Uri localUri = Telephony.Carriers.CONTENT_URI;
if (PhoneApp.MODE_DUAL)
{
if (TextUtils.isEmpty(str3))
{
VLog.logd("PhoneApp", "APN name is null!");
if (i3 == 2)
{
PhoneApp.access$3302(PhoneApp.this, false);
return;
}
if (i3 == 1)
{
PhoneApp.access$3402(PhoneApp.this, false);
return;
}
if (i3 != 5)
continue;
PhoneApp.access$3502(PhoneApp.this, false);
return;
}
VLog.logd("PhoneApp", "phone type = " + i3);
if (i3 != 2)
break label3803;
localUri = HtcWrapTelephony.CdmaCarriers.CONTENT_URI;
}
while (true)
{
PhoneApp.this.log("EVENT_MOBILE_DATA_CONNECTED, start APNQueryThread for APN query.");
new PhoneApp.APNQueryThread(PhoneApp.this, localUri, i3, str5, str3, str4).startQuery();
return;
label3803: if (i3 == 1)
localUri = HtcWrapTelephony.GsmCarriers.CONTENT_URI;
else if (i3 == 5)
localUri = HtcWrapTelephony.SubGsmCarriers.CONTENT_URI;
}
}
if (PhoneApp.MODE_DUAL)
{
NotificationMgr.getDefault().showMobileDataConnected(str4, i3);
return;
}
NotificationMgr.getDefault().showMobileDataConnected(str4);
return;
}
Модификация кода
Мы конечно можем пересетить переменную HtcFeatureList.FEATURE_APN_CONNECTION_NOTIFICATION, но как уже я говорил, это является дурным тоном жестко избавляться от кода, если вы публикуете прошивки и правильней будет сделать возможность выбора для пользователя. Разумеется, если вы делаете для себя и четко уверены, что вам это не нужно, можно вырезать радикально, но я все же не советую.
1. Так как у меня есть свой твикер, который хранит настройки в системной области (об этом в будущей статьей), нам нужно в начале этого блока сделать проверку что-то вроде:
if (HtcFeatureList.FEATURE_APN_CONNECTION_NOTIFICATION)
{
if (Settings.System.getInt(PhoneApp.this.phone.getContext().getContentResolver(), "tweaks_disableConnectionNotification", 0) != 0)
{
// основной код программы
}
}
Почему именно такой код? Я его просто подсмотрел несколькими строками выше:
if ((PhoneApp.this.phone.getPhoneType() != 2) && (HtcFeatureList.FEATURE_THIS_IS_WORLD_PHONE != true))
continue;
int i9 = 1;
int i10 = Settings.Secure.getInt(PhoneApp.this.phone.getContext().getContentResolver(), "preferred_tty_mode", 0);
нам же нужно всего-то посмотреть значение настройки с другой переменной.
2. Все, мы нашли что нам нужно и теперь готовы писать свой патчик. Даем команду java -Xmx512m -jar baksmali.jar -a <API LEVEL> -d <FRAMEWORK DIR> -o Phone -x Phone.apk
<API LEVEL>
— это API вашей версии Android. Для JB — это 16
<FRAMEWORK DIR>
— папка, где находятся все фреймворки прошивки.
В моем случае это была команда
java -Xmx512m -jar baksmali.jar -a 16 -d S:devAndroidAndroid-KitchenWORKING_JB_15systemframework -o Phone -x Phone.apk
3. В нашей вновь созданной папке появилась папка Phone, а в ней наши файлы с Dalvik кодом.
4. Отыскиваем файл по пути \comandroidphonePhoneApp.java и смотрим код:
.line 1841
.local v7, phoneType:I
sget-boolean v4, Lcom/android/phone/HtcFeatureList;->FEATURE_APN_CONNECTION_NOTIFICATION:Z
if-eqz v4, :cond_c9c
5. Теперь после этой строки нам надо вставить нашу собственную проверку. Я нашел аналогичный код где проверяется настройка preferred_tty_mode. Нам ничего не стоит его взять и скопировать себе, поменяв название настройки и не беря первые две служебные строки
.line 1379
.local v43, setupTtyTakeAction:Z
move-object/from16 v0, p0
iget-object v4, v0, Lcom/android/phone/PhoneApp$3;->this$0:Lcom/android/phone/PhoneApp;
iget-object v4, v4, Lcom/android/phone/PhoneApp;->phone:Lcom/android/internal/telephony/Phone;
invoke-interface {v4}, Lcom/android/internal/telephony/Phone;->getContext()Landroid/content/Context;
move-result-object v4
invoke-virtual {v4}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v4
const-string v5, "preferred_tty_mode"
const/16 v62, 0x0
move/from16 v0, v62
invoke-static {v4, v5, v0}, Landroid/provider/Settings$Secure;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v58
и в итоге получается
.line 1841
.local v7, phoneType:I
sget-boolean v4, Lcom/android/phone/HtcFeatureList;->FEATURE_APN_CONNECTION_NOTIFICATION:Z
if-eqz v4, :cond_c9c
move-object/from16 v0, p0
iget-object v4, v0, Lcom/android/phone/PhoneApp$3;->this$0:Lcom/android/phone/PhoneApp;
iget-object v4, v4, Lcom/android/phone/PhoneApp;->phone:Lcom/android/internal/telephony/Phone;
invoke-interface {v4}, Lcom/android/internal/telephony/Phone;->getContext()Landroid/content/Context;
move-result-object v4
invoke-virtual {v4}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v4
const-string v5, "tweaks_disableConnectionNotification"
const/16 v62, 0x0
move/from16 v0, v62
invoke-static {v4, v5, v0}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v58
// - выйти из блока
6. Теперь нам надо сделать проверку переменной v58 и в случае не соответствия выйти из условия. Только куда нам выходить? Покопавшись в исходном коде и разобрав алгоритм, я понял, что нам надо просто напросто уйти из метода возвратив void
# virtual methods
.method public handleMessage(Landroid/os/Message;)V
.registers 68
.parameter "msg"
.prologue
.line 1084
move-object/from16 v0, p1
iget v4, v0, Landroid/os/Message;->what:I
sparse-switch v4, :sswitch_data_16e6
.line 2327
:cond_7
:goto_7
:sswitch_7
return-void
7. Добавляем условие
if-nez v58, :cond_7
в наш модифицированный код и получаем
.line 1841
.local v7, phoneType:I
sget-boolean v4, Lcom/android/phone/HtcFeatureList;->FEATURE_APN_CONNECTION_NOTIFICATION:Z
if-eqz v4, :cond_c9c
#---------------------------------------
# начало вживленного кода
move-object/from16 v0, p0
iget-object v4, v0, Lcom/android/phone/PhoneApp$3;->this$0:Lcom/android/phone/PhoneApp;
iget-object v4, v4, Lcom/android/phone/PhoneApp;->phone:Lcom/android/internal/telephony/Phone;
invoke-interface {v4}, Lcom/android/internal/telephony/Phone;->getContext()Landroid/content/Context;
move-result-object v4
invoke-virtual {v4}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v4
const-string v5, "tweaks_disableConnectionNotification"
const/16 v62, 0x0
move/from16 v0, v62
invoke-static {v4, v5, v0}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v58
if-nez v58, :cond_7
#---------------------------------------
# конец вживленного кода
.line 1844
if-nez v10, :cond_c86
.line 1845
new-instance v4, Ljava/lang/StringBuilder;
8. Даем команду java -Xmx512m -jar smali.jar -a 16 Phone -o classes.dex
9. В нашей папочке появляется файлик classes.dex
10. Снова открываем Phone.apk файл архиватором и заменяем в нем существующий classes.dex на наш только что созданный.
11. Все, наш Phone.apk содержит модифицированный программный код.
Автоматическая запись звонков
Реализацию данного твика я описал во второй части статей. Только там я покаывал код без использования твикера, так что выкладываю полную версию
.method private onCallConnected(Landroid/os/AsyncResult;)V
.registers 8
.parameter "r"
.prologue
#---------------------------------------
# начало вживленного кода
iget-object v5, p0, Lcom/android/phone/CallNotifier;->mContext:Landroid/content/Context;
invoke-virtual {v5}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v5
const/4 v4, 0x0
const-string v3, "tweaks_enableAutoRecording"
invoke-static {v5, v3, v4}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v3
if-eq v3, v4, :cond_27
const-string v3, "Falseclocks: recording tweak is enabled"
invoke-direct {p0, v3}, Lcom/android/phone/CallNotifier;->log(Ljava/lang/String;)V
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v3
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v4
const/4 v5, 0x0
if-ne v5, v4, :cond_27
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z
const-string v3, "Falseclock: automatic recording started"
invoke-direct {p0, v3}, Lcom/android/phone/CallNotifier;->log(Ljava/lang/String;)V
:cond_27
#---------------------------------------
# конец вживленного кода
const/4 v5, 0x0
.line 2302
iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object;
check-cast v0, Lcom/android/internal/telephony/Connection;
и
.method private onDisconnect(Landroid/os/AsyncResult;)V
.registers 41
.parameter "r"
.prologue
#---------------------------------------
# начало вживленного кода
move-object/from16 v0, p0
iget-object v0, v0, Lcom/android/phone/CallNotifier;->mApplication:Lcom/android/phone/PhoneApp;
move-object/from16 v34, v0
invoke-virtual/range {v34 .. v34}, Lcom/android/phone/PhoneApp;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v34
const-string v35, "tweaks_enableAutoRecording"
const/16 v36, 0x0
invoke-static/range {v34 .. v36}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v4
if-eqz v4, :cond_33
const-string v34, "Falseclocks: recording tweak is enabled"
move-object/from16 v0, p0
move-object/from16 v1, v34
invoke-direct {v0, v1}, Lcom/android/phone/CallNotifier;->log(Ljava/lang/String;)V
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v34
invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v4
if-eqz v4, :cond_33
invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->stop()Z
const-string v34, "Falseclock: automatic recording stopped"
move-object/from16 v0, p0
move-object/from16 v1, v34
invoke-direct {v0, v1}, Lcom/android/phone/CallNotifier;->log(Ljava/lang/String;)V
.line 2487
:cond_33
#---------------------------------------
# конец вживленного кода
move-object/from16 v0, p0
iget-object v0, v0, Lcom/android/phone/CallNotifier;->mCM:Lcom/android/internal/telephony/CallManager;
move-object/from16 v34, v0
Запретить энергосбережение
Патчить исходный код мне не пришлось, но в твикере реализовал следующий (урезанный для примера) программный код
try
{
if (value == 1)
{
Runtime.getRuntime().exec("su -c pm disable com.htc.htcpowermanager/.powersaver.PowerSaverNotificationReceiver");
} else {
Runtime.getRuntime().exec("su -c pm enable com.htc.htcpowermanager/.powersaver.PowerSaverNotificationReceiver");
}
}
catch (IOException e)
{
e.printStackTrace();
}
Запретить выключатели
Где хранятся эти
мне пришлось потратить некоторое время. Узнать от куда растут ноги просто так не получится как в случае с «Уведомление о соединении», так как это стандартный интерфейс. Мне пришлось распаковать framework-res.apk, framework-htc-res.apk, com.htc.resources.apk, Phone.apk, Rosie.apk и SystemUI.apk. Как раз в SystemUI и оказались изображения и строки Wi-Fi, Bluetooth, Мобильный интернет и т.д.
Точно также как и в случае с уведомлениями…
Потрошим приложение
1. Кладем SystemUI.apk в папку place-apk-here-for-modding нашего APK-Multi-Tool.
2. Открываем любим архиватором и удаляем от туда файл classes.dex. Это ускорит работу и избавит вас от ошибок декомпилятора.
3. Запускаем скрипт Script.bat и выбираем 9-ый пункт Decompile apk. Нам нужно распаковать приложение и покопаться в файлах res/values. После распаковки исходники будут лежать в папке .projectsSystemUI.apk
Поиски кода
1. Так как у меня интерфейс русский, то мне нужна папка с русскими словами .resvalues-ru.
2. На скриншоте из прошлой статьи видим, что у нас есть слово «В самолёте» и оно явно находится в нашей локализации.
3. Ищем по всем файлам наше слово… и находим
TOTAL: 3 matches in 1 file (1021 other files without matches are not listed)
3 matches in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-rustrings.xml
22 <string name="status_bar_settings_airplane">Режим «В самолёте»</string>
97 <string name="accessibility_airplane_mode">Режим «В самолёте».</string>
182 <string name="status_Bar_quick_setting_airplane">Режим «В самолёте»</string>
4. Нас интересует status_Bar_quick_setting_airplane. Делаем поиск по этой строке.
TOTAL: 2 matches in 2 files (9 other files without matches are not listed)
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluespublic.xml
1040 <public type="string" name="status_Bar_quick_setting_airplane" id="0x7f0900b2" />
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluesstrings.xml
189 <string name="status_Bar_quick_setting_airplane">Airplane Mode</string>
5. Мы нашли шестнадцатиричный ID текстовой строки 0x7f0900b2, что в десятичном у нас 2131296434 (переводится в виндовом калькуляторе).
6. Берем наш classes.dex из SystemUI.apk, конвертируем в jar и открываем в gd-gui;
7. Открыв сконвертированный classes.dex в gd-gui, сохраним наш исходный код для поиска в нем.
8. Сделаем поиск 2131296434 в наших исходниках и… ничего не находим :-(
9. Делаем поиск по всей папке .projectsSystemUI.apkres и получаем следующие:
TOTAL: 15 matches in 15 files (1007 other files without matches are not listed)
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkreslayoutstatus_bar_expanded_quick_setting.xml
35 <TextView android:gravity="center" android:id="@id/text_airplane" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/status_Bar_quick_setting_airplane" android:lines="2" />
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluespublic.xml
1040 <public type="string" name="status_Bar_quick_setting_airplane" id="0x7f0900b2" />
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluesstrings.xml
189 <string name="status_Bar_quick_setting_airplane">Airplane Mode</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-csstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Režim V letadle</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-destrings.xml
182 <string name="status_Bar_quick_setting_airplane">Flugmodus</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-esstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Modo avión</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-frstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Mode avion</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-itstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Modalità aereo</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-jastrings.xml
184 <string name="status_Bar_quick_setting_airplane">フライトモード</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-kostrings.xml
184 <string name="status_Bar_quick_setting_airplane">비행 모드</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-nlstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Vliegtuigmodus</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-plstrings.xml
182 <string name="status_Bar_quick_setting_airplane">Tryb samolotowy</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-rustrings.xml
182 <string name="status_Bar_quick_setting_airplane">Режим «В самолёте»</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-zh-rCNstrings.xml
184 <string name="status_Bar_quick_setting_airplane">飞行模式</string>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvalues-zh-rTWstrings.xml
184 <string name="status_Bar_quick_setting_airplane">飛安模式</string>
10. Из результатов понимаем, что для наших быстрых настроек есть готовый шаблон status_bar_expanded_quick_settin.xml
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkreslayoutstatus_bar_expanded_quick_setting.xml
35 <TextView android:gravity="center" android:id="@id/text_airplane" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/status_Bar_quick_setting_airplane" android:lines="2" />
11. Открываем xmk файл и видим, что layout имеет ID layoutquicksetting
<HorizontalScrollView android:orientation="vertical" android:id="@id/layoutquicksetting" android:background="@drawable/notification_quick_settings_bkg" android:scrollbars="none" android:fadingEdge="none" android:layout_width="wrap_content" android:layout_height="fill_parent" android:overScrollMode="ifContentScrolls"
xmlns:android="http://schemas.android.com/apk/res/android">
12. Ищем по layoutquicksetting и находим идентификатор 0x7f0c004c (2131492940)
TOTAL: 3 matches in 3 files (1019 other files without matches are not listed)
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkreslayoutstatus_bar_expanded_quick_setting.xml
2 <HorizontalScrollView android:orientation="vertical" android:id="@id/layoutquicksetting" android:background="@drawable/notification_quick_settings_bkg" android:scrollbars="none" android:fadingEdge="none" android:layout_width="wrap_content" android:layout_height="fill_parent" android:overScrollMode="ifContentScrolls"
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluesids.xml
79 <item type="id" name="layoutquicksetting">false</item>
1 match in S:devAndroidAPK-Multi-ToolprojectsSystemUI.apkresvaluespublic.xml
1198 <public type="id" name="layoutquicksetting" id="0x7f0c004c" />
13. Ищем по исходникам, что получили в пункте 8 и опять не находим. Два раза не найти — вещь не стандартная. Из опыта знаем, что gd-gui не всегда умеет декомпилировать код и выдает // INTERNAL ERROR //
, поэтому попробуем распаковать до smali.
14. Даем команду java -Xmx512m -jar baksmali.jar -a <API LEVEL> -d <FRAMEWORK DIR> -o SystemUI -x SystemUI.apk
<API LEVEL>
— это API вашей версии Android. Для JB — это 16
<FRAMEWORK DIR>
— папка, где находятся все фреймворки прошивки.
В моем случае это была команда
java -Xmx512m -jar baksmali.jar -a 16 -d S:devAndroidAndroid-KitchenWORKING_JB_15systemframework -o SystemUI -x SystemUI.apk
15. В нашей вновь созданной папке появилась папка SystemUI, а в ней наши файлы с Dalvik кодом.
16. Ищем в коде строку 7f0c004c
и находим ее в методе
.method private updateQuickSettingView()V
.registers 6
.prologue
const/4 v0, -0x2
.line 830
new-instance v1, Landroid/widget/LinearLayout$LayoutParams;
invoke-direct {v1, v0, v0}, Landroid/widget/LinearLayout$LayoutParams;-><init>(II)V
.line 832
iget-object v0, p0, Lcom/android/systemui/statusbar/phone/PhoneStatusBar;->mDisplayMetrics:Landroid/util/DisplayMetrics;
iget v0, v0, Landroid/util/DisplayMetrics;->widthPixels:I
div-int/lit8 v0, v0, 0x5
iput v0, v1, Landroid/view/ViewGroup$LayoutParams;->width:I
.line 834
iget-object v0, p0, Lcom/android/systemui/statusbar/phone/PhoneStatusBar;->mStatusBarWindow:Lcom/android/systemui/statusbar/phone/StatusBarWindowView;
const v2, 0x7f0c004c
Модификация кода
Анализируя Dalvik код понимаем, что метод проверяет текущие настройки и состояние железа и подставляет нужные иконки.
Чтобы убрать наш слой, мы просто его можем скрыть через метод setVisibility, поставив туда значение 8.
.line 945
iget-object v0, p0, Lcom/android/systemui/statusbar/phone/PhoneStatusBar;->mRotationBtn:Landroid/widget/LinearLayout;
new-instance v1, Lcom/android/systemui/statusbar/phone/PhoneStatusBar$17;
invoke-direct {v1, p0}, Lcom/android/systemui/statusbar/phone/PhoneStatusBar$17;-><init>(Lcom/android/systemui/statusbar/phone/PhoneStatusBar;)V
invoke-virtual {v0, v1}, Landroid/widget/LinearLayout;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 962
#---------------------------------------
# начало вживленного кода
iget-object v0, p0, Lcom/android/systemui/SystemUI;->mContext:Landroid/content/Context;
invoke-virtual {v0}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v0
const-string v1, "tweaks_disable_stock_qs"
const/4 v2, 0x0
invoke-static {v0, v1, v2}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I
move-result v0
const/4 v2, 0x1
if-ne v0, v2, :cond_2de
iget-object v0, p0, Lcom/android/systemui/statusbar/phone/PhoneStatusBar;->mQuickSettingBar:Landroid/widget/HorizontalScrollView;
const/16 v2, 0x8
invoke-virtual {v0, v2}, Landroid/widget/HorizontalScrollView;->setVisibility(I)V
:cond_2de
#---------------------------------------
# конец вживленного кода
return-void
.end method
Заключение
В целом модификация прошивок весьма интересное и увлекательное занятие. Разобравшись однажды в принципах, вы сможете с легкой руки добавлять или избавляться от функционала в своем телефоне. Искренне надеюсь, что вам это нравится также как и мне.
Автор: Falseclock