Gibt es ein Nicht-App-Tool für die Batch-Sicherung von APK-Dateien?

Wie die anderen Male darf eine Antwort auf meine Frage keine App aus dem Play Store oder einer anderen Quelle in Betracht ziehen. Was ich suche, ist ein Tool, das in der Lage ist, Benutzer-Apps , System-Apps oder beide Kategorien zu sichern , wenn es von einem funktionierenden Android-Betriebssystem aus gestartet wird.

Eine Sicherung der externen Daten der Apps ist nicht relevant . Das einzige, was zählt, ist die Möglichkeit, die base.apk- Datei, die sich in jedem /data/app/appName/ -Verzeichnis befindet, im Stapelbetrieb zu sichern, damit ich sie entweder neu installieren kann, um zu einer früheren Version zurückzukehren, oder es von Grund auf neu zu installieren.

Schließlich wäre es mir lieber, wenn das Tool frei anpassbar wäre (z. B. nicht kompiliert). Vielleicht so etwas wie ein Shell-Skript?

Antworten (2)

Dieses Skript ist veraltet und wird nicht mehr unterstützt. Die neueste Version, die Android 5+ und Python 3.5+ erfordert und Arcus-Designvarianten und Substratum-Overlays unterstützt, finden Sie in meinem GitHub-Repository .


Hier ist mein überarbeitetes Skript, das jetzt als NEMRIS bezeichnet wird und die Frage beantwortet. Wie üblich ist es von jedem frei editierbar und auch sh-konform, was bedeutet, dass es von der Android-Shell ausgeführt werden kann.

Tatsächlich hängt das Skript vom aapt- Dienstprogramm ab, kann erkennen, ob das Dienstprogramm installiert ist, kann feststellen, ob die backupDirund die appsToBackupVariablen leer sind oder nicht, und wenn dies der Fall ist, setzt es Standardwerte, anstatt abzubrechen.

Dennoch kann die Datums -Binärdatei Ihre wahre Zeitzone nicht bestimmen, daher sollte jeder seine TZVariable manuell bearbeiten.

Sobald es zum ersten Mal ausgeführt wird, kann das Tool anhand eines mit MD5-Prüfsummen gefüllten Arrays erkennen, ob ein APK bereits gesichert wurde oder nicht.

Es speichert das Array auch in einem Wörterbuch namens md5sums.txt , das eine Klartextdatei ist, die sich im APK-Sicherungsverzeichnis befindet. Diese Funktion kann die verstrichene Zeit um gut die Hälfte reduzieren, hängt jedoch von einer Statistik- Binärdatei ab, die in der Lage ist, die genaue Größe einer Datei in Bytes zu messen. Wenn stat dem nicht entspricht, werden die Prüfsummen jedes Mal neu berechnet, wie in der vorherigen Version des Skripts. Damit soll sichergestellt werden, dass NEMRIS nur von aapt abhängig ist . Denken Sie daran, die md5sums.txt manuell zu löschen , wenn Sie den Sicherungsordner bereinigen.

Als Plus hat das Skript jetzt eine maxDictSizeVariable, die einen ganzzahligen Wert akzeptiert. Der eingegebene Wert wird als Bytes interpretiert. Dies kann angepasst werden und dient dazu, das Prüfsummenwörterbuch neu zu berechnen, wenn seine Größe das Maximum überschreitet.

Schließlich habe ich es gepatcht, um Apps von Drittanbietern zu unterstützen, deren Namen Schrägstriche enthalten. Keine seltsamen Fehler mehr, denke ich.

Ich hoffe, es kann sich als nützlich erweisen.

Lizenz: GNU General Public License, Version 3 oder höher.


#!/system/bin/sh

# nemris - an Android app backup tool

# Copyright (C) 2016 Death Mask Salesman
# <http://android.stackexchange.com/users/152843/death-mask-salesman>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License at <http://www.gnu.org/licenses/> for
# more details.

#This variable is used to calculate the total elapsed time
#DO NOT alter it, or the script won't be able to calculate it
SECONDS=0

#This variable is used to output the time when the script is launched
#Edit the plus/minus and the value after "UTC", to force date into displaying your correct time zone
timeAtStart="$(TZ=UTC-2 date +%H:%M:%S)"

