Sunday, June 14, 2020

AndroidSDKのjavaライブラリの場所

android.app.ActivityManagerやandroid.os.BuildなどといったJavaのAndroid用ライブラリの場所が気になったので調べてみた。

結論からいうと私の環境では C:/Users/z4ck/AppData/Local/Android/Sdk/sources/android-29/以下にあった。

一般的に <sdkDir>/sources/android-<sdkVer>/ 以下にあると思われる。

sdkDirはAndroidStudioでTools->SDKManager に表示されるAndroid SDK Locationsというところで確認できる。


Friday, June 12, 2020

AndroidのPerfMon解析 その2

前回の続き。PerfMonを解析していく。

前回はアプリ起動時に実行されるActivityから順に処理を追っていたが、プログラムがデコンパイルしたソースということもあり順に追っていくのは大変そうなのでアプローチを変えてみる。

/proc/以下や /sys/以下のハードウェア情報にアクセスしてると予想できるのでソースファイルの入ったフォルダで文字列検索


$ grep -R "/proc/" ../../PerfMon-v1.21-java/sources/eu/chainfire/perfmon/
../../PerfMon-v1.21-java/sources/eu/chainfire/perfmon/ac.java:            BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/stat"));
../../PerfMon-v1.21-java/sources/eu/chainfire/perfmon/ac.java:                        StringTokenizer stringTokenizer = new StringTokenizer(new BufferedReader(new FileReader(String.format("/proc/%d/stat", new Object[]{Integer.valueOf(iArr[i5])}))).readLine());
../../PerfMon-v1.21-java/sources/eu/chainfire/perfmon/ac.java:            BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/net/dev"));
ac.javaだけヒットした。このファイルを開いてみた。

        for (int i = 0; i < 256; i++) {
            if (new File("/sys/devices/system/cpu/cpu" + String.valueOf(i)).exists() && i + 1 > this.a) {
                this.a = i + 1;
            }
        }
