Skip to content

Instantly share code, notes, and snippets.

@cunneen
Last active January 23, 2024 11:17
Show Gist options
  • Star 37 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save cunneen/1c0d9717f8ce5ea76900ba32fa037047 to your computer and use it in GitHub Desktop.
Save cunneen/1c0d9717f8ce5ea76900ba32fa037047 to your computer and use it in GitHub Desktop.
Install Open GApps In Android Emulator

Introduction

This works to install Open GApps into the Android Emulator, working around the issue where the system partition is too small.

With it, I can get Google Play installing into the emulator. Tested on KitKat (API 19), Lollipop (API 21) and Oreo (API 27).

It's tested on MacOS.

Instructions

  1. Ensure you have unzip, lzip, tar, emulator and adb in your PATH.
  2. From Android Studio (v3.0.1) create a new Android Virtual Device (AVD). For example:
    1. Tools -> Android -> AVD Manager
    2. Create Virtual Device...
    3. Phone -> Nexus 5
    4. x86 Images -> Lollipop -> Marshmallow (API 23 x86_64) (Download if necessary)
    5. Give the AVD a name ("Nexus 5 Marshmallow API 23 x86_64")
    6. Choose "Show Advanced Settings...". Give the AVD at least 1536MB Internal Storage, and 512MB SD Card
    7. Finish
  3. Download the appropriate Open GApps file from opengapps.org (x86_64 / 6.0 / micro)
  4. Save the open_gapps_install_script.sh into the same folder, and give it executable permission:
    chmod 755 open_gapps_install_script.sh
    
  5. Run the open_gapps_install_script.sh with the Open GApps file as the first argument; e.g.
    open_gapps_install_script.sh open_gapps-x86_64-6.0-micro-20180123.zip
    
  6. When prompted for an AVD, copy the appropriate AVD name and paste it. Press Enter.
  7. Wait for the emulator to boot fully, then press Enter.
  8. Once the script tells you to "Restart android in your emulator", hold down the virtual power button to shutdown.
  9. Manually start the emulator again (press the 'play' button in Android Studio's AVD Manager).
  10. In the emulator, open the Google app and sign in with your google account.
  11. Shut down the emulator and start it again.

A sample transcript follows.

Sample Transcript

$ ./open_gapps_install_script.sh open_gapps-x86_64-6.0-micro-20180123.zip 
Archive:  open_gapps-x86_64-6.0-micro-20180123.zip
signed by SignApk
 extracting: /tmp/gapps_installer/Core/configupdater-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/defaultetc-common.tar.lz  
 extracting: /tmp/gapps_installer/Core/defaultframework-common.tar.lz  
 extracting: /tmp/gapps_installer/Core/gmscore-x86_64.tar.lz  
 extracting: /tmp/gapps_installer/Core/googlebackuptransport-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/googlecontactssync-all.tar  
 extracting: /tmp/gapps_installer/Core/googlefeedback-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/googleonetimeinitializer-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/googlepartnersetup-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/gsfcore-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/gsflogin-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/setupwizarddefault-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/setupwizardtablet-all.tar.lz  
 extracting: /tmp/gapps_installer/Core/vending-x86_64.tar.lz  
Which AVD?
Nexus_5X_Oreo_8.1_API_27
Nexus_5_KitKat_API_19_32-bit_x86
Nexus_5_Lollipop_API_22_32-bit
Nexus_5_Marshmallow_API_23_x86_64

>Nexus_5_Marshmallow_API_23_x86_64

determining location of system.img file for Nexus_5_Marshmallow_API_23_x86_64 .../Users/mikecunneen/Library/Android/sdk/system-images/android-23/google_apis/x86_64/
copying, checking and resizing system.img for Nexus_5_Marshmallow_API_23_x86_64
copying encryptionkey.img ...
e2fsck 1.42.13 (17-May-2015)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
system: 1958/98304 files (0.0% non-contiguous), 358163/393216 blocks
resize2fs 1.42.13 (17-May-2015)
Resizing the filesystem on /Users/mikecunneen/.android/avd/Nexus_5_Marshmallow_API_23_x86_64.avd/system.img to 524288 (4k) blocks.
The filesystem on /Users/mikecunneen/.android/avd/Nexus_5_Marshmallow_API_23_x86_64.avd/system.img is now 524288 (4k) blocks long.

starting Nexus_5_Marshmallow_API_23_x86_64

Press enter when the emulator has booted...

adbd is already running as root
remount succeeded
etc/: 11 files pushed. 3.8 MB/s (58819 bytes in 0.015s)
framework/: 3 files pushed. 34.8 MB/s (212217 bytes in 0.006s)
app/: 1 file pushed. 81.9 MB/s (284795 bytes in 0.003s)
priv-app/: 9 files pushed. 188.1 MB/s (93280033 bytes in 0.473s)
restart android in your emulator
done.

#!/usr/bin/env bash
ZIPFILE=${1}
USAGESTRING="Usage: ${0} open_gapps_zip_file.zip";
TEMPDIR=/tmp/gapps_installer
if [ $# -lt 1 ]; then
echo ${USAGESTRING};
exit 1;
fi
if [ ! -f ${ZIPFILE} ]; then
echo ${USAGESTRING};
exit 2;
fi
if [ ! ${ANDROID_HOME} ]; then
echo "ANDROID_HOME is not set"
exit 3;
fi
if [ ! -d ${ANDROID_HOME} ]; then
echo "ANDROID_HOME is not valid"
exit 4;
fi
rm -rf ${TEMPDIR} && mkdir -p ${TEMPDIR}
unzip ${ZIPFILE} 'Core/*' -d ${TEMPDIR}
cd ${TEMPDIR}
rm Core/setup*
lzip -d Core/*.lz
for f in $(ls Core/*.tar); do
tar -x --strip-components 2 -f $f
done
echo "Which AVD?"
emulator -list-avds
echo -en "\n>"
read AVD
echo -en "\ndetermining location of system.img file for ${AVD} ..."
IMAGE_SYSDIR=`grep "image.sysdir.1" ${HOME}/.android/avd/${AVD}.avd/config.ini | cut -f2 -d"="`
echo "${ANDROID_HOME}/${IMAGE_SYSDIR}"
echo "copying, checking and resizing system.img for ${AVD}"
cp "${ANDROID_HOME}/${IMAGE_SYSDIR}/system.img" "${HOME}/.android/avd/${AVD}.avd/system.img"
if [ -f "${ANDROID_HOME}/${IMAGE_SYSDIR}/encryptionkey.img" ]; then
echo "copying encryptionkey.img ..."
cp "${ANDROID_HOME}/${IMAGE_SYSDIR}/encryptionkey.img" "${HOME}/.android/avd/${AVD}.avd/encryptionkey.img"
fi
"${ANDROID_HOME}/emulator/bin64/e2fsck" -f "${HOME}/.android/avd/${AVD}.avd/system.img"
"${ANDROID_HOME}/emulator/bin64/resize2fs" "${HOME}/.android/avd/${AVD}.avd/system.img" 3072M
echo "starting ${AVD}"
${ANDROID_HOME}/tools/emulator -netdelay none -netspeed full -avd ${AVD} -partition-size 1024 -writable-system > /dev/null 2>&1 &
read -rsp $'\nPress enter when the emulator has booted...\n\n'
adb root
adb remount
adb push etc /system
adb push framework /system
adb push app /system
adb push priv-app /system
sleep 5
# adb reboot system
# adb -e emu kill
echo "Restart android in your emulator"
echo "done."
@cunneen
Copy link
Author

cunneen commented Oct 18, 2021

@alkaris2 Best of luck debugging your issues. Let us know what you find.

@tenzap
Copy link

tenzap commented Jan 10, 2022

@cunneen, could you please apply this patch?
It adds support for when the system.img is not a bare ext fs, but a disk image containing a GPT with an ext fs inside.
This will also fixe the issue encountered by @alkaris2

--- open_gapps_install_script.sh.1      2022-01-10 09:48:02.947415100 +0100
+++ open_gapps_install_script.sh        2022-01-10 12:18:20.141186300 +0100
@@ -33,7 +33,7 @@
 done
 
 echo "Which AVD?"
-emulator -list-avds
+${ANDROID_HOME}/emulator/emulator -list-avds
 echo -en "\n>"
 read AVD
 
@@ -47,12 +47,30 @@
   echo "copying encryptionkey.img ..."
   cp "${ANDROID_HOME}/${IMAGE_SYSDIR}/encryptionkey.img" "${HOME}/.android/avd/${AVD}.avd/encryptionkey.img"
 fi
-"${ANDROID_HOME}/emulator/bin64/e2fsck" -f "${HOME}/.android/avd/${AVD}.avd/system.img"
-"${ANDROID_HOME}/emulator/bin64/resize2fs" "${HOME}/.android/avd/${AVD}.avd/system.img" 3072M
+
+if file -b "${HOME}/.android/avd/${AVD}.avd/system.img" | grep "DOS/MBR boot sector"; then
+    # The system.img is not ext2/ext4 but a DOS/MBR boot sector which contains an ext2/3/4 fs
+    fallocate -l 3G "${HOME}/.android/avd/${AVD}.avd/system.img"
+    #PART_UUID=$(/usr/sbin/sgdisk -i 1 "${HOME}/.android/avd/${AVD}.avd/system.img" | grep "unique GUID" | cut -d ' ' -f4)
+    /usr/sbin/sgdisk -d 1 "${HOME}/.android/avd/${AVD}.avd/system.img"
+    /usr/sbin/sgdisk -n 1:0:0 "${HOME}/.android/avd/${AVD}.avd/system.img"
+    /usr/sbin/sgdisk -c 1:system "${HOME}/.android/avd/${AVD}.avd/system.img"
+    #/usr/sbin/sgdisk -u 1:$PART_UUID "${HOME}/.android/avd/${AVD}.avd/system.img"
+    echo "Root rights needed to mount system.img & resize ext2fs. Please enter root password"
+    PART=$(su -c "/usr/sbin/kpartx -a -v \"${HOME}/.android/avd/${AVD}.avd/system.img\"" | cut -f 3 -d ' ')
+    SYS_EXT_PART_PATH="/dev/mapper/$PART"
+    su -c "set -x;
+        '${ANDROID_HOME}/emulator/bin64/e2fsck' -f '$SYS_EXT_PART_PATH' ;
+        '${ANDROID_HOME}/emulator/bin64/resize2fs' '$SYS_EXT_PART_PATH' ;
+        /usr/sbin/kpartx -d -v '${HOME}/.android/avd/${AVD}.avd/system.img'"
+else
+    "${ANDROID_HOME}/emulator/bin64/e2fsck" -f "${HOME}/.android/avd/${AVD}.avd/system.img"
+    "${ANDROID_HOME}/emulator/bin64/resize2fs" "${HOME}/.android/avd/${AVD}.avd/system.img" 3072M
+fi
 
 echo "starting ${AVD}"
 
-${ANDROID_HOME}/tools/emulator -netdelay none -netspeed full -avd ${AVD} -partition-size 1024 -writable-system > /dev/null 2>&1 &
+${ANDROID_HOME}/emulator/emulator -netdelay none -netspeed full -avd ${AVD} -partition-size 1024 -writable-system > /dev/null 2>&1 &
 
 read -rsp $'\nPress enter when the emulator has booted...\n\n'
 

BTW, I also had to remove the system.img.qcow2 file so that it could boot in the emulator, but that may be because of another reason.

@tenzap
Copy link

tenzap commented Jan 10, 2022

I figured out that we have to start the emulator by command line in the .sh file, otherwise, it will remove Google Play
${ANDROID_HOME}/tools/emulator -netdelay none -netspeed full -avd ${AVD} -partition-size 1024 -writable-system > /dev/null 2>&1 &

Same thing here. It looks like -writable-system is mandatory to have google play :/ (lineageos pie, with open gapps pico)

@tenzap
Copy link

tenzap commented Jan 10, 2022

Here is another method that uses the OpenGApps installer script.

It doesn't need to launch the emulator and copy the files through adb, no need to use -writable-system.

You may want to increase the system size to 3500MiB or even more if you install the stock package.
This was tested with a LineageOS pie x86 image & OpenGApps 9.0 pico & stock on x86 (host system: debian 11 (bullseye))

The google play works fine even without -writable-system (In case it says your device is not protected, you are likely to run a custom ROM. For this, you need to register your device, then wait a few minutes. If it is still not certified, try clearing Google Play Store's cache and/or Google Play services's cache).

Here are the steps. Most commands need to be run as root.

export OPENGAPPZIP=open_gapps-x86-9.0-stock-20211217.zip
export OPENGAPPZIP_LOCATION=$HOME/Downloads
export RTDIR=/mnt/mounted_avd
export AVD_DIR=$HOME/.android/avd/${AVD}.avd

# Mount the images
mkdir -p $RTDIR

PART_SYSTEM=$(kpartx -a -v $AVD_DIR/system.img | cut -f 3 -d ' ')
mount /dev/mapper/$PART_SYSTEM $RTDIR/

PART_VENDOR=$(kpartx -a -v $AVD_DIR/vendor.img | cut -f 3 -d ' ')
mount /dev/mapper/$PART_VENDOR $RTDIR/vendor

mount $AVD_DIR/cache.img $RTDIR/cache

#it doesn's seem userdata.img is used at first boot, so skip.
#mke2fs $AVD_DIR/userdata.img
#mount $AVD_DIR/userdata.img $RTDIR/data

mount --bind /proc/ $RTDIR/proc

# Mount dir holding the zip (so that it doesn't take space on "/system"
mkdir -p $RTDIR/tmp_opengappzip
mount --bind $OPENGAPPZIP_LOCATION $RTDIR/tmp_opengappzip

# Unpack busybox
unzip -o $RTDIR/tmp_opengappzip/$OPENGAPPZIP busybox-x86 -d $RTDIR/tmp
mkdir -p $RTDIR/tmp/bin
$RTDIR/tmp/busybox-x86 --install $RTDIR/tmp/bin

# Change pid_max to a value that suits the chroot
PID_MAX_ORIG=$(cat /proc/sys/kernel/pid_max)
echo "Original PID_Max was $PID_MAX_ORIG"
echo "Run 'echo $PID_MAX_ORIG > /proc/sys/kernel/pid_max' to revert to it"
echo 65500 > /proc/sys/kernel/pid_max


# Enter the chroot & run ash shell
PATH=/tmp/bin:/system/bin:$PATH chroot $RTDIR /tmp/bin/ash

# Set variables needed by the installer.sh script (when flashed through TWRP
# they are set by META-INF/com/google/android/update-binary)
export TMP="/tmp"
case "$(uname -m)" in
  *86*) export BINARCH="x86";;  # e.g. Zenfone is i686
  *ar*) export BINARCH="arm";; # i.e. armv7l and aarch64
esac
export OPENGAZIP=/tmp_opengappzip/$OPENGAPPZIP

# Extract necessary files
# Copied from META-INF/com/google/android/update-binary (of the OPENGAPPZIP)
for f in app_densities.txt app_sizes.txt bkup_tail.sh gapps-remove.txt g.prop installer.sh busybox-x86 tar-x86 unzip-x86 zip-x86; do
  unzip -o "$OPENGAZIP" "$f" -d "$TMP"
done

Now, patch the installer script (located in $RTDIR/$TMP)

--- a/installer.sh
+++ b/installer.sh
@@ -986,11 +986,10 @@
 #                                                  Pre-define Helper Functions
 get_file_prop() { grep -m1 "^$2=" "$1" | cut -d= -f2-; }
 
-set_progress() { echo "set_progress $1" >> $OUTFD; }
+set_progress() { awk -v VAL=$1 'BEGIN {print "...Progress: ", (VAL * 100),"%" }' ; }
 
 ui_print() {
+  echo "$1"
-  echo "ui_print $1
-    ui_print" >> $OUTFD
 }
 
 find_slot() {
@@ -1187,27 +1188,27 @@
 [ "$ANDROID_ROOT" ] || ANDROID_ROOT=/system
 
 # emulators can only flash booted and may need /system (on legacy images), or / (on system-as-root images), remounted rw
-if ! $BOOTMODE; then
-  mount -o bind /dev/urandom /dev/random
-  if [ -L /etc ]; then
-    setup_mountpoint /etc
-    cp -af /etc_link/* /etc
-    sed -i 's; / ; /system_root ;' /etc/fstab
-  fi
-  umount_all
-  mount_all
-fi
-if [ -d /dev/block/mapper ]; then
-  for block in system vendor product system_ext; do
-    for slot in "" _a _b; do
-      blockdev --setrw /dev/block/mapper/$block$slot 2>/dev/null
-    done
-  done
-fi
-mount -o rw,remount -t auto /system || mount -o rw,remount -t auto /
-(mount -o rw,remount -t auto /vendor
-mount -o rw,remount -t auto /product
-mount -o rw,remount -t auto /system_ext) 2>/dev/null
+# if ! $BOOTMODE; then
+#   mount -o bind /dev/urandom /dev/random
+#   if [ -L /etc ]; then
+#     setup_mountpoint /etc
+#     cp -af /etc_link/* /etc
+#     sed -i 's; / ; /system_root ;' /etc/fstab
+#   fi
+#   umount_all
+#   mount_all
+# fi
+# if [ -d /dev/block/mapper ]; then
+#   for block in system vendor product system_ext; do
+#     for slot in "" _a _b; do
+#       blockdev --setrw /dev/block/mapper/$block$slot 2>/dev/null
+#     done
+#   done
+# fi
+# mount -o rw,remount -t auto /system || mount -o rw,remount -t auto /
+# (mount -o rw,remount -t auto /vendor
+# mount -o rw,remount -t auto /product
+# mount -o rw,remount -t auto /system_ext) 2>/dev/null

 ui_print " "
 
@@ -2603,7 +2604,7 @@
 # Read and save system partition size details
 df=$(df -k /system | tail -n 1)
 case $df in
-  /dev/block/*) df=$(echo "$df" | awk '{ print substr($0, index($0,$2)) }');;
+  /dev/*) df=$(echo "$df" | awk '{ print substr($0, index($0,$2)) }');;
 esac
 total_system_size_kb=$(echo "$df" | awk '{ print $1 }')
 used_system_size_kb=$(echo "$df" | awk '{ print $2 }')

Continue with the script

# Run the installer.sh script
cd /
ash /tmp/installer.sh

# Leave chroot
exit

# Make a copy of the /data partition because we can't load it into the chroot
mkdir -p /tmp/my_avd_data
cp -a $RTDIR/data/* /tmp/my_avd_data/

# Unmount the images & cleanup
umount $RTDIR/tmp_opengappzip
umount $RTDIR/cache
umount $RTDIR/vendor
#umount $RTDIR/data
umount $RTDIR/proc
umount $RTDIR
kpartx -d -v $AVD_DIR/vendor.img
kpartx -d -v $AVD_DIR/system.img

# Restore original pid_max
echo $PID_MAX_ORIG > /proc/sys/kernel/pid_max

# Review the contents of the data partition, you may need to copy
# things over through adb once the emulator is loaded (if this works)

# adb push /tmp/my_avd_data/* /data
# rm -r /tmp/my_avd_data/

Now you can boot emulator through AVD.

@tenzap
Copy link

tenzap commented Jan 21, 2022

Those searching for a script working for system-images up to Android 12 / API 31 can look at my fork.
Thanks @cunneen for your work and showing the way.

@cunneen
Copy link
Author

cunneen commented Mar 22, 2022

I only just saw this, thanks @tenzap for this!

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