#!/system/bin/sh
# chmod -R 755 /system/etc/init.d /system/su.d /su/su.d /sbin/supersu/su.d /magisk/.core/post-fs-data.d /sbin/.core/img/.core/post-fs-data.d /data/adb/post-fs-data.d
#
# ROM GApps Auto-Integration
# osm0sis @ xda-developers
debugging=0
dryrun=0
logbuff() { logbuff+="$($*)"; }
writable() { touch $1/tmpfile 2>/dev/null; ret=$?; rm $1/tmpfile 2>/dev/null; echo $ret; }
# figure out if the next integration will likely have enough free space in /system
freespace-estimate() {
free=$(df -Ph /system 2>>$out | tail -n 1 | awk '{ print $4 }');
case $free in *K) free=0;; *M) free=`echo $free | cut -dM -f1 | cut -d. -f1`;; *G) free=$((`echo $free | cut -dG -f1 | cut -d. -f1` * 1024));; esac;
libsize=0; buffer=4;
if [ "$nozipbins" ]; then
for entry in $(unzip -l $1 lib/* 2>>$out | tail -n +4 | awk '{ print $1 }'); do
case $entry in
---------) ;; # skip top line displayed in some unzip list edge cases
--------) break;;
*) libsize=$((libsize + entry));;
esac;
done;
if [ $libsize -lt 1048576 ]; then
libsize=1;
else
libsize=$((libsize / 1048576));
fi;
else
buffer=8;
fi;
datapksize=$(du -m $1 | awk '{ print $1 }');
sysapksize=$(du -m $2 | awk '{ print $1 }');
need=$((datapksize + libsize - sysapksize + buffer));
echo "free($free) ?> need($need) = datapksize($datapksize) + libsize($libsize) - sysapksize($sysapksize) + buffer($buffer)" >> $out;
[ $free -gt $need ]; echo $?;
}
# determine if final replacement APK and library sizes will fit in /system
freespace-precise() {
free=$(df -Ph /system 2>>$out | tail -n 1 | awk '{ print $4 }');
case $free in *K) free=0;; *M) free=`echo $free | cut -dM -f1 | cut -d. -f1`;; *G) free=$((`echo $free | cut -dG -f1 | cut -d. -f1` * 1024));; esac;
oldfiles=$(du -mc $1 2>>$out | tail -n1 | awk '{ print $1 }');
newfiles=$(du -mc $2 2>>$out | tail -n1 | awk '{ print $1 }');
echo "free($free) + oldfiles($oldfiles) = total_space($((free + oldfiles))) ?> newfiles($newfiles)" >> $out;
[ $((free + oldfiles)) -gt $newfiles ]; echo $?;
}
# add support for Magisk by altering the actual mounted system partition
[ -d /magisk/.core/mirror ] && magisk=/magisk/.core/mirror;
[ -d /sbin/.core/mirror ] && magisk=/sbin/.core/mirror;
[ -d /sbin/.magisk/mirror ] && magisk=/sbin/.magisk/mirror;
[ -e /dev/*/.magisk/mirror ] && magisk=$(echo /dev/*/.magisk/mirror);
# alter Magisk's post-fs-data.d PATH to prioritize actual mounted system over the included busybox
[ "$magisk" ] && export PATH="$magisk/system/bin:$magisk/system/xbin:$PATH";
# group all commands and allow redirection of output for debugging
out=/dev/null;
[ "$debugging" == 1 ] && out=/data/local/tmp/debug-gappsint.log;
(
# run in an asynchronous subshell to not impede the boot with su.d/post-fs-data.d
{
# loop counter timeout limits for device writability
sdlimit=900; syslimit=120;
# define working paths
gtmp=/data/local/tmp/gapp;
gbak=/data/local/tmp/gxml-bak;
log=/sdcard/gapps-integrator.log;
# create log/change filedate immediately to indicate script is active once /sdcard is available
counter1=0;
until [ "$(writable /sdcard; touch $log 2>/dev/null)" == 0 -o "$counter1" -gt "$sdlimit" ]; do
sleep 1;
counter1=$((counter1 + 1));
done&
if [ "$dryrun" == 0 ]; then
# wait for /system to become remountable/writable, abort if it does not
counter2=0;
until [ "$(mount --help 2>/dev/null)" ] && [ "$(mount -o rw,remount -t auto $magisk/system >>$out; writable $magisk/system; mount -o ro,remount -t auto $magisk/system >>$out)" == 0 -o "$counter2" -gt "$syslimit" ]; do
sleep 1;
counter2=$((counter2 + 1));
done;
if [ "$counter2" -gt "$syslimit" ]; then
logbuff echo -ne "\n\x20* Fatal error: $magisk/system did not become remountable/writable during boot\n\n";
abort=1;
fi;
fi;
# log earliest accurate processing start timestamp once date stops returning epoch in early boot
until [ "$(date 2>/dev/null)" ] && [ ! "$(date | grep '1970')" ] && [ ! "$(date | grep '1999')" ] && [ ! "$(date | grep '2000')" ]; do
sleep 1;
done;
logbuff echo -n `date`;
echo -ne "\n$(date) ";
if [ "$dryrun" == 1 ]; then
logbuff echo -ne "\n\x20* Dry run mode: No actual changes will be made to $magisk/system or /data";
echo -ne "--- Dry run mode! ";
fi;
# test for and try to ensure a working basic script environment, often broken in Marshmallow (AOSP)
for i in awk basename cut dirname sed tail unzip which; do
if [ ! "$(which $i)" ]; then
logbuff echo -ne "\n\x20* Missing script environment element detected: $i";
abort=1;
fi;
done;
if [ ! "$(echo testing | sed 's/test/pass/')" == "passing" ]; then
logbuff echo -ne "\n\x20* Broken script environment element detected: sed";
if [ "$(echo testing | busybox sed 's/test/pass/')" == "passing" ]; then
logbuff echo -ne "\n\x20* -- using busybox sed instead";
bb=busybox;
else
abort=1;
fi;
fi;
touch /data/local/testfile1; touch /data/local/testfile2;
if [ ! "$(chmod 666 /data/local/testfile*; echo $?)" == 0 ]; then
logbuff echo -ne "\n\x20* Broken script environment element detected: chmod";
if [ "$(busybox chmod 666 /data/local/testfile*; echo $?)" == 0 ]; then
logbuff echo -ne "\n\x20* -- using busybox chmod instead";
bb=busybox;
else
abort=1;
fi;
fi;
rm -f /data/local/testfile*;
# check for zip and zipalign binaries to determine integration method
for i in zip zipalign; do
if [ ! "$(which $i)" ]; then
logbuff echo -ne "\n\x20* Missing binary for primary integration method: $i";
nozipbins=1;
fi;
done;
[ "$nozipbins" ] && logbuff echo -ne "\n\x20* -- using fallback method instead";
# get SDK version to perform different actions due to /data/app layout changes
sdkver=`getprop ro.build.version.sdk`;
[ ! "$sdkver" ] && sdkver=$(grep '^ro.build.version.sdk' $magisk/system/build.prop | cut -d= -f2);
# find new unintegrated Google Apps APKs in /data
for i in $(ls /data/app/ | grep -E '^com.android|^com.chrome|^com.google.android|^com.google.earth|^com.motorola'); do
# skip all if script environment is broken
[ "$abort" ] && break;
# find equivalent /system APK name and only process if it exists
xml=/data/system/packages.xml;
package=`echo $i | rev | cut -d- -f2- | rev`;
sysapk=$(grep "updated-package name=\"$package\"" $xml | grep -o 'codePath=.*$' | cut -d\" -f2);
logbuff echo -ne "\n/data/app/$i $sysapk";
if [ "$sysapk" ]; then
# compare /data and /system APK versions and only integrate if /data is newer (necessary on Lollipop and above)
datver=$(grep "codePath=\"/data/app/$i\"" $xml | grep -o 'version=.*$' | cut -d\" -f2);
sysver=$(grep "codePath=\"$sysapk\"" $xml | grep -o 'version=.*$' | cut -d\" -f2);
if [ "$datver" -gt "$sysver" ]; then
logbuff echo -ne "\x20($datver > $sysver)";
echo -e "\n$i $magisk$sysapk";
rm -rf $gtmp;
mkdir -p $gtmp;
# KitKat (and below) support
if [ "$sdkver" -le 20 ]; then
datapk=/data/app/$i;
# exclude APKs that cause breakage after integration
case $package in
com.google.android.apps.messaging|com.google.android.apps.photos|com.google.android.apps.plus|com.google.android.street|com.google.android.talk|com.google.android.youtube)
logbuff echo -ne "\x20!";
continue;;
esac;
# skip current APK if it fails a free space check
if [ "$(freespace-estimate $datapk $magisk$sysapk)" != 0 ]; then
logbuff echo -ne "\x20*";
continue;
fi;
# remove libraries from copied APK and zipalign if the binaries exist
if [ ! "$nozipbins" ]; then
cp -fp $datapk $gtmp/preopt-$i;
zip -d $gtmp/preopt-$i lib/*/*;
zipalign 4 $gtmp/preopt-$i $gtmp/$i;
datapk=$gtmp/$i;
fi;
# extract and force copy libraries to /system, abort if fails final total space check
unzip /data/app/$i -d $gtmp lib/*;
for j in $(ls $gtmp/lib/*/); do
liblist="$liblist $magisk/system/lib/$j";
done;
if [ "$(freespace-precise "$magisk$sysapk $liblist" "$datapk $gtmp/lib/*/*" )" != 0 ]; then
logbuff echo -ne "\x20**";
continue;
fi;
$bb chmod 644 $gtmp/lib/*/*;
if [ "$dryrun" == 0 ]; then
mount -o rw,remount -t auto $magisk/system;
rm -f $magisk$sysapk;
cp -fp $gtmp/lib/*/* $magisk/system/lib/;
# overwrite /system APK with new /data APK then fix permissions
cp -f $datapk $magisk$sysapk;
chown root.root $magisk$sysapk;
$bb chmod 644 $magisk$sysapk;
fi;
# Lollipop support
elif [ "$sdkver" -le 22 ]; then
datapk=/data/app/$i/base.apk;
sysname=`basename $sysapk`;
# save time on boots after APK work has been completed but apps are awaiting optimization
if [ ! -f /data/app/$i/integrated ]; then
# skip current APK if it fails a free space check
if [ "$(freespace-estimate $datapk $magisk$sysapk/$sysname.apk)" != 0 ]; then
logbuff echo -ne "\x20*";
continue;
fi;
# remove libraries from copied APK and zipalign if the binaries exist
if [ ! "$nozipbins" ]; then
# workaround for Chrome not playing by the usual rules (per usual)
case $package in
com.android.chrome|com.chrome.*)
if [ "$dryrun" == 0 ]; then
mount -o rw,remount -t auto $magisk/system;
rm -f $magisk$sysapk/lib/*/libchrome.so;
fi;;
*)
cp -fp $datapk $gtmp/preopt-$i.apk;
zip -d $gtmp/preopt-$i.apk lib/*/*;
zipalign 4 $gtmp/preopt-$i.apk $gtmp/$i.apk;
datapk=$gtmp/$i.apk;;
esac;
fi; # end nozipbins test
# abort if fails final total space check
if [ "$(freespace-precise "$magisk$sysapk" "$datapk /data/app/$i/lib" )" != 0 ]; then
logbuff echo -ne "\x20**";
continue;
fi;
if [ "$dryrun" == 0 ]; then
# force copy libraries to /system respecting symlinks then clean up empty files
mount -o rw,remount -t auto $magisk/system;
rm -f $magisk$sysapk/$sysname.apk;
cp -RLf /data/app/$i/lib $magisk$sysapk;
for j in `ls $magisk$sysapk/lib/*/*`; do
[ ! -s $j ] && rm -f $j;
done;
# overwrite /system APK with new /data APK then fix permissions
cp -fp $datapk $magisk$sysapk/$sysname.apk;
chown -R root.root $magisk$sysapk;
$bb chmod -R 755 $magisk$sysapk/lib;
$bb chmod 644 $magisk$sysapk/$sysname.apk $magisk$sysapk/lib/*/*;
# flag for cleanup on reboot following optimization
touch /data/app/$i/integrated;
fi;
fi; # end integrated test
if [ "$dryrun" == 0 ]; then
# remove packages.xml entry for /data APK and ensure proper ownership/permissions
md5sum $xml;
if [ "$(grep "')" ]; then
$bb sed -i "//d" $xml;
fi;
chown system:system $xml;
$bb chmod 660 $xml;
md5sum $xml;
fi;
# Marshmallow (and above) support
elif [ "$sdkver" -ge 23 ]; then
datapk=/data/app/$i/base.apk;
sysname=`basename $sysapk`;
# save time on boots after APK work has been completed but apps are awaiting optimization
if [ ! -f /data/app/$i/integrated ]; then
# skip current APK if it fails a free space check
if [ "$(freespace-estimate $datapk $magisk$sysapk/$sysname.apk)" != 0 ]; then
logbuff echo -ne "\x20*";
continue;
fi;
# decompress libraries within copied APK and zipalign if the binaries exist (maintains proper Marshmallow and above structure)
if [ ! "$nozipbins" ]; then
unzip $datapk -d $gtmp lib/*;
cp -fp $datapk $gtmp/preopt-$i.apk;
zip -d $gtmp/preopt-$i.apk lib/*/*;
cd $gtmp;
zip -r -0 -D $gtmp/preopt-$i.apk lib/;
zipalign -p 4 $gtmp/preopt-$i.apk $gtmp/$i.apk;
datapk=$gtmp/$i.apk;
# abort if fails final total space check
datoat=/data/app/$i/oat;
[ -d $magisk$sysapk/oat ] || unset datoat;
if [ "$(freespace-precise "$magisk$sysapk/$sysname.apk $magisk$sysapk/oat $magisk$sysapk/lib" "$datapk $datoat" )" != 0 ]; then
logbuff echo -ne "\x20**";
continue;
fi;
if [ "$dryrun" == 0 ]; then
# remove any unpacked libraries in the /system APK directory since they are no longer needed
mount -o rw,remount -t auto $magisk/system;
[ -d $magisk$sysapk/lib ] && rm -rf $magisk$sysapk/lib;
fi;
else
# otherwise force copy libraries to /system respecting symlinks then clean up empty files, abort if fails final total space check
datoat=/data/app/$i/oat;
[ -d $magisk$sysapk/oat ] || unset datoat;
if [ "$(freespace-precise "$magisk$sysapk/$sysname.apk $magisk$sysapk/oat $magisk$sysapk/lib" "$datapk $datoat /data/app/$i/lib" )" != 0 ]; then
logbuff echo -ne "\x20**";
continue;
fi;
if [ -e /data/app/$i/lib/* -a "$dryrun" == 0 ]; then
mount -o rw,remount -t auto $magisk/system;
cp -RLf /data/app/$i/lib $magisk$sysapk;
for j in `ls $magisk$sysapk/lib/*/*`; do
[ ! -s $j ] && rm -f $j;
done;
fi;
fi; # end nozipbins test
if [ "$dryrun" == 0 ]; then
# if necessary force copy APK odex file to /system
mount -o rw,remount -t auto $magisk/system;
if [ -d $magisk$sysapk/oat ]; then
cp -fp /data/app/$i/oat $magisk$sysapk;
cd $magisk$sysapk/oat/*/;
mv base.odex $sysname.odex;
fi;
# overwrite /system APK with new /data APK then fix permissions
cp -fp $datapk $magisk$sysapk/$sysname.apk;
chown -R root.root $magisk$sysapk;
$bb chmod -R 755 $magisk$sysapk/lib $magisk$sysapk/oat;
$bb chmod 644 $magisk$sysapk/$sysname.apk $magisk$sysapk/lib/*/* $magisk$sysapk/oat/*/*;
# flag for cleanup on reboot following optimization
touch /data/app/$i/integrated;
fi;
fi; # end integrated test
if [ "$dryrun" == 0 ]; then
# remove packages.xml entry for /data APK and ensure proper ownership/permissions
md5sum $xml;
if [ "$(grep "')" ]; then
$bb sed -i "//d" $xml;
fi;
chown system:system $xml;
$bb chmod 660 $xml;
md5sum $xml;
# workaround for APKs that cause breakage after integration by temporarily removing runtime-permissions.xml to force rebuild
case $package in
com.google.android.calendar|com.google.android.inputmethod.*|com.google.android.play.games|com.google.android.tts|com.google.android.webview)
logbuff echo -ne "\x20~";
if [ ! -d $gbak ]; then
mkdir -p $gbak;
for rtperm in /data/system/users/*/runtime-permissions.xml; do
if [ -e "$rtperm" ]; then
user=`basename $(dirname $rtperm)`;
cp -fp $rtperm $gbak/$user;
rm -f $rtperm;
fi;
done;
fi;;
esac;
fi;
fi; # end sdkver test
if [ "$dryrun" == 0 ]; then
mount -o ro,remount -t auto $magisk/system;
fi;
else
if [ "$dryrun" == 0 ]; then
# clean up any duplicate packages that are not the current active copy
rm -rf /data/app/$i;
fi;
fi; # end datver+sysver test
elif [ -f /data/app/$i/integrated ]; then
# clean up to mimic pre-Lollipop (AOSP) behavior
rm -rf /data/app/$package-*;
fi; # end sysapk test
done; # end find unintegrated loop
# global cleanups required on Lollipop (and above)
if [ "$sdkver" -ge 21 -a "$dryrun" == 0 ]; then
# fix /system/lib permissions to ensure libs copied via symlink are correct
mount -o rw,remount -t auto $magisk/system;
[ "$magisk" ] && mount -o rw,remount -t auto /system;
chown root.root $magisk/system/lib/*.so;
$bb chmod 644 $magisk/system/lib/*.so;
mount -o ro,remount -t auto $magisk/system;
[ "$magisk" ] && mount -o ro,remount -t auto /system;
fi;
rm -rf $gtmp;
# write buffered log once /sdcard is available, fallback to a known working location if this fails
counter3=0;
until [ "$(writable /sdcard)" == 0 -o "$counter3" -gt "$sdlimit" ]; do
sleep 1;
counter3=$((counter3 + 1));
done;
[ "$counter3" -gt "$sdlimit" ] && log=/data/local/gapps-integrator.log;
[ ! -s $log ] && echo -e "## GApps Auto-Integration Script Log\n#\n# ~ = forced permissions list rebuild,\n# * = not enough free space, ! = excluded\n" > $log;
echo "$logbuff" >> $log;
echo "---" >> $log;
# limit log length to ~16Kb (when exceeded) by removing oldest entry
[ "$(du -k $log | cut -f1)" -gt 16 ] && $bb sed -i "/$(grep -m1 -vE '^$|^#.*$' $log)/,/^\s*$/d" $log;
# restore runtime-permissions.xml backup files to maintain granted user app permissions and reboot after boot is completed
counter4=0;
if [ -d "$gbak" -a "$dryrun" == 0 ]; then
until [ "$(getprop sys.boot_completed)" == 1 -o "$counter4" -gt "$sdlimit" ]; do
sleep 1;
counter4=$((counter4 + 1));
done;
if [ "$counter4" -gt "$sdlimit" ]; then
echo "Ooops! Timeout for boot completion to restore user app runtime-permissions files" >> $log;
echo "---" >> $log;
fi;
for user in `ls $gbak`; do
cp -fp $gbak/$user /data/system/users/$user/runtime-permissions.xml;
done;
rm -rf $gbak;
echo "Rebooting after restoring user app runtime-permissions files" >> $log;
echo -e "---\n" >> $log;
reboot;
fi;
echo -ne "\n" >> $log;
echo "---";
}&
) >> $out 2>&1;