まずはコンストラクタにあるこのコード。/sys/devices/system/cpu/下にあるcpu0,cpu1,cpu2...というフォルダを数えることでコア数を数えている。
Android10でのPerfMon
コア数を数えることはできているようだ。


            BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/stat"));
            for (String readLine = bufferedReader.readLine(); readLine != null; readLine = bufferedReader.readLine()) {
                if (readLine.startsWith("cpu")) {

次にこのコード。/proc/statの中身を読んでそれぞれの数値を抽出してる部分である。

ファイルの中身を読むbuffereReaderをsudo cat /proc/statの出力ストリームに変えればよさそうである。


        while (i <= this.a) {
            try {
                agVarArr[i].j = a(String.format("/sys/devices/system/cpu/%s/cpufreq/scaling_min_freq", new Object[]{agVarArr[i].a}), 0);
                if (agVarArr[i].j == 0) {
                    agVarArr[i].j = a(String.format("/sys/devices/system/cpu/%s/cpufreq/cpuinfo_min_freq", new Object[]{agVarArr[i].a}), 0);
                }
                agVarArr[i].k = a(String.format("/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq", new Object[]{agVarArr[i].a}), 0);
                if (agVarArr[i].k == 0) {
                    agVarArr[i].k = a(String.format("/sys/devices/system/cpu/%s/cpufreq/cpuinfo_cur_freq", new Object[]{agVarArr[i].a}), 0);
                }
                agVarArr[i].l = a(String.format("/sys/devices/system/cpu/%s/cpufreq/scaling_max_freq", new Object[]{agVarArr[i].a}), 0);
                if (agVarArr[i].l == 0) {
                    agVarArr[i].l = a(String.format("/sys/devices/system/cpu/%s/cpufreq/cpuinfo_max_freq", new Object[]{agVarArr[i].a}), 0);
                }
                agVarArr[0].j = Math.max(agVarArr[0].j, agVarArr[i].j);
                agVarArr[0].k = Math.max(agVarArr[0].k, agVarArr[i].k);
                agVarArr[0].l = Math.max(agVarArr[0].l, agVarArr[i].l);
                i++;
            } catch (Exception e3) {
            }
        }

/sys/devices/system/cpu/cpu[0-9]/cpufreq/は各コアの動作クロックの情報がある。a()という関数にかけてるのでa()を見てみる。


    private int a(String str, int i) {
        try {
            return Integer.parseInt(new BufferedReader(new FileReader(str)).readLine(), 10);
        } catch (Exception e2) {
            return i;
        }
    }

/proc/statを読む部分と同じである。この部分もsuを使えば良さそうである。

Patch

実際にsuを使ったコードを書いてみる

    public static BufferedReader sudoCat(String filename){
        Process p;
        BufferedReader reader;
        try {
            p = Runtime.getRuntime().exec("su");
            DataOutputStream os = new DataOutputStream(p.getOutputStream());
            os.writeBytes("cat "+filename+"\n");
            os.writeBytes("exit\n");
            os.flush();
            p.waitFor();
            reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
        } catch (Exception e) {
            e.printStackTrace();
            reader = new BufferedReader(new StringReader("error"));
        }
        return reader;
    }

このsudoCatメソッドをFileReaderの代わりに使えば良さそう。
さて、変更を加えたこのソースをビルドしたいのだがAndroidStudioのプロジェクトとして開くにはフォルダの構造が違いすぎる。apkのフォルダ構造をAndroidStudioのプロジェクトとして開くプラグインもコンバーターのようなツールも私の知る限りでは存在しない。


なのでapktoolを使う方が楽である。smaliで先ほど書いたsudoCatを書き、FileReaderが使われているところに置換する。ちなみにsmaliとはdalvikバイトコードのアセンブリである。

5行程度だったa()メソッドはsmaliでは以下のようになる。

.method private a(Ljava/lang/String;I)I
    .locals 2
    :try_start_0
    new-instance v0, Ljava/io/BufferedReader;
    new-instance v1, Ljava/io/FileReader;
    invoke-direct {v1, p1}, Ljava/io/FileReader;->(Ljava/lang/String;)V
    invoke-direct {v0, v1}, Ljava/io/BufferedReader;->(Ljava/io/Reader;)V    
    invoke-virtual {v0}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String;
    move-result-object v0
    const/16 v1, 0xa
    invoke-static {v0, v1}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;I)I
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
    move-result p2    
    :goto_0
    return p2
    :catch_0
    move-exception v0
    goto :goto_0
.end method

続く

Sunday, June 7, 2020

AndroidのPerfMon解析 その1

AndroidのCPU使用率や動作クロックを表示してくれるアプリ「PerfMon」がAndroid8以降では/proc/以下や/sys/以下の権限がなく、動作しなくなっている。


rootを取得してる端末でもアプリがroot権限を要求してこないのでrootで実行させることができない。(過去にAndroid10のRedmiK20ProでPerMonにmagiskで権限を与えて動作させることができたのだが端末をすられてしまい、真相は闇の中)
なぜかPerfMonが動作しているAndroid10


ということでPerfMonが/sys/や/proc/にアクセスするときにsuを要求するように変更したい。



現在多忙のため何日か掛けながら目標を達成したいと思ってるので、毎日に進捗をメモしていこうと思う。



まずはapkをデコンパイルしてみた。
まず見るのはマニフェストファイルの以下の行
<activity android:excludeFromRecents="true" android:label="@string/app_name" android:launchMode="singleInstance" android:name=".LaunchActivity" android:theme="@android:style/Theme.NoDisplay">
LaunchActivityというクラスが最初に実行されるようだ。
LaunchActivityを開くとOnCreateがまず実行される。

    public void onCreate(Bundle bundle) {
        boolean z;
        boolean z2 = false;
        //~~長いので中略~~~
        if (z || booleanExtra || !z2) {
            a();
            return;
        }

重要なのは最後のa();の部分である。a()メソッドが定義された場所を見てみる。

    public void a() {
        boolean[] zArr = new boolean[4];
        for (int i = 0; i < zArr.length; i++) {
            zArr[i] = false;
        }
        if (PerfMonWindow.a(256)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 256);
            zArr[0] = true;
        }
        if (PerfMonWindow.a(768)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 768);
            zArr[1] = true;
        }
        if (PerfMonWindow.a(512)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 512);
            zArr[2] = true;
        }
        if (PerfMonWindow.a(1024)) {
            StandOutWindow.b((Context) this, PerfMonWindow.class, 1024);
            zArr[3] = true;
        }
        Builder builder = VERSION.SDK_INT >= 11 ? new Builder(this, 2131099651) : new Builder(this);
        builder.setTitle("PerfMon" + this.a);
        builder.setItems(new CharSequence[]{"Foreground App", "CPU", "Disk I/O", "Network I/O", "Open all", "Close all", "My apps on Google Play", "Follow me on Twitter/G+"}, new d(this, zArr, this));
        builder.setCancelable(true);
        builder.setOnCancelListener(new e(this, zArr, this));
        builder.show();
    }
    
どうやらPerfMonWindowクラスのaメソッドを実行し、返り値次第でStandOutWindowのbメソッドも実行している。

そしてその下ではAlerDialogのBuilderが作られている。そのBuilderの中に"Foreground App", "CPU", "Disk I/O",...とPerfMonを起動したときに最初に現れるビューに書かれている文字のリストがある。

あれはAlertDialogだったのか。。。

PerfMonWindowクラスのaメソッドを見てみる

    public boolean a(int i, k kVar) {
        y yVar = (y) a.get(Integer.valueOf(i));
        if (yVar != null) {
            yVar.c = false;
        }
        a.remove(Integer.valueOf(i));
        return super.a(i, kVar);
    }

yクラスで定義されたオブジェクトの形にa.getの返り値を変換している。ちなみにaとはHashMapである
    public static HashMap a = new HashMap();

このHashMapに対してデータを挿入するメソッドが同クラス内にあった。
    public void a(int i, FrameLayout frameLayout) {
        if (i == 256) {
            a.put(Integer.valueOf(i), new n(this, this, i, frameLayout));
        }
        if (i == 512) {
            a.put(Integer.valueOf(i), new q(this, this, i, frameLayout));
        }
        if (i == 768) {
            a.put(Integer.valueOf(i), new j(this, this, i, frameLayout));
        }
        if (i == 1024) {
            a.put(Integer.valueOf(i), new u(this, this, i, frameLayout));
        }
    }

n,q,j,uというクラスが出てきた。それぞれ見てみる。
    public n(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.foreground_window, "Foreground App");
        this.l = (TextView) this.g.findViewById(C0000R.id.stat_pkg);
        this.m = (TextView) this.g.findViewById(C0000R.id.stat_pkg_name);
        this.n = (TextView) this.g.findViewById(C0000R.id.stat_cpu);
        this.o = (TextView) this.g.findViewById(C0000R.id.stat_cpu_detail);
        this.p = (TextView) this.g.findViewById(C0000R.id.stat_memory);
        this.q = (TextView) this.g.findViewById(C0000R.id.stat_memory_detail);
        a(new View[]{this.l, this.m, this.n, this.o, this.p, this.q});
        a();
        this.c = true;
        new Thread(new o(this)).start();
    }
nクラス

    public q(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.io_window, "Disk I/O");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_ios);
        this.c = true;
        new Thread(new r(this)).start();
    }
qクラス

    public j(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.cpu_window, "CPU");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_cpus);
        this.c = true;
        new Thread(new k(this)).start();
    }
jクラス

    public u(PerfMonWindow perfMonWindow, Context context, int i, FrameLayout frameLayout) {
        this.a = perfMonWindow;
        super(perfMonWindow, context, i, frameLayout);
        a(C0000R.layout.net_window, "Network I/O");
        this.l = (ListView) this.g.findViewById(C0000R.id.stat_list_net);
        this.c = true;
        new Thread(new v(this)).start();
    }
uクラス

なるほど、それぞれ"Foreground App", "Disk I/O", "CPU", "Network I/O"に対応しているらしい。
すると先ほどのPerfMonWindow.aメソッドの返り値次第でStandOutWindow.bメソッドを呼んでいたのは「どのモードを選択したか、どのモードを起動するか」というコードだろうと予想できる。

外部ライブラリとしてインポートされているこちらの wei.mark.standout.StandOutWindow 調べたら2012年に作られたFloatingWindow用のライブラリということが分かった
StandOut - Create Floating Apps

なるほど、chainefire氏はこのライブラリを使ってPerfMonを作ってたのか。。。
xdaにこのライブラリの使い方が詳しく説明されてるようなのでまずはこれを読んでみる。。。



続く



Sunday, December 22, 2019

Z5proGTに公式ZUI中華ロムを焼く方法

xdaにあった解説記事ほぼそのままですがEDLの入れ方が難しかったのでその部分だけ変えました。

※注意

この操作を行うと端末内の全データが消えます。バックアップなどを取っておきましょう。

必要な物

QPSTのインストールをします。











インストールが完了するとQFILというプログラムが現れるので起動します。