#This variable stores the path where to backup your apps' APK files
#Edit it freely, but do not forget the quotes
backupDir=""

#This variable stores the path where the script is located
scriptDir="$(dirname "$(readlink -f "$0")")"

#This variable tells the script which apps to backup
#Supported values are:
# - "User": tells the script to backup only third-party apps
# - "System": tells the script to backup only system apps
# - "All": tells the script to backup both 3rd party and system apps
appsToBackup=""

#This variable contains the maximum size of the checksums dictionary, in bytes
#It is used to decide whether to flush the dictionary or not
typeset -i maxDictSize=20000

#This function outputs the time when the operations start. Purely cosmetic: can be disabled safely
#///Beginning of function "timeInfo"///
function timeInfo {
 echo "[ INFO ] Operations started at "$timeAtStart"."
}
#///End of function "timeInfo"///

#This function checks if the "aapt" and "stat" utilities are installed
#///Beginning of function "dependencyChecker"///
function dependencyChecker {
 echo "[ INFO ] Checking if aapt and stat are installed..."
 whence -v aapt &> /dev/null
 isAaptAbsent="$(echo $?)"

 whence -v stat &> /dev/null
 isStatAbsent="$(echo $?)"

 if [ "$isAaptAbsent" == "0" ]; then
  echo "[ INFO ] aapt has been found."
  unset isAaptAbsent
 else
  echo "[ FATAL ] aapt cannot be found. Aborting."
  exit
 fi

 if [ "$isStatAbsent" == "0" ]; then
  echo "[ INFO ] stat has been found."
  echo ""
  echo "[ INFO ] Checking if stat can measure exact file sizes..."

  stat -c %s "$scriptDir/nemris.sh" &> /dev/null
  if [ "$(echo $?)" == "0" ]; then
   echo "[ INFO ] Measurement successful."
   isStatUsable="1"
   unset isStatAbsent
  else
   echo "[ WARNING ] Measurement failed. stat will be ignored."
   isStatUsable="0"
   unset isStatAbsent
  fi
 else
  echo "[ WARNING ] stat cannot be found."
  isStatUsable="0"
  unset isStatAbsent
 fi
}
#///End of function "dependencyChecker"///

#This function verifies whether the values into "backupDir" and "appsToBackup" have anything inside or not
#In the case that the variables are empty, default values will be used, instead
#///Beginning of the function "variablesChecker"///
function variablesChecker {
 echo "[ INFO ] Checking if a backup path has already been specified..."
 if [ "$backupDir" == "$(cat /dev/null)" ]; then
  echo "[ WARNING ] A backup path has not been specified."
  echo "[ WARNING ] Setting the path to default (/sdcard/AppsBackup)."
  backupDir="/sdcard/AppsBackup"
 else
  echo "[ INFO ] Backup path has already been specified."
 fi
 echo ""

 echo "[ INFO ] Checking if the typology of apps to backup has already been chosen..."
 if [ "$appsToBackup" == "$(cat /dev/null)" ]; then
  echo "[ WARNING ] A typology of apps to backup has not been specified."
  echo "[ WARNING ] Setting the default typology (User)."
  appsToBackup="User"
 else
  echo "[ INFO ] A typology of apps has already been specified."
 fi
}
#///End of the function "variablesChecker"///

#This function emulates the case insensitivity for "appsToBackup", usually provided by brace expansion, but lacked by sh
#In the case that an unsupported value is found, a default value will be used instead
#///Beginning of the function "caseInsensitiveWorkaround"///
function caseInsensitiveWorkaround {
 systemArray="$(echo -n {s,S}{y,Y}{s,S}{t,T}{e,E}{m,M})"
 userArray="$(echo -n {u,U}{s,S}{e,E}{r,R})"
 allArray="$(echo -n {a,A}{l,L}{l,L})"

 case ${appsToBackup:0:1} in
  s|S)
   for i in $systemArray; do
    case "$appsToBackup" in
     $i)
      appsToBackup="System"
      ;;
    esac
   done
   ;;
  u|U)
   for i in $userArray; do
    case "$appsToBackup" in
     $i)
      appsToBackup="User"
      ;;
    esac
   done
   ;;
  a|A)
   for i in $allArray; do
    case "$appsToBackup" in
     $i)
      appsToBackup="All"
      ;;
    esac
   done
   ;;
  *)
   echo "[ WARNING ] \""$appsToBackup"\": invalid typology."
   echo "[ WARNING ] Setting the default typology (User)."
   appsToBackup="User"
   ;;
 esac

 case "$appsToBackup" in
  System|User|All)
   ;;
  *)
   echo "[ WARNING ] \""$appsToBackup"\": invalid typology."
   echo "[ WARNING ] Setting the default typology (User)."
   appsToBackup="User"
   ;;
 esac
}
#///End of the function "caseInsensitiveWorkaround"///

