В прошлой статье мы обсудили некоторые нюансы касательно интерфейса, а сегодня попробуем разобрать детально каждый случай в отдельности.
На прошлых скриншотах были следующие меню в моем самодельном твикере и вызвало множество приватных вопросов о реализации.
Предпочтительный слот
Выберите 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. После распаковки исходники будут лежать в папке .\projects\Phone.apk\
Поиски кода
1. Так как у меня интерфейс русский, то мне нужна папка с русскими словами .\res\values-ru.
2. На скриншоте из прошлой статьи видим, что у нас есть слово «Подключено» и оно явно находится в нашей локализации.
3. Ищем по всем файлам наше слово… и не находим 🙁
4. У нас есть еще иконка в виде двух стрелок, поищем ее. Идем в папку \projects\Phone.apk\res\drawable-hdpi и видим ее stat_sys_apn.png.
5. Ищем идентификатор картинки по ее названию.
TOTAL: 2 matches in 2 files (13 other files without matches are not listed) 1 match in S:\dev\Android\APK-Multi-Tool\projects\Phone.apk\res\values\drawables.xml 49 <item type="drawable" name="stat_sys_apn">@drawable/zero_dummy_asset</item> 1 match in S:\dev\Android\APK-Multi-Tool\projects\Phone.apk\res\values\public.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:\Desktop\classes_dex2jar.src\com\android\phone\NotificationMgr.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:\Desktop\classes_dex2jar.src\com\android\phone\R.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:\Desktop\classes_dex2jar.src\com\android\phone\NotificationMgr.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:\Desktop\classes_dex2jar.src\com\android\phone\PhoneApp.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 файл com\android\phone\PhoneApp.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:\dev\Android\Android-Kitchen\WORKING_JB_15\system\framework -o Phone -x Phone.apk
3. В нашей вновь созданной папке появилась папка Phone, а в ней наши файлы с Dalvik кодом.
4. Отыскиваем файл по пути \\com\android\phone\PhoneApp.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. После распаковки исходники будут лежать в папке .\projects\SystemUI.apk
Поиски кода
1. Так как у меня интерфейс русский, то мне нужна папка с русскими словами .\res\values-ru.
2. На скриншоте из прошлой статьи видим, что у нас есть слово «В самолёте» и оно явно находится в нашей локализации.
3. Ищем по всем файлам наше слово… и находим
TOTAL: 3 matches in 1 file (1021 other files without matches are not listed) 3 matches in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-ru\strings.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:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\public.xml 1040 <public type="string" name="status_Bar_quick_setting_airplane" id="0x7f0900b2" /> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\strings.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. Делаем поиск по всей папке .\projects\SystemUI.apk\res\ и получаем следующие:
TOTAL: 15 matches in 15 files (1007 other files without matches are not listed) 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\layout\status_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:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\public.xml 1040 <public type="string" name="status_Bar_quick_setting_airplane" id="0x7f0900b2" /> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\strings.xml 189 <string name="status_Bar_quick_setting_airplane">Airplane Mode</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-cs\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Režim V letadle</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-de\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Flugmodus</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-es\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Modo avión</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-fr\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Mode avion</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-it\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Modalità aereo</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-ja\strings.xml 184 <string name="status_Bar_quick_setting_airplane">フライトモード</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-ko\strings.xml 184 <string name="status_Bar_quick_setting_airplane">비행 모드</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-nl\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Vliegtuigmodus</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-pl\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Tryb samolotowy</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-ru\strings.xml 182 <string name="status_Bar_quick_setting_airplane">Режим «В самолёте»</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-zh-rCN\strings.xml 184 <string name="status_Bar_quick_setting_airplane">飞行模式</string> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values-zh-rTW\strings.xml 184 <string name="status_Bar_quick_setting_airplane">飛安模式</string>
10. Из результатов понимаем, что для наших быстрых настроек есть готовый шаблон status_bar_expanded_quick_settin.xml
1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\layout\status_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:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\layout\status_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:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\ids.xml 79 <item type="id" name="layoutquicksetting">false</item> 1 match in S:\dev\Android\APK-Multi-Tool\projects\SystemUI.apk\res\values\public.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:\dev\Android\Android-Kitchen\WORKING_JB_15\system\framework -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
Заключение
В целом модификация прошивок весьма интересное и увлекательное занятие. Разобравшись однажды в принципах, вы сможете с легкой руки добавлять или избавляться от функционала в своем телефоне. Искренне надеюсь, что вам это нравится также как и мне.
ссылка на оригинал статьи http://habrahabr.ru/post/190428/
Добавить комментарий