Almost 9 years ago I got myself a nice little tablet made by nVidia, the shield tablet had a very powerfull SoC and some very nice features, among them was an SD card slot and a direct HDMI output with a dedicated port. The biggest downside was that its battery could explode. Without going into much details nVidia made a total recall of the product and released an OTA to make all tablets unusable, this could obviously be avoided by doing some basic modifications. Later on they released the same tablet with a different name (K1) and with what I assume a non-exploding battery, to their credit they did support the firmware from android 4 to android 7! and that’s more than what most OEM do even today.

Since I don’t really use the tablet at all I decided to try to make it a media center for an old non-smart TV I have. Here’s a simple rundown of what the tablet does when connected to a TV and what I want it to do instead:

  • As soon as the HDMI cable is connected a pop-up shows up on the screen asking if you want to go into Console Mode or Mirror Mode. Mirror Mode pretty much explains itself, Console Mode is where the interface changes a little bit (mainly it goes into full screen mode and launches the “nVidia Games” app). Then when you disconnect the HDMI cable another pop-up asks you if you want to exit Console Mode (it should be pointed out there’s a bug here that nVidia never fixed where the screen remains in landscape mode but the actual touch screen is in portrait mode so you kinda have to guess what part of the screen to touch to actually hit the exit button).

  • What I want to do is very simple, when I connect the HDMI cable I want to go into Console Mode automatically without a prompt and when I disconnect the HDMI cable I want it to exit from Console Mode automatically too. We are also adding a leanback interface for ease-of-use, some small tweaks for Kodi to play more smoothly some content on this device (mainly x265) and some data transfer tips.

Knowledge needed:

  1. Smali, JavaScript.
  2. Bash.
  3. Basic understanding of how android and Magisk work.
  4. Common sense.

Before we start know that most mods/tweaks are going to be packaged and delivered through Magisk. It’s the fastest and easiest way, plus you can disable any module at any time for any reason. Be familiar with how Magisk modules work.


Part I - Modding ConsoleUI.apk

In order to modify the behaviour of the ConsoleUI apk to do what we want we need to edit the smali code inside, reading and modifying smali is not pretty, JADX and Bytecode Viewer are two very useful tools that try to translate smali into something more understandable, later on we can use that knowledge to locate and then edit the smali code itself.

A couple of good resources to have at hand:

http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions

Some good articles:

https://httptoolkit.com/blog/android-reverse-engineering/

https://www.hebunilhanli.com/wonderland/mobile-security/decompile-modify-smali-recompile-and-sign-apk/

https://forum.xda-developers.com/t/guide-smali-understanding-and-creating-smali-mods-general-smali-questions.2488033/

https://book.hacktricks.xyz/mobile-pentesting/android-app-pentesting/google-ctf-2018-shall-we-play-a-game

If bluetooth is disabled while connecting the HDMI cable we not only get the typical Console Mode/Mirror Mode splash screen, after choosing any mode we also get a pop-up telling us bluetooth is disabled and asks us if we still want to proceed. To remove this check we can do so in TVModeEnabler.smali

Insert in .line 170 (copied from line .199 and .200), and add +1 to .locals:

	iget-object v5, p0, Lcom/nvidia/roth/consoleui/TVModeEnabler;->mConsoleMode:Lcom/nvidia/roth/consoleui/ConsoleMode;

    invoke-virtual {v5, p1}, Lcom/nvidia/roth/consoleui/ConsoleMode;->setConsoleMode(Z)Z
    
    invoke-virtual {p0}, Lcom/nvidia/roth/consoleui/TVModeEnabler;->finish()V

But we can do even better, we can skip all this by just going into Console Mode as soon as the HDMI cable is connected by editing TVModeEnabler.smali

In .method public onResume()V change .locals to 8 and change .line 90 to this:

    invoke-static {}, Lcom/nvidia/roth/consoleui/HDMIState;->getInstance()Lcom/nvidia/roth/consoleui/HDMIState;

    move-result-object v4

    invoke-virtual {v4, p0}, Lcom/nvidia/roth/consoleui/HDMIState;->registerCallback(Lcom/nvidia/roth/consoleui/HDMIState$HotplugCallback;)V
    
    iget-object v6, p0, Lcom/nvidia/roth/consoleui/TVModeEnabler;->mConsoleMode:Lcom/nvidia/roth/consoleui/ConsoleMode;
    
    const/4 v7, 0x1

    invoke-virtual {v6, v7}, Lcom/nvidia/roth/consoleui/ConsoleMode;->setConsoleMode(Z)Z

    invoke-virtual {p0}, Lcom/nvidia/roth/consoleui/TVModeEnabler;->finish()V

Now to automatically leave Console Mode when disconnecting the HDMI cable in ExitRequired.smali:

Change .locals to 8 in .method public onResume()V and in .line 91 change to this:

    .end local v0    # "alert":Landroid/app/AlertDialog$Builder;
    :cond_0
    iget-object v6, p0, Lcom/nvidia/roth/consoleui/ExitRequired;->mConsoleMode:Lcom/nvidia/roth/consoleui/ConsoleMode;
    
    const/4 v7, 0x0

    invoke-virtual {v6, v7}, Lcom/nvidia/roth/consoleui/ConsoleMode;->setConsoleMode(Z)Z
    
    new-instance v0, Landroid/app/AlertDialog$Builder;

    iget-object v2, p0, Lcom/nvidia/roth/consoleui/ExitRequired;->mFragmentActivity:Lcom/nvidia/roth/consoleui/ExitRequired;

    const v3, 0x7f080003

    invoke-direct {v0, v2, v3}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;I)V

Part II - Adding a leanback launcher

To get a modern interface we are going to replace the default launcher used in Console Mode with a leanback version, in this case I’m going to use Leanback on Fire as a base. In order for this to work we need to:

  • Rename the package name of LeanbackOnFire to be exactly the same as the one used in the Console Mode launcher.
  • Re-sign the apk.
  • Create a Magisk module to override it systemlessly.

When entering Console Mode for the first time the tablet launches automatically the nVidia Games application (package name: com.nvidia.tegrazone3), this is the application we need to replace with our modded version of LeanbackOnFire. To do this modifications we need apktool and jarsigner (it should be included with your Java SDK of choice). First we decompile the LeanbackOnFire apk:

apktool d LeanbackOnFire_v*-release.apk

Then, inside the decompressed app folder we edit the apktool.yml file and change:

renameManifestPackage: null to: renameManifestPackage: com.nvidia.tegrazone3

Repackage the apk:

apktool b LeanbackOnFire_v*-release

Remove the META-INF folder from inside the new apk and re-sign it:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore HEREGOESYOURKEYSTORE.keystore LeanbackOnFire_v*-release.apk alias_name

As a final step the only thing needed is to rename our modded version of LeanbackOnFire_v*-release.apk to TegraZone.apk and create a Magisk module to replace it at system/vendor/app/TegraZone/TegraZone.apk


Part III - Transfering files and Kodi tweaks

In my testing transfering files through MTP is slow (4mb/s) while using adb (and even adb wireless) is twice as fast, and since this will be most likely used to transfer somewhat large files we are making a bash script to do just that.
For simplicity to transfer a file using adb you’ll just do this: adb push -p /mnt/sourcefiles/myvideo.mkv /Download/destinationfolder
But if you are feeling fancy here’s a simple (and rough) bash script that connects to the pre-configured tablet’s hotspot using adb wirelessly and ask you which files to copy from and to using the custom-configured default directories.

#!/bin/bash
# needs bash, awk, ip, adb
#v0.1alpha

# is always nice to have options
Help()
{
   # Display Help
   echo "defaults: wireless transfer to device folder MULTIMEDIA from TORRENTS dir."
   echo
   echo "options:"
   echo "-w     Wireless connection. (default)"
   echo "-c     Wired connection."
   echo
   echo "-x     Copy from remote device."
   echo "-t     Copy to remote device. (default)"
   echo
   echo "-a     Alternate local dir."
   echo "-s     Alternate remote dir."
   echo
   echo "-h     Print this Help."
}

# change defaults to your needs
wireless_opt="true"
transferto_opt="true"
localdiralt_opt="false"
remotediralt_opt="false"
LOCALTDIR="/mnt/files/"
LOCALTDIRALT="/home/files/"
# example of a remote dir in a SDCARD, find the code for yours
REMOTETDIR="/storage/8DC8-1337/Download/"
REMOTETDIRALT="/storage/emulated/0/Download/"
SSID="SHIELD"
PWD="verystrongpassword"

# options
while getopts ":wcxthas" option; do
	case $option in
		w) wireless_opt="true";;
		c) wireless_opt="false";;
		x) transferto_opt="false";;
		t) transferto_opt="true";;
		h) Help
		   exit;;
		a) localdiralt_opt="true";;
		s) remotediralt_opt="true";;
		\?) echo "Error: Invalid option"
		    exit;;
	esac
done

# Alternate directories
if [ $localdiralt_opt = "true" ]; then
	LOCALTDIR="$LOCALTDIRALT"
	echo -e "Using Alternative local dir: \033[1m"$LOCALTDIR"\033[0m"
else
	echo -e "Using default local dir: \033[1m"$LOCALTDIR"\033[0m"
fi

if [ $remotediralt_opt = "true" ]; then
	REMOTETDIR="$REMOTETDIRALT"
	echo -e "Using Alternative local dir: \033[1m"$REMOTETDIR"\033[0m"
else
	echo -e "Using default remote dir: \033[1m"$REMOTETDIR"\033[0m"
fi

if [ $wireless_opt = "true" ]; then
	#wireless remote
	ADBREMOTEIP="192.168.43.1"
	
	#autoselect iface because 99% of the time you dont need to do it manually
	INTERFACES="$(ls /sys/class/net | paste -sd " ")"
	INTERFACES="$(echo $INTERFACES | awk '{print $NF}')"
	INTERFACE="$INTERFACES"
	
	#wireless interface
	#INTERFACES="$(ls /sys/class/net | paste -sd " ")"
	#echo -e "\033[1mAvailable Interfaces: \033[0m"$INTERFACES""
	#INTERFACES="$(echo $INTERFACES | awk '{print $NF}')"
	#read -r -e -p "Interface: " -i "$INTERFACES" INTERFACE
	
	#wireless pre-flush
	adb kill-server
	ip link set dev "$INTERFACE" down
	ip addr flush dev "$INTERFACE"	
	ip route flush dev "$INTERFACE"
	killall wpa_supplicant
	killall dhcpcd
	
	#wireless connect
	echo -e "\033[1mConnecting...\033[0m"
	ip link set dev "$INTERFACE" up
	sleep 1
	wpa_supplicant -D n180211,wext -i "$INTERFACE" -c <(wpa_passphrase "$SSID" "$PWD") -B
	sleep 1
	echo -e "\n\033[1mGetting IP...\033[0m"		
	ip addr add "192.168.43.19/24" dev "$INTERFACE"
	ip route add default via "192.168.43.1" dev "$INTERFACE"
	sleep 3
	
	#wireless start adb service and test connection
	adb connect "$ADBREMOTEIP"
else
	adb devices
	sleep 1
fi

#show space left in remote storage/sdcard
echo -e "\n\033[1mRemote Device:\033[0m"
adb shell df -h "$REMOTETDIR"
echo

if [ $transferto_opt = "true" ]; then
	#show folders with subfolres available for transfer
	echo -e "\033[1mCurrent remote dir ("$REMOTETDIR"):\033[0m"
	adb shell ls "$REMOTETDIR"
	echo -e "\033[1mCurrent local dir ("$LOCALTDIR"):\033[0m"
	ls "$LOCALTDIR"
	echo
	#transfer to
	for d in "$LOCALTDIR"* ; do
	echo "$d"
	read -r -e -p "Copy this file/directory to device? (Y/N/Exit): " -i "n" QUESTION
		if [[ $QUESTION =~ ^[Yy]$ ]]; then
			adb push -p "$d" "$REMOTETDIR"
			echo -e "copy complete."
		elif [[ $QUESTION =~ ^[Nn]$ ]];	then
			echo
		else [[ $QUESTION =~ ^[Ee]$ ]]
			break
		fi
	done
else
	#show remote folder with subfolders available for transfer
	echo -e "\033[1mCurrent local dir ("$LOCALTDIR"):\033[0m"
	ls "$LOCALTDIR"
	echo -e "\033[1mCurrent remote dir ("$REMOTETDIR"):\033[0m"
	IFS=$'\n'; set -f
	adb shell ls "$REMOTETDIR"
	echo
	#transfer from
	for d in  $(adb shell ls "$REMOTETDIR") ; do
	echo "$REMOTETDIR""$d"
	read -r -e -p "Copy this file/directory from device? (Y/N/Exit): " -i "n" QUESTION
		if [[ $QUESTION =~ ^[Yy]$ ]]; then
			adb pull -p "$REMOTETDIR""$d" "$LOCALTDIR"
			echo -e "copy complete."
		elif [[ $QUESTION =~ ^[Nn]$ ]];	then
			echo
		else [[ $QUESTION =~ ^[Ee]$ ]]
			break
		fi
	done
	unset IFS; set +f
fi

#Kill & Clean
if [ $wireless_opt = "true" ]; then
	echo
	echo -e "Cleaning..."
	adb kill-server
	ip link set dev "$INTERFACE" down
	ip addr flush dev "$INTERFACE"	
	ip route flush dev "$INTERFACE"
	killall wpa_supplicant
	killall dhcpcd
	sleep 1
	exit
else
	echo
	echo -e "Cleaning..."
	adb kill-server
	sleep 1
	exit
fi

NOTE 1: Android 9+ randomly generates the IP address that assigns to devices so this is not going to work wirelessly.

NOTE 2: In order to use adb wirelessly in android version 8 or less you need to have connected the tablet at least once in a wired fashion and authorized the device. After that’s done using a root shell (in the tablet) you need to execute the following commands in order:

stop adbd
setprop service.adb.tcp.port 5555
sleep 5
start adbd

This changes are lost on restart so we need a Magisk module to automatize it, for this at the root of the module we create a file service.sh with the previous commands in order.


After installing Kodi some changes are needed to play x265 files without stutter (mostly).
In general Kodi will play any type of media file you throw at it and it’ll do a great job but the tablet’s hardware can’t decode h265 very well (or at all in some cases) and using the software decoder in Kodi and most players will get you constant stutter at best. Luckily MX Player works great for the majority of the content ;) you may want to play using x265. Since I prefer to use Kodi to play everything and there’s an option to use an external player only for playback we’ll make use of it. To do this 2 files need to be added in the userdata folder usually located at: /android/data/org.xbmc.kodi/files/.kodi/

The first file is advancedsettings.xml (for improved overall caching):

<advancedsettings>
  <cache>
    <buffermode>1</buffermode>
    <memorysize>139460608</memorysize>
    <readfactor>20</readfactor>
  </cache>
</advancedsettings>

The second file is playercorefactory.xml (to use MXplayerPRO in this case for x265 only):

<playercorefactory>
    <players>
        <player name="MXPlayerPro" type="ExternalPlayer" audio="false" video="true">
            <filename>com.mxtech.videoplayer.pro</filename>
            <hidexbmc>true</hidexbmc>
            <playcountminimumtime>120</playcountminimumtime>
        </player>
    </players>
    <rules action="prepend">
        <rule video="true" player="dvdplayer">
            <rule filename=".*265.*|.*HEVC.*|.*hevc.*" player="MXPlayerPro"/>
        </rule>
    </rules>
</playercorefactory>

Final words / Acknowledgement

There’s so much more that can be done to make it even more compelling like adding toggles for bluetooth and hotspot to the launcher itself, installing RetroArch to emulate old games (I finished Mario64 doing just that!), etc.

Some of the things explained here are from memory because I did them a long time ago but waited too much to write them down so YMMV and some (or most) explanations are not step-by-step, this is by design.

I decided to write this in English because I don’t practice enough, next one may be in German.

Bonus! here’s a link to a zip file with all the modules and files referenced in this post: LINK