Как стать автором
Обновить

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

Время на прочтение21 мин
Количество просмотров50K
Здравствуй Хабр!

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

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

Предпочтительный слот
Выберите 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 -d -o Phone -x Phone.apk

— это API вашей версии Android. Для JB — это 16
— папка, где находятся все фреймворки прошивки.

В моем случае это была команда
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 -d -o SystemUI -x SystemUI.apk

— это API вашей версии Android. Для JB — это 16
— папка, где находятся все фреймворки прошивки.

В моем случае это была команда
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



Заключение


В целом модификация прошивок весьма интересное и увлекательное занятие. Разобравшись однажды в принципах, вы сможете с легкой руки добавлять или избавляться от функционала в своем телефоне. Искренне надеюсь, что вам это нравится также как и мне.
Теги:
Хабы:
Всего голосов 35: ↑33 и ↓2+31
Комментарии14

Публикации

Истории

Работа

Swift разработчик
43 вакансии
iOS разработчик
25 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн