Added local contacts

pull/147/head
M66B 6 years ago
parent c9308e71ff
commit 5b124c8ce4

File diff suppressed because it is too large Load Diff

@ -479,6 +479,11 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
jaccounts.put(jaccount);
}
// Contacts
JSONArray jcontacts = new JSONArray();
for (EntityContact contact : db.contact().getContacts())
jcontacts.put(contact.toJSON());
// Answers
JSONArray janswers = new JSONArray();
for (EntityAnswer answer : db.answer().getAnswers())
@ -497,6 +502,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
JSONObject jexport = new JSONObject();
jexport.put("accounts", jaccounts);
jexport.put("contacts", jcontacts);
jexport.put("answers", janswers);
jexport.put("settings", jsettings);
@ -617,6 +623,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
try {
db.beginTransaction();
// Accounts
JSONArray jaccounts = jimport.getJSONArray("accounts");
for (int a = 0; a < jaccounts.length(); a++) {
JSONObject jaccount = (JSONObject) jaccounts.get(a);
@ -676,6 +683,18 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
db.account().updateAccount(account);
}
// Contacts
JSONArray jcontacts = jimport.getJSONArray("contacts");
for (int c = 0; c < jcontacts.length(); c++) {
JSONObject jcontact = (JSONObject) jcontacts.get(c);
EntityContact contact = EntityContact.fromJSON(jcontact);
if (db.contact().getContacts(contact.type, contact.email).size() == 0) {
contact.id = db.contact().insertContact(contact);
Log.i("Imported contact=" + contact);
}
}
// Answers
JSONArray janswers = jimport.getJSONArray("answers");
for (int a = 0; a < janswers.length(); a++) {
JSONObject janswer = (JSONObject) janswers.get(a);
@ -684,6 +703,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
Log.i("Imported answer=" + answer.name);
}
// Settings
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
JSONArray jsettings = jimport.getJSONArray("settings");

@ -49,7 +49,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 43,
version = 44,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -57,6 +57,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
EntityMessage.class,
EntityAttachment.class,
EntityOperation.class,
EntityContact.class,
EntityAnswer.class,
EntityRule.class,
EntityLog.class
@ -77,6 +78,8 @@ public abstract class DB extends RoomDatabase {
public abstract DaoOperation operation();
public abstract DaoContact contact();
public abstract DaoAnswer answer();
public abstract DaoRule rule();
@ -496,6 +499,19 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `account` ADD COLUMN `pop` INTEGER NOT NULL DEFAULT 0");
}
})
.addMigrations(new Migration(43, 44) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("CREATE TABLE IF NOT EXISTS `contact`" +
" (`id` INTEGER PRIMARY KEY AUTOINCREMENT" +
", `type` INTEGER NOT NULL" +
", `email` TEXT NOT NULL" +
", `name` TEXT)");
db.execSQL("CREATE UNIQUE INDEX `index_contact_email_type` ON `contact` (`email`, `type`)");
db.execSQL("CREATE INDEX `index_contact_name_type` ON `contact` (`name`, `type`)");
}
})
.build();
}

@ -0,0 +1,58 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.database.Cursor;
import java.util.List;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
@Dao
public interface DaoContact {
@Query("SELECT * FROM contact")
List<EntityContact> getContacts();
@Query("SELECT *" +
" FROM contact" +
" WHERE email = :email" +
" AND (:type IS NULL OR type = :type)")
List<EntityContact> getContacts(Integer type, String email);
@Query("SELECT id AS _id, name, email" +
", CASE type" +
" WHEN " + EntityContact.TYPE_TO + " THEN '>'" +
" WHEN " + EntityContact.TYPE_FROM + " THEN '<'" +
" ELSE '?'" +
" END AS type" +
" FROM contact" +
" WHERE name LIKE :name" +
" AND (:type IS NULL OR type = :type)")
Cursor searchContacts(Integer type, String name);
@Insert
long insertContact(EntityContact contact);
@Update
int updateContact(EntityContact contact);
}

@ -0,0 +1,81 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
// https://developer.android.com/training/data-storage/room/defining-data
@Entity(
tableName = EntityContact.TABLE_NAME,
foreignKeys = {
},
indices = {
@Index(value = {"email", "type"}, unique = true),
@Index(value = {"name", "type"}),
}
)
public class EntityContact implements Serializable {
static final String TABLE_NAME = "contact";
static final int TYPE_TO = 0;
static final int TYPE_FROM = 1;
@PrimaryKey(autoGenerate = true)
public Long id;
@NonNull
public int type;
@NonNull
public String email;
public String name;
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("id", id);
json.put("type", type);
json.put("email", email);
json.put("name", name);
return json;
}
public static EntityContact fromJSON(JSONObject json) throws JSONException {
EntityContact contact = new EntityContact();
// id
contact.type = json.getInt("type");
contact.email = json.getString("email");
if (json.has("name"))
contact.name = json.getString("name");
return contact;
}
@NonNull
@Override
public String toString() {
return (name == null ? email : name + " <" + email + ">");
}
}