#This function checks if the backup directory already exists
#///Beginning of the function "backupDirCheck"///
function backupDirCheck {
 echo "[ INFO ] Checking if the backup directory already exists..."
 if [ -d "$backupDir" ]; then
  echo -n "[ INFO ] Backup directory already exists, "

  cd "$backupDir"
  #echo "[ INFO ] Checking if the directory is empty..."
  if [ "$(ls | grep "\.apk$" | head -n 1)" == "$(cat /dev/null)" ]; then
   echo "and does not have any APK file inside."
  else
   echo "and has at least an APK file inside."
   isDirectoryEmpty="0"
  fi
 else
  echo "[ INFO ] Backup directory does not exist. Creating it now."
  mkdir -p "$backupDir"
  cd "$backupDir"
 fi
}
#///End of the function "backupDirCheck"///

#This function compares the size of the checksums dictionary and flushes it if too big
#///Beginning of function "sizeComparer"///
function sizeComparer {
 echo "[ INFO ] Comparing the dictionary size to the max value..."

 typeset -i dictSize="$(stat -c %s "$backupDir/md5sums.txt")"
 if [ $dictSize -ge maxDictSize ]; then
  echo "[ INFO ] The dictionary size exceeds the maximum value. The checksums will be recomputed."
  dictTooBig="1"
  unset dictSize
 else
  echo "[ INFO ] The dictionary size does not exceed the maximum value."
 fi
}
#///End of function "sizeComparer"///

#This function generates a MD5 array, filled with the checksum of each APK file
#///Beginning of the function "checksumsGenerator"///
function checksumsGenerator {
 echo "[ INFO ] Generating file checksums..."
 apkList="$(printf "%s\n" * | grep "\.apk$")"
 for i in $apkList; do
  tempMd5=($(md5sum $i))
  md5Array="$tempMd5 $md5Array"
 done

 cat << EOF > "$backupDir/md5sums.txt"
md5Array="$md5Array"
EOF

 echo "[ INFO ] MD5 array generated."
}
#///End of the function "checksumsGenerator"///

#This function creates a list of the apps to backup, based on the content in "appsToBackup"
#///Beginning of the function "appListGenerator"///
function appListGenerator {
 case "$appsToBackup" in
  System)
   echo "[ INFO ] Retrieving system apps list..."
   appList="$(pm list packages -s)"
   ;;
  User)
   echo "[ INFO ] Retrieving third-party apps list..."
   appList="$(pm list packages -3)"
   ;;
  All)
   echo "[ INFO ] Retrieving apps list..."
   appList="$(pm list packages)"
   ;;
 esac

 echo "[ INFO ] Applications list retrieved."
}
#///End of the function "appListGenerator"///

#This function creates an array, filled with each app's full path
#///Beginning of the function "apkPathRetriever"///
function apkPathRetriever {
 echo "[ INFO ] Collecting the path of each app's APK..."
 for i in $appList; do
  apkPath="${i#p*:}"
  apkPath="$(pm path $apkPath)"
  apkPath="${apkPath#p*:}"
  pathArray="$apkPath $pathArray"
 done
 echo "[ INFO ] Paths collected."
}
#///End of the function "apkPathRetriever"///

#This function extracts the label or the package name for the APK supplied to it
#It extracts the app label for any third-party app, the package name for any system app
#///Beginning of the function "appNameRetriever"///
function appNameRetriever {
 case "$1" in
  /system/*)
   appName="${aaptOutput#*name=\'}"
   appName="${appName//\'*/}"
   displayedName="$appName"
   ;;
  /data/*|/mnt/asec/*)
   appName="${aaptOutput#*application-label:\'}"
   appName="${appName//app*/}"
   appName="${appName%\'*}"
   displayedName=$appName
   appName="${appName//\//}"
   appName="$(printf "%s" $appName)"
   ;;
 esac
}
#///End of the function "appNameRetriever"///

