Skip to content

Instantly share code, notes, and snippets.

@joshenders
Last active June 21, 2026 02:34
Show Gist options
  • Select an option

  • Save joshenders/94eb01422da8db77aadcef82a618a92f to your computer and use it in GitHub Desktop.

Select an option

Save joshenders/94eb01422da8db77aadcef82a618a92f to your computer and use it in GitHub Desktop.
Force-landscape Sideload of a Portrait-Locked Play Store App on Meta Portal Plus (2021)

Force-landscape Sideload of a Portrait-Locked Play Store App on Meta Portal Plus (2021)

The 2021 Meta Portal Plus (codename cipher) has a panel fixed in landscape orientation with no rotation, only a tilt mechanism. Some apps are locked to portrait-only mode and these instructions outline how to download and modifify apps from the Google Play Store to force landscape mode.

Note

The Target app in this example is jp.or.nhk.nhkworld.tv (NHK WORLD TV), versionCode 21259021. These instructiosn were preapred on macOS, so sed uses the BSD -i '' form; substitute -i for GNU sed.

Dependencies

  • Requires adb (device authorized)
  • Android SDK build-tools on PATH (aapt2, zipalign, apksigner, keytool), apktool
  • Java 11+ (I prefer Amazon Coretto)
  • gplaydl
  • APKEditor.jar.

Download the app splits with gplaydl

Play Store apps ship as App Bundles, delivered as a base.apk plus config.* splits. gplaydl uses an anonymous token dispenser, so no Google account is required.

gplaydl download jp.or.nhk.nhkworld.tv

Only the "base" and "density split" are used below. The "language split" and the "Play Asset Delivery", -asset.apk are excluded; the asset pack carries a "null split name" and collides with the base on install.

Identify or create the signing keystore

keytool -list -keystore self.jks

Tip

To create a new keystore instead:

keytool -genkeypair -v -keystore self.jks -alias sideload \
  -keyalg RSA -keysize 2048 -validity 10000

Merge base and density split into a universal APK

Rebuilding only the base with apktool shifts resource IDs and breaks references into the untouched density split, which surfaces later as an InflateException. Merging first keeps the resource table consistent and clears the split requirement.

mkdir -p nhk_splits
cp jp.or.nhk.nhkworld.tv-21259021.apk \
   jp.or.nhk.nhkworld.tv-21259021-config.xxhdpi.apk \
   nhk_splits/
java -jar APKEditor.jar merge -i nhk_splits -o nhk-merged.apk

Decode the merged APK

apktool d nhk-merged.apk -o nhk

Force landscape in the manifest

Each activity declares android:screenOrientation="portrait". apktool decodes the orientation integer 1 to the string "portrait".

sed -i '' 's/android:screenOrientation="portrait"/android:screenOrientation="landscape"/g' nhk/AndroidManifest.xml
grep -c 'screenOrientation="portrait"' nhk/AndroidManifest.xml
0

Force landscape in the bytecode

The app also calls setRequestedOrientation() at runtime, overriding the manifest. Delete those invocations; the loaded orientation constant becomes a harmless unused register.

Tip

This matches only invocations, not a .method override definition.

grep -rl 'setRequestedOrientation(I)V' nhk/smali* | while read -r f; do
  sed -i '' '/invoke-.*setRequestedOrientation(I)V/d' "$f"
done

Rebuild, align, and sign

apktool b nhk -o nhk-patched.apk
zipalign -p 4 nhk-patched.apk nhk-aligned.apk
apksigner sign --ks self.jks --ks-key-alias sideload nhk-aligned.apk
apksigner verify --print-certs nhk-aligned.apk

Install

The device holds the Google-signed copy, whose certificate differs, so uninstall before installing the re-signed build.

adb uninstall jp.or.nhk.nhkworld.tv
adb install nhk-aligned.apk

Reference

Orientation values

Used in both the manifest screenOrientation attribute and the setRequestedOrientation(int) argument.

Value Constant
0 landscape
1 portrait
6 sensorLandscape
7 sensorPortrait
-1 / 0xffffffff unspecified

Tip

Surgical alternative to deletion in the bytecode step: change the constant feeding each call site from 0x1 to 0x0 rather than removing the invoke.


Troubleshooting

INSTALL_FAILED_INVALID_APK: Split null was defined multiple times

The Play Asset Delivery -asset.apk carries a null (empty) split name, identical to the base, so the install session sees two base APKs. Exclude it. Confirm split names with:

aapt2 dump xmltree --file AndroidManifest.xml <apk> | grep -m1 'A: split='

INSTALL_FAILED_MISSING_SPLIT

The base manifest requires a config split. Either install the density split alongside it, or use the merged universal APK above, which clears the requirement.

InflateException / Resources$NotFoundException after patching

Resource IDs in a separately rebuilt base no longer align with an untouched split. Merge the splits into one APK before patching so IDs stay internally consistent.

Diagnostics

Device ABI, density, and API level:

adb shell getprop ro.product.cpu.abilist
adb shell wm density
adb shell getprop ro.build.version.sdk

Capture a crash trace (survives process death):

adb logcat -c
adb logcat -b crash -d

Force-stop the app, including its services:

adb shell am force-stop jp.or.nhk.nhkworld.tv
adb shell pidof jp.or.nhk.nhkworld.tv

An empty pidof result means the package is fully stopped.

Note

Forcing orientation does not fix defects in the app's own code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment