EST. 2026 — Notes from real server work SYSADMIN · SECURITY · AI · DIRECTADMIN Issue 01
THE SYSADMIN ALMANAC
Home / Linux / Android Dev on MIUI/HyperOS — adb Traps on Windows
Linux

Android Dev on MIUI/HyperOS — adb Traps on Windows

by Ton-Or ·23 Jul 2026·8 min read
Android Dev on MIUI/HyperOS — adb Traps on Windows

adb reports INSTALL_FAILED, screenshots are 0 bytes, and /sdcard paths end up in the Git folder — these three symptoms do not mean the machine is broken, but that Windows, Git Bash, PowerShell, and MIUI each interfere with adb at a different point

Lately I have been working on an Android app written in Jetpack Compose, with a Laravel backend, deploying it to a Xiaomi test device running MIUI/HyperOS from a Windows dev box — once I got started, I found several places where adb "did not work," even though adb itself was fine. The problem was that Windows, Git Bash, PowerShell, and MIUI each got between the commands and the device in their own way. Tracking them all down took me a lot of time, so I have collected the traps I actually ran into, along with commands that worked.

The package name (com.example.pos), device serial, and paths in this article are examples; the commands and symptoms are all real.

Trap 1: Git Bash mangles /sdcard/ paths

The first symptom was that I ran adb push to /sdcard/Download/, and the file simply disappeared from view — it was not on the device. After digging around, I found it on disk at C:/Program Files/Git/sdcard/Download/ instead. The cause was Git Bash (MSYS) automatically converting POSIX paths. When it encountered /sdcard/..., it treated it as an absolute path on Windows and prepended the Git prefix.

Why the path gets mangledadb push ... /sdcard/Typed in Git Bash→MSYS converts POSIX pathsAutomatically→Ends up at C:/Program Files/Git/sdcard/Wrong location

My solution was to run adb push from PowerShell instead — the device-side path does not get converted at all:

$device = "abc123def456"
$apk = "app\build\outputs\apk\debug\app-debug.apk"
adb -s $device push $apk /sdcard/Download/app-beta.apk

If you really need to stay in Git Bash, disable path conversion with the MSYS_NO_PATHCONV variable for that command only:

MSYS_NO_PATHCONV=1 adb -s "$device" push "$apk" /sdcard/Download/app-beta.apk

Personally, I chose to do all my adb work in PowerShell. There are fewer rules to remember, and I do not have to keep track of which commands contain device-side paths.

Trap 2: PowerShell produces 0-byte screenshots

When I needed a screenshot from the device to attach to a test report, I ran the command I was used to on Linux:

adb exec-out screencap -p > .tmp\screen.png

It produced a file, but I could not open it; sometimes it was 0 bytes. The cause was the > operator in PowerShell: it is Out-File, which applies text encoding to everything passing through it — with a binary stream such as PNG, that conversion corrupts the bytes, so it is no longer the original image file.

Often breaks

Pipe directly through PowerShell

Pass PNG bytes through PowerShell's > — text encoding alters every byte, leaving a 0-byte file or one that will not open

What actually works

Save on the device, then pull

Have screencap write to /sdcard on the device first, then bring it back with adb pull — the bytes never pass through PowerShell, and the image is intact every time

The method that gave me an intact image every time was to have the device save it to /sdcard first, then bring it back with adb pull. The bytes never have to pass through PowerShell:

adb -s $device shell screencap -p /sdcard/screen.png
adb -s $device pull /sdcard/screen.png .tmp\screen.png
adb -s $device shell rm /sdcard/screen.png

If you really want to pipe directly, use cmd.exe redirection instead, because cmd does not interfere with the stream's encoding:

adb exec-out screencap -p > screen.png

Another common mistake is saving to C:\temp: that folder may not actually exist, and PowerShell does not create it the way Git Bash does — saving to a folder in the project (such as .tmp\) that definitely exists is more reliable.

Trap 3: MIUI blocks the first installation

After the build finished, my first attempt to install on the device got this response from MIUI/HyperOS:

adb: failed to install fresh.apk: Failure [INSTALL_FAILED_USER_RESTRICTED: Install canceled by user]

The confirmation popup had not even appeared on the device yet. The cause was that the "Install via USB" toggle in Developer options was off. With it off, MIUI blocks every first installation through adb. Enabling this toggle requires signing in to a Mi Account and having a SIM in the device, and some test devices have no SIM inserted. My solution was to go around that check — push the APK to /sdcard, then tap to install it manually through the Files app on the device, because installations initiated by the user do not hit this check.

  1. Run assembleDebugAndroidTest to produce both the app APK and the test APK
  2. Install manually the first time — push the APK to /sdcard, then tap to install through the Files app on the device (bypassing the MIUI check)
  3. For subsequent installations, use adb install -r to replace it silently without being blocked again
  4. Run tests directly with am instrument, without relying on a gradle task

Once the first installation went through, subsequent updates worked silently with the -r flag (reinstall over the existing app). MIUI did not ask again:

.\gradlew.bat assembleDebugAndroidTest
# รอบแรก: push ให้ผู้ใช้ติดตั้งด้วยมือผ่าน Files
adb -s $device push app\build\outputs\apk\androidTest\debug\app-debug-androidTest.apk /sdcard/Download/
# รอบถัดไป: ทับได้เลยด้วย -r
adb -s $device install -r app\build\outputs\apk\androidTest\debug\app-debug-androidTest.apk

The important part is to avoid the gradle task connectedAndroidTest on MIUI — run am instrument directly instead:

# รันทั้งชุด
adb -s $device shell am instrument -w -r -e debug false com.example.pos.test/androidx.test.runner.AndroidJUnitRunner
# รันเฉพาะคลาสเดียว
adb -s $device shell am instrument -w -r -e class com.example.pos.OwnerHomeTest com.example.pos.test/androidx.test.runner.AndroidJUnitRunner

-w means wait until it finishes; -r means raw output that can be parsed afterward.

Do not use gradle connectedAndroidTest on MIUI

This task uninstalls the main app first and then reinstalls it. When the MIUI check rejects the reinstall, the main app and the test app are both gone from the device — you have to install everything manually again. I have been through this myself. Run am instrument directly from the start instead

Another issue I ran into with test suites lasting more than five minutes was that MIUI put the screen to sleep and the activity lost focus, causing batches of tests that tapped or scrolled to fail. Prevent this by keeping the screen awake before starting:

adb -s $device shell input keyevent KEYCODE_WAKEUP
adb -s $device shell svc power stayon true

Trap 4: PowerShell here-strings swallow backslashes when generating PHP code

The last trap was on the backend side, when generating Laravel seed / test fixtures to send to the server. I once tried putting multiple lines of PHP code into a PowerShell here-string and piping it over ssh — the backslashes in the code were swallowed at the source. By the time it reached the server, PHP threw a confusing parse error that was very hard to track down, because the local file and the one sent to the server looked different.

Do not generate PHP code through PowerShell here-strings

Backslashes and escape characters in the code get swallowed. Once sent to the server, PHP throws a parse error that is hard to trace because the source and destination code do not match — writing a local file first, then sending it with scp to run on the server, is much safer

My solution was to write the code to an actual local file first (through an editor, not a here-string), then upload it with scp and run it. Afterward, I deleted the temporary file every time:

scp .\scripts\seed.php host:/home/deploy/seed.php
ssh host "php /home/deploy/seed.php && rm -f /home/deploy/seed.php"

One thing to watch for in that file is to never put Windows paths (D:\... or C:\...) in a script that will run on Linux, because the backslashes turn into strange filenames in /tmp, and some servers have monitors watching for unusual filenames. Use Linux paths throughout, and clean up temporary files every time after the script finishes.

The sequence that works reliably

After tracking down all four issues, this became my regular pipeline — choose the right tool for each step and avoid switching back and forth:

The sequence that works reliablybuild + push in PowerShellNot Git Bash→Install manually firstBypass the MIUI check→adb install -r afterward→am instrument to run testsNo gradle task→screencap on device + pull

All these traps come down to the same thing — adb commands do not go straight to the device as I expected. Windows, Git Bash, and PowerShell add layers in between, and MIUI adds another check of its own. Once I knew what was interfering and where, I could work around all of it. Feel free to adjust the flow to suit your own device and ROM. If I have left anything out or made a typo, I will come back and fix it later.

Layers between commands and the deviceadb commandon WindowsGit BashMangles /sdcardPowerShell> corrupts PNG bytesadbMIUI deviceBlocks first installInstall manually firstadb itself is fine; the layers in between cause the unexpected results
Git Bash converts paths, PowerShell corrupts binary bytes, and MIUI blocks the first installation

Hit something like this? Stuck somewhere?

Come trade notes on the LinuxThai board

→ Continue on the board

Comments 0

No comments yet — be the first