#This function retrieves the version number of the APK
#///Beginning of the function "appVersionRetriever"///
function appVersionRetriever {
 appVersion="${aaptOutput#*versionName=\'}"
 appVersion="${appVersion//platformBuildVersion*/}"
 appVersion="${appVersion%\'*}"
 appVersion="$(printf "%s" $appVersion)"
}
#///End of the function "appVersionRetriever"///

#This function compares the MD5 of the APK in question against any MD5 in the array
#If it finds a match, then the function exits and the APK isn't backed up
#///Beginning of the function "md5Compare"///
function md5Compare {
 alreadyBackedUp=0
 appMd5=($(md5sum "$1"))

 for c in $md5Array; do
  if [ "$alreadyBackedUp" == "0" ]; then
   if [ "$appMd5" == "$c" ]; then
    alreadyBackedUp=1;
   fi
  else
   break;
  fi
 done
}
#///End of the function "md5Compare"///

#This function decides whether to backup an app or not
#It calls both "appNameRetriever" and "appVersionRetriever" for renaming any copied "base.apk"
#It also decides whether to call "md5Compare" or not, in order to see if an app has already been backed up
#///Beginning of the function "apkBackup"///
function apkBackup {
 for i in $pathArray; do
  aaptOutput="$(aapt d badging "$i")"

  appNameRetriever "$i"
  appVersionRetriever

  if [ "$isDirectoryEmpty" == "0" ]; then
   md5Compare "$i"

   if [ "$alreadyBackedUp" == "0" ]; then
    echo -n "[ INFO ] Backing up "$displayedName"... "
    cp "$i" "$backupDir"/$appName"_"$appVersion.apk
    echo "done."
    md5Array="$appMd5 $md5Array"
   else
    echo "[ INFO ] "$displayedName" has already been backed up."
   fi
  else
   echo -n "[ INFO ] Backing up "$displayedName"... "
   cp "$i" "$backupDir"/$appName"_"$appVersion.apk
   echo "done."
  fi
 done

 if [ "$md5Array" != "$(cat /dev/null)" ]; then
  cat << EOF > "$backupDir/md5sums.txt"
md5Array="$md5Array"
EOF
 fi
}
#///End of the function "apkBackup"///

#This is the core of the tool
echo "**************************"
echo " NEMRIS - App backup tool "
echo "  by Death Mask Salesman  "
echo "**************************"

timeInfo
echo ""

dependencyChecker
echo ""

variablesChecker
echo ""

caseInsensitiveWorkaround
echo ""

backupDirCheck
echo ""

#This section checks whether to analyze the dictionary size
#It also decides whether to compute the checksums
if [ "$isDirectoryEmpty" == "0" ]; then
 if [ -e "$backupDir/md5sums.txt" ]; then
  if [ "$isStatUsable" == "1" ]; then
   sizeComparer
   echo ""
  else
   dictTooBig="1"
  fi

  if [ "$dictTooBig" == "1" ]; then
   checksumsGenerator
   echo ""
  else
   source "$backupDir/md5sums.txt"
  fi  
 else
  echo "[ INFO ] Checksums not calculated yet."
  checksumsGenerator
  echo ""
 fi
fi

appListGenerator
echo ""

apkPathRetriever
echo ""

apkBackup
echo ""

echo "[ INFO ] Operations took "$(((SECONDS/60)/60))" hours, "$(((SECONDS/60)%60))" minutes and "$((SECONDS%60))" seconds."
echo "[ INFO ] All done!"
Wenn Sie mehr Funktionalität wünschen, schauen Sie sich mein Skript an: Adebar :) Benötigt nur Bash und ADB, läuft aber nicht direkt auf dem Gerät (benötigt einen Computer mit Linux oder Cygwin) und erstellt ua Skripte für adb backup– also keine einfachen .apkDateien entweder (obwohl das hinzugefügt werden könnte, Wurzel gegeben).
+1. Death Mask Salesman, vielen Dank, dass Sie dieses Skript geschrieben und veröffentlicht haben! Ich frage mich, ob Sie das Skript bitte unter einer Softwarelizenz veröffentlichen könnten? (Diese ganze Website ist CC-BY-SA 3.0, aber das ist keine sehr gute Softwarelizenz.) Wenn Sie mir sagen, in welchem ​​Land Sie leben, kann ich Ihnen vielleicht mehr Ratschläge geben, wie Sie eine einfach auswählen und anwenden können Lizenz.
@unforgettableid Freut mich, dass es sich als nützlich herausgestellt hat. Gemäß Ihrem Vorschlag habe ich mich entschieden, das Skript mit der GNU GPLv3 zu lizenzieren, um sicherzustellen, dass jeder es verwenden und an seine Bedürfnisse anpassen kann.
@DeathMaskSalesman: Vielen Dank! "GPLv2 oder höher" wäre aus Gründen der Lizenzkompatibilität vielleicht noch besser gewesen, aber "GPLv3 oder höher" ist auch ausgezeichnet.

