March 31, 2019

Детальный разбор TaxOid [1/2]

ДИСКЛЕЙМЕР: Всё нижесказанное относится, в большей степени к TaxOid 2.6. Все фрагменты кода я публикую в виде Java-псевдокода на JaDX

Общая структура

  1. /assets/ — история изменений в формате HTML (ru + uk) / Лицензия
  2. /fabric/ — служебные файлы библиотеки fabric (статистика и отчёты об ошибках)
  3. /lib/ — скомпилированные нативные библиотеки (libyandexmapkit.so) для работы карт Яндекс в приложении
  4. /META-INF/ — подпись приложения
  5. /okhttp3/ — служебные файлы библиотеки OkHttp v3 (работа с сетью)
  6. /res/ — ресурсы приложения (иконки/разметка/звуки и т.д.)
  7. AndroidManifest.xml — паспорт приложения
  8. classes.dex — скомпилированный код приложения
  9. resources.arsc — строковые ресурсы приложения

Код

Основной код приложения находится в classes.dex, а именно, в двух пакетах:

  1. /com/pk/
  2. /com/a/

Рассмотрим поподробнее:

Анимация интерфейса

com/pk/taxoid/a/e.smali — класс управляющий интерфейсом списка заказов. Отвечает за отображения готовых заказов, выводит предупреждения о том, что приложение устарело/позывной заблокирован/сеанс уже запущен на другом устройстве.

Для удобства я буду предоставлять более понятный и удобный Java-код, вместо smali:

private void e(JSONObject jSONObject) throws Exception {
        String string;
        int i = 0;
        this.l.setVisibility(0);
        g();
        if (this.e.d()) {
            this.i.setCurAddress(this.e.a());
        }
        if (jSONObject.has("error")) {
            string = jSONObject.getString("error");
            Object obj = -1;
            int hashCode = string.hashCode();
            if (hashCode != 327580607) {
                if (hashCode == 2099550724 && string.equals("wrong_session")) {
                    obj = null;
                }
            } else if (string.equals("driver_not_found")) {
                obj = 1;
            }
            switch (obj) {
                case null:
                    i();
                    return;
                case 1:
                    j();
                    return;
            }
        }
        if (jSONObject.has("road_from") && !jSONObject.isNull("road_from")) {
            string = jSONObject.getString("road_from");
            if (!this.c.U().equals(string)) {
                this.c.a(string);
            }
        }
        JSONArray jSONArray = jSONObject.getJSONArray("status");
        int i2 = 0;
        while (i2 < jSONArray.length()) {
            JSONObject jSONObject2 = jSONArray.getJSONObject(i2);
            com.pk.taxoid.c.b.a aVar = (com.pk.taxoid.c.b.a) this.f.get(i2);
            if (jSONObject2.getString("order").equals("locked")) {
                CharSequence charSequence = BuildConfig.FLAVOR;
                try {
                    charSequence = jSONObject2.getString("fmanid");
                } catch (Exception unused) {
                    if (!charSequence.equals(BuildConfig.FLAVOR)) {
                        while (i < this.f.size()) {
                            if (((com.pk.taxoid.c.b.a) this.f.get(i)).c().contains(charSequence)) {
                                aVar = (com.pk.taxoid.c.b.a) this.f.get(i);
                            } else {
                                i++;
                            }
                        }
                    }
                    a(aVar.f(), jSONObject2.getString("reason"), Long.valueOf(!jSONObject2.isNull("ftime_unlock") ? jSONObject2.getLong("ftime_unlock") : 0));
                    return;
                }
            } else if (jSONObject2.isNull("order")) {
                if (!(jSONObject2.isNull("message") || this.c.O())) {
                    a(jSONObject2.getString("message"), aVar);
                }
                i2++;
            } else {
                a(jSONObject2, aVar);
                return;
            }
        }
        this.d.a(jSONObject.getJSONObject("orders"), this.f, getActivity());
        h();
        this.u.removeCallbacks(this.t);
        this.u.postDelayed(this.t, 2000);
        if (this.c.c() == 3) {
            Iterator it = this.d.iterator();
            while (it.hasNext()) {
                com.pk.taxoid.c.b.b bVar = (com.pk.taxoid.c.b.b) it.next();
                if (!this.d.d(bVar)) {
                    if (VERSION.SDK_INT >= 21) {
                        this.n.speak(bVar.t(), 0, null, null);
                    } else {
                        this.n.speak(bVar.t(), 0, null);
                    }
                    bVar.f(true);
                    this.d.c(bVar);
                }
            }
        }
        if (this.c.B()) {
            f(jSONObject);
        }
    }

