Модификация стоковых прошивок для Android. Часть 4

от автора

Здравствуй Хабр!

В прошлой статье мы обсудили некоторые нюансы касательно интерфейса, а сегодня попробуем разобрать детально каждый случай в отдельности.

На прошлых скриншотах были следующие меню в моем самодельном твикере и вызвало множество приватных вопросов о реализации.

Предпочтительный слот
Выберите SIM карту на которой использовать передачу данных
Уведомление о соединении
Запретить оповещение об интернет подключении
Автоматическая запись звонков
Все звонки будут записаны стандартным диктофоном согласно его настройкам
Запретить энергосбережение
Запретить иконку энергосбережения в слайдере и статус баре
Запретить выключатели
Отключение в слайдере статус бара

Предпочтительный слот

Так как я являюсь ярым поклонником двухсимочных телефонов, данная функция мне нужна для того, чтобы иметь возможность использовать интернет от любого из операторов, где есть покрытие. 3G/GPRS/EDGE покрытие у всех разное, а необходимость быть действительно мобильным — для меня задача первостепенная. По умолчанию интернет работает на первой основной сим карте, но в некоторых местах оператор не имеет 3G и предоставляет слабую пропускную способность, урезая EDGE тайм слоты на канале передачи данных, соответственно передача идет по GPRS. Имя такой твикер я могу легко переключиться на второго оператора и иметь подключение по крайней мере под EDGE.

Модифицировать прошивку для этого не обязательно, а достаточно вызвать диалог и указать что вам необходимо. Сразу отмечу, что данный код применим к телефонам HTC и был написан согласно библиотеке android.net.HtcIfConnectivityManager.

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 идем смотреть что это за код.

showMobileDataConnected

  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 и понимаем что вызов у нас срабатывает в следующем блоке

FEATURE_APN_CONNECTION_NOTIFICATION

          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. Нам ничего не стоит его взять и скопировать себе, поменяв название настройки и не беря первые две служебные строки

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

который находится на строке 2327

# 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 содержит модифицированный программный код.

Автоматическая запись звонков

Реализацию данного твика я описал во второй части статей. Только там я покаывал код без использования твикера, так что выкладываю полную версию

onCallConnected

.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; 

и

onDisconnect

.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(); } 

Запретить выключатели

Где хранятся эти

выключатели

image

мне пришлось потратить некоторое время. Узнать от куда растут ноги просто так не получится как в случае с «Уведомление о соединении», так как это стандартный интерфейс. Мне пришлось распаковать 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 и находим ее в методе

updateQuickSettingView

.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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *