Most of the contacts backup apps lack one feature or the other e.g. they don't backup high resolution photo when creating .vcf
file. So I prefer CLI method to extract contacts data directly from database file using some scripting, but it requires root. That's how I do scheduled back up of my contacts without Google account.
The following script doesn't save Google sync account information to .vcf
file but you can extend the script to include/exclude any data if the concept is perceived.
Original credits to dump-contacts2db.sh.
#!/system/bin/bash -e
# dumps following fields of contacts from Android's sqlite database to vcf:
# Name, Phone Number, Email, Address, Photo, Title, Organization, Notes, Website
# required binaries: sqlite3, base64, xxd (all included with Android)
# contacts directory, database file and photos directory
CONT_PROVIDER='/data/user/0/com.android.providers.contacts'
DB="$CONT_PROVIDER/databases/contacts2.db"
PHOTO_DIR="$CONT_PROVIDER/files/photos"
# vcf file
VCF="/data/media/0/contacts_$(date '+%d-%b-%y_%H-%M-%S').vcf"
# delete backup file if error occurs
trap '[ $? -eq 0 ] || rm -f $VCF' EXIT
GEN_VCARD()
{
# skip blank contact for first time
[ -n "$name" ] || return 0
# count number of contacts
n=$((n+1))
vcard="${
name
}
${
tel
}
${
adr
}
${
email
}
${
url
}
${
note
}
${
org
}
${
title
}
${
photo
}
"
vcard="BEGIN:VCARD"$'\n'"VERSION:3.0"$'\n'"${
vcard
}
""END:VCARD"
echo "$vcard" >>$VCF
echo >>$VCF
}
# fetch contacts data from different columns of 'view_entities' table of contacts sqlite database
ROWS="$(
sqlite3 $DB "
SELECT
view_entities._id, view_entities.mimetype_id, view_entities.data1, view_entities.data2, view_entities.data3,
view_entities.data4, view_entities.data5, view_entities.data6, quote(view_entities.data15), view_entities.photo_uri
FROM view_entities
WHERE view_entities.deleted = 0
ORDER BY view_entities._id, view_entities.mimetype_id
")"
# to parse rows
IFS=$'\n'
# iterate through contacts data rows
for ROW in $ROWS
do
# to parse columns from a row
IFS="|"
i=0
# iterate through columns
for COL in $ROW
do
i=$((i+1))
# data included in each column
case $i in
1) # Contact ID
id=$COL;;
2) # Mime Type ID
mime_id=$COL;;
3) # Phone Number, Website, Email, Notes, Address, Organization
data=$COL;;
4) # Type ID of Phone No. / Email / Address, First Name
first_name=$COL; type_id=$COL;;
5) # Name of Custom Phone No. Type / Email Type / Address Type, Last Name
last_name=$COL; type=$COL;;
6) # Name Prefix, Title
name_prefix=$COL; tytle=$COL;;
7) # Middle Name
middle_name=$COL;;
8) # Name Suffix
name_suffix=$COL;;
9) # Photo thumbnail hex data
photo_hex=$COL;;
10) # Full resolution Photo URI
photo_uri=$COL;;
esac
done
# start new contact when all rows of same contact ID are parsed
if [ "$prev_id" != "$id" ]
then
# echo current vcard prior to resetting variables
GEN_VCARD
# init new vcard
for i in name tel adr email url note photo org title; do eval "$i=''"; done
fi
# add current row to current vcard, 'mimetype' determines data type on every row
case $mime_id in
1)
# Email
case $type_id in
0) email_type=X-$type;;
1) email_type=HOME;;
2) email_type=WORK;;
3) email_type="";;
4) email_type=CELL;;
*) echo "Unknown email type of '$data'" >&2; exit 1;;
esac
email=$email'EMAIL;TYPE='$email_type':'$data$'\n';;
4)
# Organization, Title
org='ORG:'$data$'\n'
title='TITLE:'$tytle$'\n';;
5)
# Phone No.
case $type_id in
0) tel_type=X-$type;;
1) tel_type=HOME;;
2) tel_type=CELL;;
3) tel_type=WORK;;
7) tel_type=VOICE;;
12) tel_type=PREF;;
*) echo "Unknown phone no. type of '$data'" >&2; exit 1;;
esac
tel=$tel'TEL;TYPE='$tel_type':'$data$'\n';;
7)
# Name
name="$name_prefix $first_name $middle_name $last_name $name_suffix"
# remove leading/trailing spaces
IFS=' ' read name <<<"$name"
# always add complete name as First Name
name="N:;"$name";;;"$'\n'"FN:"$name$'\n';;
8)
# Postal Address
case $type_id in
0) adr_type=X-$type;;
1) adr_type=HOME;;
2) adr_type=WORK;;
*) echo "Unknown address type of '$data'" >&2; exit 1;;
esac
adr=$adr'ADR;TYPE='$adr_type':;;'$data';;;;'$'\n';;
10)
# Photo
if [ $photo_hex != "NULL" ]
then
# look for high-resolution photo instead of thumbnail
uri=$(echo $photo_uri | sed 's|content://com.android.contacts/display_photo|'"$PHOTO_DIR"'|')
if [ -f $uri ]
then
# convert binary to base64
foto="$(base64 -w 0 $uri)"
else
# remove prefix/suffix from hex output, convert hex to binary to base64
foto=$(echo $photo_hex | sed "s/^X'//; s/'$//" | tr '[:upper:]' '[:lower:]' | xxd -pr | base64 -w 0)
fi
photo="PHOTO;ENCODING=BASE64;JPEG:"$foto$'\n'
fi;;
12)
# Note
note="NOTE:"$data$'\n';;
14)
# Website
url=$url"URL:"$data$'\n';;
*) echo "Unknown mime type: '$(sqlite3 $DB "SELECT view_entities.mimetype FROM view_entities WHERE view_entities.mimetype_id = $mime_id" | head -n1)'" >&2; exit 1;;
esac
# preserve current ID to compare with next row
prev_id=$id
# reset IFS for parent loop to parse rows
IFS=$'\n'
done
# echo last vcard, loop is done
GEN_VCARD
echo "Backed up $n contacts."
If you prefer GUI or don't have root, you can use some app like Contacts VCF.