Dies ist eine spezielle, verbesserte Version des Standard -Nemris- Tools. Es stellt eine separate Antwort dar, da es zwar eine dramatische Geschwindigkeitsverbesserung bietet, aber größtenteils ungetestet ist (es funktioniert mit Sicherheit unter CM12.1 und CM13 ).


Lizenz

Dieses Tool ist unter der WTFPL-Lizenz lizenziert. Mit anderen Worten, machen Sie damit, was Sie wollen. Verwenden Sie seine Komponenten wieder, vergeben Sie Unterlizenzen ... und zitieren Sie mich als ursprünglichen Autor, wenn Sie möchten. Ich übernehme jedoch keine Verantwortung.


Unterschiede zum alten Nemris

  • Die Ausführlichkeit des Tools wurde reduziert.
  • stat ist nicht mehr notwendig.
  • Die Backup-Geschwindigkeit wurde stark verbessert: Ein Delta-Backup dauert etwa 25 Sekunden statt einer Minute.
  • Das fehlerhafte Backup von System-Apps durch Ignorieren von aapt wurde behoben.
  • Unterstützung für die Sicherung deaktivierter Apps hinzugefügt.
  • Zwei optionale Befehlszeilenargumente wurden hinzugefügt; Die Verwendung ist unten.

Standard-Sicherungsverzeichnis

Um ein benutzerdefiniertes Sicherungsverzeichnis anzugeben, bearbeiten Sie Zeile 17 manuell . Wenn Sie es leer lassen, verwendet das Skript standardmäßig /sdcard/NemrisBackup .


Verwendung

Nemris kann entweder ohne Argumente oder mit zwei optionalen Argumenten gestartet werden. Verwendung:

nemris.sh [app_typology] [reset]

. Das erste Argument kann User , System , Disabled oder All sein oder auch leer gelassen werden. Wenn das angegebene Argument nicht erkannt oder null ist, verwendet das Tool standardmäßig User . Groß-/Kleinschreibung wird unterstützt.

Das zweite Argument darf nur zurückgesetzt werden und darf nur angegeben werden, wenn auch das erste Argument angegeben wurde. Wenn Sie dies tun, löscht das Tool seine Konfigurationsdateien, die sich am selben Ort wie das Skript befinden, und erstellt sie neu.


#!/system/bin/sh

# A bunch of variables and a function that facilitate the script's workings
# Do not alter them unless you know what you're doing
SECONDS=0
app_typology="$1"
null="$(cat /dev/null)"
script_location="$(dirname "$(readlink -f "$0")")"

function var_to_file {
 echo "$1=\"$2\"" >> "$script_location/nemris_config.txt"
}

####################

# This variable is needed to define the backup location
backup_location=""

####################

# This function cleans up the files generated in a previous execution
function settings_md5_files_delete {
 echo -ne "[ INFO ] Deleting Nemris configuration and MD5 dictionary..."

 if [ -e "$script_location/nemris_config.txt" ]; then
  if [ -e "$script_location/md5_dictionary.txt" ]; then
   rm "$script_location/nemris_config.txt" "$script_location/md5_dictionary.txt"
  else
   rm "$script_location/nemris_config.txt"
  fi
 else
  if [ -e "$script_location/md5_dictionary.txt" ]; then
   rm "$script_location/md5_dictionary.txt"
  else
   echo -ne " no files to remove..."
  fi
 fi

 echo " done. The files will be regenerated."
}

