Модификация стоковых прошивок для 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 -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
    



    Заключение


    В целом модификация прошивок весьма интересное и увлекательное занятие. Разобравшись однажды в принципах, вы сможете с легкой руки добавлять или избавляться от функционала в своем телефоне. Искренне надеюсь, что вам это нравится также как и мне.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 14
    • 0
      Вопрос к знающему человеку можно?
      Надо дёргать скрипт (или записывать определённое значение в /sys/что/то/там) по переключателю из стандартного окна настроек.
      Это решаемо?

      Конкретно — выкл mobile => отключить модем, вкл mobile => сперва включить модем, потом обычная логика.
      • 0
        Извините, но я вас не понял.
        • 0
          Мм… Каким образом можно повеситься на события включения-выключения того же мобильного инета в штатных настройках?
          Я утонул в исходниках в поисках этого места :(
          • 0
            Phone.apk
            • 0
              эм. то есть окно «настройки» дёргает стандартный phone.apk ?!
              • +1
                а почему бы и нет?

                чтобы добавить пункт в меню настроек достаточно на событие нажатия указать Action для какого-то приложения.

                Если распотрошите Settings.apk то поймете, что в нем мало чего своего, все дергается из окон настроек других системных приложений, при чем многие эти системные приложения в манифесте не имеют иконку и не отображаются в списках приложений.
                • 0
                  По идее, надо перехватывать включение мобильных данных где-то уровнем выше.
                  Можно же их включить не только из страницы настроек, но и из виждета, из «шторки» или каких-то других мест.
                  • 0
                    да, но listener-ы сидят в системных приложениях и скорее всего там же в Phone.apk или framework.jar

                    можно конечно и свое приложение написать, чтобы перехватывать броадкасты
      • 0
        В HTC Sense не нравится, что двойное нажатие кнопки гарнитуры делает «Redial last number», а долгое нажатие — вызывает Google Now.
        Где могут жить эти обработчики, хотя бы приблизительно?
        • 0
          Скорее всего в SystemUI.

          сделайте как я, распотрошите приложения, найдите где хранится иконка гарнитуры, по ее ID найдите где она отрисовывается, а там где она отрисовывается и назначаются действия.
          • 0
            Тогда такой вопрос. Что подразумевается под -d <FRAMEWORK DIR>?
            Нужно скопировать на компьютер папку /system/framework и указывать путь к ней?
            • 0
              в первой статье все подробно описано

              FRAMEWORK DIR — папка, где находятся все фреймворки прошивки, уже деодексидированные
              • 0
                Файлы фреймворка могут быть и в odex-виде, всё равно идёт работа напрямую с dex-кодом.
                • 0
                  все верно, но имеется в виду сама статья, где описаны шаги по получению состояния, пригодного для начала работы.

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