Этот метод работает с уже готовыми данными, которые поступают в виде входного параметра jSONObject:

private void e(JSONObject jSONObject) throws Exception {

Но не это главное, вот 3 интересных строчки:

    this.d.a(jSONObject.getJSONObject("orders"), this.f, getActivity());
    h();
    this.u.removeCallbacks(this.t);
    this.u.postDelayed(this.t, 2000);

Коротко о том, что здесь происходит:

  1. Вызывается метод a() из класса d , который получает на вход список заказов
  2. Вызывается метод h()
  3. Удаляются каллбэки изпеременной t
  4. Через 2 секунды объект t начинает работу по-новой

Четвертая строчка самая интересная. Но что такое t? Где и как его посмотреть?

Смотрим объявление в классе:

private Runnable t = new -$Lambda$e$HKo-aGZ4iWu9ypto2NekUDTU9n4(this);

t - это анонимный класс -$Lambda$e$HKo-aGZ4iWu9ypto2NekUDTU9n4

переходим в com/pk/taxoid/a/-$Lambda$e$HKo-aGZ4iWu9ypto2NekUDTU9n4.smali

package com.pk.taxoid.a;

/* compiled from: lambda */
public final /* synthetic */ class -$Lambda$e$HKo-aGZ4iWu9ypto2NekUDTU9n4 implements Runnable {
  private final /* synthetic */ e f$0;

  public /* synthetic */ -$Lambda$e$HKo-aGZ4iWu9ypto2NekUDTU9n4(e eVar) {
    this.f$0 = eVar;
  }

  public final void run() {
    this.f$0.l();
  }
}

Что происходит здесь? Вызывается метод l() из класса e

Возвращаемся в класс e:

 private /* synthetic */ void l() {
        this.l.setVisibility(4);
    }

this.l - это прогресс-бар (полоска сразу под тулбаром). Т.е. каждые 2 секунды полоска становится видимо. Чисто интерфейс. Скорость загрузки заказов не изменяется

Подготовка заказов

Класс com/pk/taxoid/a/c.smali отвечает за детали заказа (время, начальная точка, конечная точка и т.д.)

Тут приложение получает данные о заказе, такие как время, точка "A", точка "B" и т.д.

    public void a(JSONObject jSONObject) {
        try {
            TextView textView;
            CharSequence a;
            String string = jSONObject.getString("point");
            if (string.equals("0")) {
                a();
            } else if (string.equals("A")) {
                b();
            }
            this.H = string;
            JSONObject jSONObject2 = jSONObject.getJSONObject("order");
            Long valueOf = Long.valueOf(jSONObject2.getLong("F0") * 1000);
            this.j.setVisibility(0);
            this.j.setText(d.a(getString(R.string.time_of_get_order), d.a("HH:mm:ss", valueOf.longValue())));
            if (jSONObject2.isNull("FA")) {
                long j = jSONObject.getLong("server_time") * 1000;
                if (!this.E) {
                    this.D = ((long) this.B) - (j - valueOf.longValue());
                }
                if (this.D <= 60000 && !this.E) {
                    if (!(this.w.getVisibility() == 0 || this.y)) {
                        this.w.setVisibility(0);
                        this.x.setBackgroundColor(-65536);
                    }
                    if (this.D <= 0) {
                        this.E = true;
                        d.a((int) R.raw.driver_late);
                    }
                    if (this.D > 0 && this.D < 6000) {
                        d.a((int) R.raw.beep_sound);
                    }
                    this.v.setTextColor(-65536);
                }
                if (this.E) {
                    this.D = (j - valueOf.longValue()) - ((long) this.B);
                }
                textView = this.v;
                a = d.a("mm:ss", this.D);
            } else {
                this.k.setVisibility(0);
                textView = this.k;
                a = d.a(getString(R.string.time_of_point_a), d.a("HH:mm:ss", jSONObject2.getLong("FA")));
            }
            textView.setText(a);
        } catch (JSONException unused) {
            this.b.a(this.A, "state");
        }
    }

Формирование запроса на сервер

Класс  com/pk/taxoid/network/c.smali

Запрос на сервер:

protected JSONObject a()
  {
    do
    {
      this.c = null;
      c localc1;
      if (!d()) {
        localc1 = c.b;
      }
      Object localObject;
      for (;;)
      {
        this.c = localc1;
        break;
        if (b.a(this.a).d()) {
          break;
        }
        try
        {
          b.a(this.a).a();
        }
        catch (IOException localIOException)
        {
          c localc2 = c.a;
        }
        catch (ConnectException localConnectException)
        {
          c localc3 = c.d;
        }
        catch (UnknownHostException localUnknownHostException)
        {
          localObject = c.c;
        }
      }
      if (this.c == null) {
        try
        {
          if (c()) {
            return null;
          }
          b.a(this.a).a(this.d);
          localObject = a(b.a(this.a).c());
          return (JSONObject)localObject;
        }
        catch (Exception localException)
        {
          b.a(this.a).b();
        }
      }
      b(this.c);
    } while (!c());
    return null;
  }

И еще (берется информация о водителе):

public void a(com.pk.taxoid.c.b.a aVar) {
        try {
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("action", "message_state");
            jSONObject.put("fmanid", aVar.c());
            jSONObject.put("ftaxi", aVar.e());
            jSONObject.put("fdri", aVar.f());
            a(jSONObject);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

И еще (палится id сессии/ос/версия приложения)

 public void a(com.pk.taxoid.c.b.b bVar) {
        try {
            com.pk.taxoid.c.b.a E = bVar.E();
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("action", "assign");
            jSONObject.put("fmanid", E.c());
            jSONObject.put("session", E.d());
            jSONObject.put("ftaxi", E.e());
            jSONObject.put("order_id", bVar.h());
            jSONObject.put("os", "android");
            jSONObject.put("version", 39);
            if (this.d != null) {
                this.d.interrupt();
            }
            a(jSONObject);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
 public void a(com.pk.taxoid.c.b.b bVar, String str, long j) {
        if (str.equals("assign") && this.g) {
            b(bVar, str);
            return;
        }
        if (str.equals("assign")) {
            this.g = true;
        }
        if (str.equals("state")) {
            this.g = false;
        }
        try {
            com.pk.taxoid.c.b.a E = bVar.E();
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("action", str);
            jSONObject.put("fmanid", E.c());
            jSONObject.put("session", E.d());
            jSONObject.put("ftaxi", E.e());
            jSONObject.put("order_id", bVar.h());
            jSONObject.put("os", "android");
            jSONObject.put("version", 39);
            if (this.d != null) {
                this.d.interrupt();
            }
            if (j == 0) {
                a(jSONObject);
            } else {
                a(jSONObject, j);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

А вот тут палят логин и пароль:

 public void a(String str, String str2, String str3) {
        try {
            JSONObject jSONObject = new JSONObject();
            jSONObject.put("action", str3);
            jSONObject.put("login", str);
            jSONObject.put("password", str2);
            a(jSONObject);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

Еще одна версия (палят логин/пароль/имей/мак-адрес/модель):

 public void d() {
        try {
            JSONObject jSONObject = new JSONObject();
            JSONArray jSONArray = new JSONArray();
            Iterator it = this.a.iterator();
            while (it.hasNext()) {
                com.pk.taxoid.c.b.a aVar = (com.pk.taxoid.c.b.a) it.next();
                JSONObject jSONObject2 = new JSONObject();
                jSONObject2.put("login", aVar.a());
                jSONObject2.put("password", aVar.b());
                jSONArray.put(jSONObject2);
            }
            Object obj = BuildConfig.FLAVOR;
            if (this.a.size() > 0) {
                obj = com.pk.taxoid.b.d.a((com.pk.taxoid.c.b.a) this.a.get(0));
            }
            jSONObject.put("action", "get_session");
            jSONObject.put("data", jSONArray);
            jSONObject.put("imei", com.pk.taxoid.b.d.a());
            jSONObject.put("number", com.pk.taxoid.b.d.b());
            jSONObject.put("model", com.pk.taxoid.b.d.d());
            jSONObject.put("mac", com.pk.taxoid.b.d.c());
            jSONObject.put("os", "android");
            jSONObject.put("android", VERSION.RELEASE);
            jSONObject.put("version", 39);
            jSONObject.put("version_name", "2.6.0");
            jSONObject.put("push_endpoint", this.b.ab());
            jSONObject.put("city", obj);
            a(jSONObject, 1000);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

Вот метод a(), который принимает рандомное число, и стартит метод d.start() c определенной задержкой (но на скорость загрузки заказов он не влияет)

 private void a(JSONObject jSONObject, long j) {
        int nextInt;
        this.d = new a(jSONObject.toString());
        if (j != 0) {
            Random random = this.f;
            int i = 1172;
            while (true) {
                nextInt = random.nextInt(i);
                if (nextInt >= 817) {
                    break;
                }
                random = this.f;
                i = 1151;
            }
        } else {
            nextInt = 150;
        }
        this.d.a((long) nextInt);
        this.d.start();
    }