# This function checks if a settings file has been generated from a previous execution and loads its data
function settings_file_check {
 echo -ne "[ INFO ] Checking for Nemris configuration file..."

 if [ -e "$script_location/nemris_config.txt" ]; then
  echo " found."

  if [ "$backup_location" == "$null" ]; then
   source "$script_location/nemris_config.txt"
  else
   temp="$backup_location"
   source "$script_location/nemris_config.txt"
   backup_location="$temp"

   unset temp
  fi
 else
  echo " not found. The configuration will be regenerated."

  touch "$script_location/nemris_config.txt"
 fi
}

# This function reads the list of the packages and generates a special dictionary
# This is the reason of the time gain between the legacy Nemris and this version
function packages_file_write {
 echo -ne "[ INFO ] Generating refined packages list..."

 if [ -e "$script_location/packages.txt" ]; then
  echo -n "$null" > "$script_location/packages.txt"
 fi

 cat "/data/system/packages.xml" | grep "package name" > "$script_location/packages.txt"

 IFS=$'\n'

 packages="$(cat "$script_location/packages.txt")"
 echo -n "$null" > "$script_location/packages.txt"

 for i in $packages; do
  package_name="${i//*package name=\"/}"
  package_name="${package_name//\"*/}"

  package_path="${i//*codePath=\"/}"
  package_path="${package_path//\"*/}"

  if [ "$(echo -ne "$package_path" | grep "\.apk$")" == "$null" ]; then
   package_path="$package_path/$(ls "$package_path" | grep "\.apk$")"
  fi

  echo "$package_name:$package_path" >> "$script_location/packages.txt"
 done

 unset IFS package package_name package_path

 echo " done."
}

# This function checks if aapt is installed
function aapt_check {
 echo -ne "[ INFO ] Checking for aapt..."

 whence -v aapt &> /dev/null
 is_aapt_missing="$(echo $?)"

 var_to_file "is_aapt_missing" "$is_aapt_missing"

 echo " done."
}

# This function sets the backup location and the app typology to backup to default
function variables_default {
 case $1 in
  0)
   echo -ne "[ INFO ] Setting backup directory and app typology to default..."

   backup_location="/sdcard/NemrisBackup"
   app_typology="User"

   var_to_file "backup_location" "$backup_location"

   echo " done."
   ;;
  1)
   echo -ne "[ INFO ] Setting backup location to default..."

   backup_location="/sdcard/NemrisBackup"

   var_to_file "backup_location" "$backup_location"

   echo " done."
   ;;
  2)
   echo -ne "[ INFO ] Setting app typology to default..."

   app_typology="User"

   echo " done."
   ;;
 esac
}

# This function ensures case insensitivity and saves a headache to the user
function app_typology_sanitize {
 case ${app_typology:0:1} in
  s|S)
   if [ "$system_permutations" == "$null" ]; then
    permutations_already_calculated=0
    system_permutations="$(echo -n {s,S}{y,Y}{s,S}{t,T}{e,E}{m,M})"
   fi

   for i in $system_permutations; do
    if [ "$app_typology" == "$i" ]; then
     app_typology="System"
     break
    fi
   done

   if [ "$permutations_already_calculated" == "0" ]; then
    var_to_file "system_permutations" "$system_permutations"
   fi
   ;;
  u|U)
   if [ "$user_permutations" == "$null" ]; then
    permutations_already_calculated=0
    user_permutations="$(echo -n {u,U}{s,S}{e,E}{r,R})"
   fi

   for i in $user_permutations; do
    if [ "$app_typology" == "$i" ]; then
     app_typology="User"
     break
    fi
   done

   if [ "$permutations_already_calculated" == "0" ]; then
    var_to_file "user_permutations" "$user_permutations"
   fi
   ;;
  d|D)
   if [ "$disabled_permutations" == "$null" ]; then
    permutations_already_calculated=0
    disabled_permutations="$(echo -n {d,D}{i,I}{s,S}{a,A}{b,B}{l,L}{e,E}{d,D})"
   fi

   for i in $disabled_permutations; do
    if [ "$app_typology" == "$i" ]; then
     app_typology="Disabled"
     break
    fi
   done

   if [ "$permutations_already_calculated" == "0" ]; then
    var_to_file "disabled_permutations" "$disabled_permutations"
   fi
   ;;
  a|A)
   if [ "$all_permutations" == "$null" ]; then
    permutations_already_calculated=0
    all_permutations="$(echo -n {a,A}{l,L}{l,L})"
   fi

   for i in $all_permutations; do
    if [ "$app_typology" == "$i" ]; then
     app_typology="All"
     break
    fi
   done

   if [ "$permutations_already_calculated" == "0" ]; then
    var_to_file "all_permutations" "$all_permutations"
   fi
   ;;
  System|User|Disabled|All)
   ;;
  *)
   echo -ne "[ WARN ] \"$app_typology\": invalid value. Setting to default (User)..."

   app_typology="User"

   echo " done."
   ;;
 esac

 case $app_typology in
  System|User|Disabled|All)
   ;;
  *)
   echo -ne "[ WARN ] \"$app_typology\": invalid value. Setting to default (User)..."

   app_typology="User"

   echo " done."
   ;;
 esac
}