@ -393,31 +393,40 @@ public class FragmentCompose extends FragmentBase {
getActivity().invalidateOptionsMenu();
Helper.setViewsEnabled(view, false);
if (Helper.hasPermission(getContext(), Manifest.permission.READ_CONTACTS)) {
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
getContext(),
android.R.layout.simple_list_item_2,
null,
new String[]{
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.DATA
},
new int[]{
android.R.id.text1,
android.R.id.text2
},
0);
etTo.setAdapter(adapter);
etCc.setAdapter(adapter);
etBcc.setAdapter(adapter);
etTo.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
etCc.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
etBcc.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
adapter.setFilterQueryProvider(new FilterQueryProvider() {
final DB db = DB.getInstance(getContext());
final boolean contacts = Helper.hasPermission(getContext(), Manifest.permission.READ_CONTACTS);
SimpleCursorAdapter cadapter = new SimpleCursorAdapter(
getContext(),
R.layout.spinner_contact,
null,
contacts
? new String[]{
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.DATA
}
: new String[]{
"name",
"email",
"type"
},
contacts
? new int[]{android.R.id.text1, android.R.id.text2}
: new int[]{android.R.id.text1, android.R.id.text2, R.id.tvType},
0);
etTo.setAdapter(cadapter);
etCc.setAdapter(cadapter);
etBcc.setAdapter(cadapter);
etTo.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
etCc.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
etBcc.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
if (contacts)
cadapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence typed) {
Log.i("Searching provided contact=" + typed);
return getContext().getContentResolver().query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
new String[]{
@ -434,24 +443,31 @@ public class FragmentCompose extends FragmentBase {
", " + ContactsContract.CommonDataKinds.Email.DATA + " COLLATE NOCASE");
}
});
adapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() {
public CharSequence convertToString(Cursor cursor) {
int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
String name = cursor.getString(colName);
String email = cursor.getString(colEmail);
StringBuilder sb = new StringBuilder();
if (name == null)
sb.append(email);
else {
sb.append("\"").append(name).append("\" ");
sb.append("<").append(email).append(">");
}
return sb.toString();
else
cadapter.setFilterQueryProvider(new FilterQueryProvider() {
@Override
public Cursor runQuery(CharSequence typed) {
Log.i("Searching local contact=" + typed);
return db.contact().searchContacts(null, "%" + typed + "%");
}
});
}
cadapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() {
public CharSequence convertToString(Cursor cursor) {
int colName = cursor.getColumnIndex(contacts ? ContactsContract.Contacts.DISPLAY_NAME : "name");
int colEmail = cursor.getColumnIndex(contacts ? ContactsContract.CommonDataKinds.Email.DATA : "email");
String name = cursor.getString(colName);
String email = cursor.getString(colEmail);
StringBuilder sb = new StringBuilder();
if (name == null)
sb.append(email);
else {
sb.append("\"").append(name).append("\" ");
sb.append("<").append(email).append(">");
}
return sb.toString();
}
});
rvAttachment.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(getContext());

@ -2054,6 +2054,30 @@ public class ServiceSynchronize extends LifecycleService {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel("send", message.identity.intValue());
if (message.to != null)
for (Address recipient : message.to) {
String email = ((InternetAddress) recipient).getAddress();
String name = ((InternetAddress) recipient).getPersonal();
List<EntityContact> contacts = db.contact().getContacts(EntityContact.TYPE_TO, email);
if (contacts.size() == 0) {
EntityContact contact = new EntityContact();
contact.type = EntityContact.TYPE_TO;
contact.email = email;
contact.name = name;
db.contact().insertContact(contact);
Log.i("Inserted recipient contact=" + contact);
} else {
EntityContact contact = contacts.get(0);
if (name != null && !name.equals(contact.name)) {
contact.name = name;
db.contact().updateContact(contact);
Log.i("Updated recipient contact=" + contact);
}
}
}
} catch (Throwable ex) {
if (sid != null)
db.message().deleteMessage(sid);
@ -2798,6 +2822,31 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(folder.name + " unchanged uid=" + uid);
}
if (!folder.isOutgoing() && !EntityFolder.ARCHIVE.equals(folder.type)) {
Address[] senders = (message.reply != null ? message.reply : message.from);
if (senders != null)
for (Address sender : senders) {
String email = ((InternetAddress) sender).getAddress();
String name = ((InternetAddress) sender).getPersonal();
List<EntityContact> contacts = db.contact().getContacts(EntityContact.TYPE_FROM, email);
if (contacts.size() == 0) {
EntityContact contact = new EntityContact();
contact.type = EntityContact.TYPE_FROM;
contact.email = email;
contact.name = name;
contact.id = db.contact().insertContact(contact);
Log.i("Inserted sender contact=" + contact);
} else {
EntityContact contact = contacts.get(0);
if (name != null && !name.equals(contact.name)) {
contact.name = name;
db.contact().updateContact(contact);
Log.i("Updated sender contact=" + contact);
}
}
}
}
List<String> fkeywords = new ArrayList<>(Arrays.asList(folder.keywords));
for (String keyword : keywords)

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@android:id/text1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="Text1"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@android:id/text2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Text2"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toStartOf="@+id/tvType"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@android:id/text1" />
<TextView
android:id="@+id/tvType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@android:id/text1" />
</androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save