Load Contentというボタンをクリックし解凍したZUIロムのディレクトリ内にあるcontentsというxmlファイルを選びます。


この段階ではDownload Contentというボタンはグレーアウトしてて押せないと思います。この状態で端末とパソコンを接続しEDLモードに入れます。adb reboot edlがおすすめです。EDLモードに入ったらDownload Contentのボタンが点くので素早く押します。タイミングを逃すとSahara Errorと表示されます。その場合は一度電源を切り再びEDLモードに入り直す必要があります。



以上で完成です!

Z5proGTにTWRPを焼く方法

xdaの解説記事だけでは少しネックな所があったので纏めてみました。

必要な物

STEP1: fastbootモードで焼く

まず端末をfastbootモードに入れます。そしてコマンドプロンプトで
fastboot flash recovery recovery-TWRP-3.3.1-1004-LENOVO_Z5PRO_GT-CN-wzsx150.img
と入力します。
焼く方法は以上になりますが、厄介なことにこの状態でZUIシステムを起動すると標準のリカバリがtwrpを上書きしてしまいます。ZUIシステムを起動してもtwrpを持続させるにはmagiskを焼く必要があります。なのでtwrpを焼いた後ZUIシステムを起動せずに直接twrpを起動してmagiskを焼く必要があります。

STEP2: Recoveryモードで起動しmagiskを焼く

fastbootモードから直接Recoveryモードを起動します。twrpが起動したらダウンロードしておいたmagiskを端末に送り、焼きます。
adb push Magisk-v20.1(20100).zip /sdcard/Download/
ファイルの転送が出来たらInstallメニューからmagiskのzipを選択して焼きます。
あとはrebootして完成です。xdaにあったtwrp3.3.0は自分の端末では起動しませんでした。情報求む...

Androidを【recovery/fastboot/EDL】モードに入れる方法

電源オフ状態から

recoveryモード

  • 音量アップボタンを長押ししながら電源を付ける

fastbootモード

  • 音量ダウンボタンを長押ししながら電源を付ける

EDLモード

  • 音量アップ&ダウンボタンを押しながらケーブルでパソコンに接続する

Recoveryモードから

recoveryモード

  • adb reboot recovery
  • rebootメニューからRecoveryを選択

fastbootモード

  • adb reboot bootloader
  • rebootメニューからBootloaderを選択

EDLモード

  • adb reboot edl
  • rebootメニューからEDLを選択

Fastbootモードから

recoveryモード

  • fastbootのメニューからrecoveryを選択
  • fastboot reboot recovery
  • fastboot reboot-recovery
  • fastboot oem recovery
  • fastboot oem reboot-recovery

fastbootモード

  • fastbootのメニューからfastbootを選択
  • fastboot reboot bootloader
  • fastboot reboot-bootloader
  • fastboot oem bootloader
  • fastboot oem reboot-bootloader

EDLモード

  • fastboot reboot edl
  • fastboot reboot-edl
  • fastboot oem edl

EDLモードから

EDLモードからは電源を一旦消すしかない


※注意
上記のコマンドは端末依存・環境依存であり、特にEDLモードは存在しない端末もある。上記のfastbootコマンドは本来の仕様外のコマンドであり動作しないのが普通であるがネット上で良く動作報告があるので書いておいた。

LENOVO Z5 PRO GTのブートローダロック方法

アンロック方法ではなくロック方法です。

LenovoZ5proGTはAliexpressで買われる方が殆どだと思います。Aliexpressでこの端末を売っている業者さんはブートローダがアンロックされた状態で発送しているので届いた時点でブートローダがアンロックされていたという方が殆どかと思います。
しかしアンロックされていることのデメリットもあります。ZUIの公式ROMをQFILで焼いた後、OTAアップデートができないのです。
Your system has been root, we are unable to provide the latest version of the system
という謎の英文が出てきます。(いやroot化してないしhas been rootって英語おかしくないか...)
magiskを焼くとOTAが出来そうになるのだが途中でfailedと出てしまうためやはりブートローダが解除された状態のままアプデするのは不可能だと思われる。(情報求む)

ブートローダをロックすると問題なくOTAアップデートができるのである。

fastbootモードでブートローダロック

ブートローダをロックするにはまず端末をfastbootモードに入れます。
そしてコマンドプロンプトで
fastboot flashing lock
と入力します。fastbootモードに再起動してDEVICE STATEの所がlockedになっていれば成功です。