# This function checks if the backup directory contains any APK
function backup_location_check {
 if [ -d "$backup_location" ]; then
  if [ "$(ls "$backup_location" | grep "\.apk$")" == "$null" ]; then
   backup_directory_is_empty=1
  else
   backup_directory_is_empty=0
  fi
 else
  echo -ne "[ INFO ] Creating backup directory..."

  mkdir -p "$backup_location"

  echo " done."
 fi

 cd "$backup_location"
}

# This function generates a MD5 checksums dictionary
# This shortens down the operations time in any execution of the script
function md5_dict_generate {
 echo -ne "[ INFO ] Generating MD5 checksums..."

 apk_list="$(printf "%s\n" * | grep "\.apk$")"

 for i in $apk_list; do
  temp=($(md5sum "$i"))
  md5_array="$temp $md5_array"
 done

 echo -n "md5_array=\""$md5_array"\"" > "$script_location/md5_dictionary.txt"

 echo " done."
}

# This function retrieves the list of installed apps
function apps_list_retrieve {
 case $app_typology in
  System)
   echo -ne "[ INFO ] Retrieving system apps list..."

   for i in $(pm list packages -s); do
    pkg="${i//p*:/}"
    pkg="${pkg//./\\.}"
    apps_list="$apps_list $pkg"
   done

   echo " done."
   ;;
  User)
   echo -ne "[ INFO ] Retrieving third-party apps list..."

   for i in $(pm list packages -3); do
    pkg="${i//p*:/}"
    pkg="${pkg//./\\.}"
    apps_list="$apps_list $pkg"
   done

   echo " done."
   ;;
  Disabled)
   echo -ne "[ INFO ] Retrieving disabled apps list..."

   for i in $(pm list packages -d); do
    pkg="${i//p*:/}"
    pkg="${pkg//./\\.}"
    apps_list="$apps_list $pkg"
   done

   echo " done."
   ;;
  All)
   echo -ne "[ INFO ] Retrieving apps list..."

   for i in $(pm list packages); do
    pkg="${i//p*:/}"
    pkg="${pkg//./\\.}"
    apps_list="$apps_list $pkg"
   done

   echo " done."
   ;;
 esac
}

# This function retrieves the APKs paths via their package name (from the dictionary)
# Actually, this is the second part of the magic
function apks_paths_retrieve {
 echo -ne "[ INFO ] Collecting the path of each app's APK..."

 for i in $apps_list; do
  row="$(cat "$script_location/packages.txt" | grep "$i:")"
  paths_list="$paths_list ${row//*:/}"
 done

 echo " done."
}

# This function retrieves the app name
# If an app has no name, it falls back to the package name
function app_name_retrieve {
 app_name="$(echo -ne "$aapt_output" | grep "application-label:\'")"

 if [ "$app_name" != "$null" ]; then
  app_name="${app_name//application-label:\'/}"
  app_name="${app_name%\'*}"
  displayed_name="$app_name"
  app_name="${app_name//\//}"
  app_name="$(printf "%s" $app_name)"
 else
  app_name="$(echo -ne "$aapt_output" | grep "package: name.")"
  app_name="${app_name//package: name=\'/}"
  app_name="${app_name//\'*/}"
  displayed_name="$app_name"
 fi
}

# This function retrieves the app version
# If an app has no version (weird, but that happens) it defaults to "None"
function app_version_retrieve {
 app_version="${aapt_output#*versionName=\'}"
 app_version="${app_version//platformBuildVersion*/}"
 app_version="${app_version%\'*}"
 app_version="$(printf "%s" $app_version)"

 if [ "$app_version" == "$null" ]; then
  app_version="None"
 fi
}

# This function sets the app name to the name of the APK file
# It's used when the app to be backed up is a system app, which may make aapt go crazy
function system_app_name_set {
 app_name="$(basename $1)"
 app_name="${app_name%\.apk}"
 displayed_name="$app_name"
}

# This function checks if the checksum of any app figures in the already backed up apps
# This way, any app is backed up exactly once, and Nemris cannot be fooled by file names
function md5_compare {
 already_backed_up=0
 app_md5=($(md5sum "$1"))

 for c in $md5_array; do
  if [ "$already_backed_up" == "0" ]; then
   if [ "$app_md5" == "$c" ]; then
    already_backed_up=1
   fi
  else
   break
  fi
 done
}

# This function backs up the APK files, and calls the appropriate functions to handle both system and third-party apps
function apk_backup {
 for i in $paths_list; do
  case $i in
   /system/*)
    system_app_name_set $i
    ;;
   /data/app/*|/mnt/asec/*)
    aapt_output="$(aapt d badging "$i")"

    app_name_retrieve
    app_version_retrieve
    ;;
  esac

  if [ "$backup_directory_is_empty" == "0" ]; then
   md5_compare "$i"

   if [ "$already_backed_up" == "0" ]; then
    echo -ne "[ INFO ] Backing up "$displayed_name"..."

    case $i in
     /system/*)
      cp "$i" "$backup_location"/$app_name.apk
      ;;
     /data/app/*|/mnt/asec/*)
      cp "$i" "$backup_location"/$app_name"_"$app_version.apk
      ;;
    esac

    md5_array="$app_md5 $md5_array"

    echo " done."
   else
    echo "[ INFO ] "$displayed_name" has already been backed up."
   fi
  else
   echo -ne "[ INFO ] Backing up "$displayed_name"..."

   case $i in
    /system/*)
     cp "$i" "$backup_location"/$app_name.apk
     ;;
    /data/app/*|/mnt/asec/*)
     cp "$i" "$backup_location"/$app_name"_"$app_version.apk
     ;;
   esac

   echo " done."
  fi
 done

 if [ "$md5_array" != "$null" ]; then
  echo -ne "md5_array=\""$md5_array"\"" > "$script_location/md5_dictionary.txt"
 fi
}

# This function informs about the time that has been required by all of the operations
function goodbye {
 echo "[ INFO ] Operations took "$(((SECONDS/60)/60))" hours, "$(((SECONDS/60)%60))" minutes and "$((SECONDS%60))" seconds."
 echo "[ INFO ] All done!"
}

####################

# This is the core of the tool
echo "**************************"
echo " NEMRIS - App backup tool "
echo "  by Death Mask Salesman  "
echo "**************************"
echo ""

# Logic that handles the optional second argument that may be supplied by an user
# Only the "reset" argument is detected
if [ "$2" == "reset" ]; then
 settings_md5_files_delete
else
 settings_file_check
fi

packages_file_write

if [ "$is_aapt_missing" == "$null" ]; then
 aapt_check
fi

# Logic that aborts the execution if aapt is not present
if [ "$is_aapt_missing" == "1" ]; then
 echo "[ FATAL ] aapt is not installed: aborting."
 exit
fi

# Logic that decides whether variables to check
if [ "$backup_location" == "$null" ]; then
 if [ "$app_typology" == "$null" ]; then
  variables_default 0
 else
  variables_default 1
  app_typology_sanitize
 fi
else
 if [ "$app_typology" == "$null" ]; then
  variables_default 2
  else
   app_typology_sanitize
 fi
fi

backup_location_check

# Logic that checks whether to compute the checksums of the already backed up apps
if [ "$backup_directory_is_empty" == "0" ]; then
 if [ ! -e "$script_location/md5_dictionary.txt" ]; then
  md5_dict_generate
 else
  source "$script_location/md5_dictionary.txt"
 fi
fi

apps_list_retrieve

apks_paths_retrieve

echo ""

apk_backup

echo ""

goodbye