/* * Copyright (C) 2017 Anton Kaliturin <[email protected]> * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.kaliturin.blacklist.utils; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.CursorWrapper; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.FileReader; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * Database access helper */ public class DatabaseAccessHelper extends SQLiteOpenHelper { private static final String TAG = DatabaseAccessHelper.class.getName(); public static final String DATABASE_NAME = "blacklist.db"; private static final int DATABASE_VERSION = 1; private static volatile DatabaseAccessHelper sInstance = null; @Nullable public static DatabaseAccessHelper getInstance(Context context) { if (sInstance == null) { synchronized (DatabaseAccessHelper.class) { if (sInstance == null) { if (Permissions.isGranted(context, Permissions.WRITE_EXTERNAL_STORAGE)) { sInstance = new DatabaseAccessHelper(context.getApplicationContext()); } } } } return sInstance; } public static void invalidateCache() { if (sInstance != null) { synchronized (DatabaseAccessHelper.class) { if (sInstance != null) { sInstance.close(); sInstance = null; } } } } private DatabaseAccessHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // helper won't create the database file until we first open it SQLiteDatabase db = getWritableDatabase(); // onConfigure isn't calling in android 2.3 db.execSQL("PRAGMA foreign_keys=ON"); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(JournalTable.Statement.CREATE); db.execSQL(ContactTable.Statement.CREATE); db.execSQL(ContactNumberTable.Statement.CREATE); db.execSQL(SettingsTable.Statement.CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int i, int i1) { if (i != i1) { db.execSQL("DROP TABLE IF EXISTS " + SettingsTable.NAME); db.execSQL("DROP TABLE IF EXISTS " + ContactNumberTable.NAME); db.execSQL("DROP TABLE IF EXISTS " + ContactTable.NAME); db.execSQL("DROP TABLE IF EXISTS " + JournalTable.NAME); onCreate(db); } } @Override public void onConfigure(SQLiteDatabase db) { super.onConfigure(db); db.execSQL("PRAGMA foreign_keys=ON"); } //---------------------------------------------------------------- // Checks whether file is SQLite file public static boolean isSQLiteFile(String fileName) { File file = new File(fileName); if (!file.exists() || !file.canRead()) { return false; } FileReader reader = null; try { reader = new FileReader(file); char[] buffer = new char[16]; if (reader.read(buffer, 0, 16) > 0) { String str = String.valueOf(buffer); return str.equals("SQLite format 3\u0000"); } } catch (Exception e) { Log.w(TAG, e); } finally { Utils.close(reader); } return false; } // Closes cursor if it is empty and returns false private boolean validate(Cursor cursor) { if (cursor == null || cursor.isClosed()) return false; if (cursor.getCount() == 0) { cursor.close(); return false; } return true; } // Common statements private static class Common { /** * Creates 'IN part' of 'WHERE' clause. * If "all" is true - includes all items, except of specified in list. * Else includes all items specified in list. */ @Nullable static String getInClause(String column, boolean all, List<String> items) { if (all) { if (items.isEmpty()) { // include all items return null; } else { // include all items except of specified String args = joinStrings(items, ", "); return column + " NOT IN ( " + args + " ) "; } } // include all specified items String args = joinStrings(items, ", "); return column + " IN ( " + args + " ) "; } /** * Creates 'LIKE part' of 'WHERE' clause */ @Nullable static String getLikeClause(String column, String filter) { return (filter == null ? null : column + " LIKE '%" + filter + "%' "); } /** * Concatenates passed clauses with 'AND' operator */ static String concatClauses(String[] clauses) { StringBuilder sb = new StringBuilder(); for (String clause : clauses) { if (TextUtils.isEmpty(clause)) continue; if (sb.length() > 0) sb.append(" AND "); sb.append(clause); } return sb.toString(); } static String joinStrings(List<String> list, String separator) { StringBuilder sb = new StringBuilder(); boolean first = true; for (String item : list) { if (first) first = false; else sb.append(separator); sb.append(item); } return sb.toString(); } } // Journal table scheme private static class JournalTable { static final String NAME = "journal"; static class Column { static final String ID = "_id"; static final String TIME = "time"; static final String CALLER = "caller"; static final String NUMBER = "number"; static final String TEXT = "text"; } static class Statement { static final String CREATE = "CREATE TABLE " + JournalTable.NAME + "(" + Column.ID + " INTEGER PRIMARY KEY NOT NULL, " + Column.TIME + " INTEGER NOT NULL, " + Column.CALLER + " TEXT NOT NULL, " + Column.NUMBER + " TEXT, " + Column.TEXT + " TEXT " + ")"; static final String SELECT_FIRST_PART = "SELECT " + Column.ID + ", " + Column.TIME + " FROM " + JournalTable.NAME + " ORDER BY " + Column.TIME + " DESC"; static final String SELECT_LAST_PART_BY_ID = "SELECT " + Column.CALLER + ", " + Column.NUMBER + ", " + Column.TEXT + " FROM " + JournalTable.NAME + " WHERE _id = ? "; static final String SELECT_FIRST_PART_BY_FILTER = "SELECT * " + " FROM " + JournalTable.NAME + " WHERE " + Column.CALLER + " LIKE ? " + " OR " + Column.TEXT + " LIKE ? " + " ORDER BY " + Column.TIME + " DESC"; } } // Journal table record public static class JournalRecord { public final long id; public final long time; public final String caller; public final String number; public final String text; JournalRecord(long id, long time, @NonNull String caller, String number, String text) { this.id = id; this.time = time; this.caller = caller; this.number = number; this.text = text; } } // Journal record cursor wrapper public class JournalRecordCursorWrapper extends CursorWrapper { private final int ID; private final int TIME; JournalRecordCursorWrapper(Cursor cursor) { super(cursor); cursor.moveToFirst(); ID = cursor.getColumnIndex(JournalTable.Column.ID); TIME = cursor.getColumnIndex(JournalTable.Column.TIME); } public JournalRecord getJournalRecord() { long id = getLong(ID); long time = getLong(TIME); String[] parts = getJournalRecordPartsById(id); return new JournalRecord(id, time, parts[0], parts[1], parts[2]); } public long getTime(int position) { long time = 0; if (0 <= position && position < getCount()) { int curPosition = getPosition(); if (moveToPosition(position)) { time = getLong(TIME); moveToPosition(curPosition); } } return time; } } // Selects journal record's parts by id private String[] getJournalRecordPartsById(long id) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery(JournalTable.Statement.SELECT_LAST_PART_BY_ID, new String[]{String.valueOf(id)}); String[] parts; if (validate(cursor)) { cursor.moveToFirst(); final int CALLER = cursor.getColumnIndex(JournalTable.Column.CALLER); final int NUMBER = cursor.getColumnIndex(JournalTable.Column.NUMBER); final int TEXT = cursor.getColumnIndex(JournalTable.Column.TEXT); parts = new String[]{ cursor.getString(CALLER), cursor.getString(NUMBER), cursor.getString(TEXT)}; cursor.close(); } else { parts = new String[]{"?", "?", "?"}; } return parts; } // Selects all journal records @Nullable private JournalRecordCursorWrapper getJournalRecords() { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery(JournalTable.Statement.SELECT_FIRST_PART, null); return (validate(cursor) ? new JournalRecordCursorWrapper(cursor) : null); } // Selects journal records filtered with passed filter @Nullable public JournalRecordCursorWrapper getJournalRecords(@Nullable String filter) { if (filter == null) { return getJournalRecords(); } SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery(JournalTable.Statement.SELECT_FIRST_PART_BY_FILTER, new String[]{"%" + filter + "%", "%" + filter + "%"}); return (validate(cursor) ? new JournalRecordCursorWrapper(cursor) : null); } // Deletes all records specified in container and fit to filter public int deleteJournalRecords(IdentifiersContainer contactIds, @Nullable String filter) { if (contactIds.isEmpty()) return 0; boolean all = contactIds.isAll(); List<String> ids = contactIds.getIdentifiers(new LinkedList<String>()); // build 'WHERE' clause String clause = Common.concatClauses(new String[]{ Common.getLikeClause(JournalTable.Column.CALLER, filter), Common.getInClause(JournalTable.Column.ID, all, ids) }); // delete records SQLiteDatabase db = getWritableDatabase(); return db.delete(JournalTable.NAME, clause, null); } // Deletes record by specified id public boolean deleteJournalRecord(long id) { SQLiteDatabase db = getWritableDatabase(); return (db.delete(JournalTable.NAME, JournalTable.Column.ID + " = " + id, null) > 0); } // Writes journal record public long addJournalRecord(long time, @NonNull String caller, String number, String text) { if (number != null && number.equals(caller)) { number = null; } SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put(JournalTable.Column.TIME, time); values.put(JournalTable.Column.CALLER, caller); values.put(JournalTable.Column.NUMBER, number); values.put(JournalTable.Column.TEXT, text); return db.insert(JournalTable.NAME, null, values); } //---------------------------------------------------------------- // Contact number table scheme private static class ContactNumberTable { static final String NAME = "number"; static class Column { static final String ID = "_id"; static final String NUMBER = "number"; static final String TYPE = "type"; static final String CONTACT_ID = "contact_id"; } static class Statement { static final String CREATE = "CREATE TABLE " + ContactNumberTable.NAME + "(" + Column.ID + " INTEGER PRIMARY KEY NOT NULL, " + Column.NUMBER + " TEXT NOT NULL, " + Column.TYPE + " INTEGER NOT NULL, " + Column.CONTACT_ID + " INTEGER NOT NULL, " + "FOREIGN KEY(" + Column.CONTACT_ID + ") REFERENCES " + ContactTable.NAME + "(" + ContactTable.Column.ID + ")" + " ON DELETE CASCADE " + ")"; static final String SELECT_BY_CONTACT_ID = "SELECT * " + " FROM " + ContactNumberTable.NAME + " WHERE " + Column.CONTACT_ID + " = ? " + " ORDER BY " + Column.NUMBER + " ASC"; static final String SELECT_BY_TYPE_AND_NUMBER = "SELECT * " + " FROM " + ContactNumberTable.NAME + " WHERE " + Column.TYPE + " = ? " + " AND " + Column.NUMBER + " = ? "; static final String SELECT_BY_NUMBER = "SELECT * " + " FROM " + ContactNumberTable.NAME + " WHERE (" + Column.TYPE + " = " + ContactNumber.TYPE_EQUALS + " AND " + " ? = " + Column.NUMBER + ") OR (" + Column.TYPE + " = " + ContactNumber.TYPE_STARTS + " AND " + " ? LIKE " + Column.NUMBER + "||'%') OR (" + Column.TYPE + " = " + ContactNumber.TYPE_ENDS + " AND " + " ? LIKE '%'||" + Column.NUMBER + ") OR (" + Column.TYPE + " = " + ContactNumber.TYPE_CONTAINS + " AND " + " ? LIKE '%'||" + Column.NUMBER + "||'%')"; } } // ContactsNumber table item public static class ContactNumber { public static final int TYPE_EQUALS = 0; public static final int TYPE_CONTAINS = 1; public static final int TYPE_STARTS = 2; public static final int TYPE_ENDS = 3; public final long id; public final String number; public final int type; public final long contactId; public ContactNumber(long id, @NonNull String number, long contactId) { this(id, number, TYPE_EQUALS, contactId); } public ContactNumber(long id, @NonNull String number, int type, long contactId) { this.id = id; this.number = number; this.type = type; this.contactId = contactId; } } // ContactsNumber item cursor wrapper private class ContactNumberCursorWrapper extends CursorWrapper { private final int ID; private final int NUMBER; private final int TYPE; private final int CONTACT_ID; ContactNumberCursorWrapper(Cursor cursor) { super(cursor); cursor.moveToFirst(); ID = cursor.getColumnIndex(ContactNumberTable.Column.ID); NUMBER = cursor.getColumnIndex(ContactNumberTable.Column.NUMBER); TYPE = cursor.getColumnIndex(ContactNumberTable.Column.TYPE); CONTACT_ID = cursor.getColumnIndex(ContactNumberTable.Column.CONTACT_ID); } ContactNumber getNumber() { long id = getLong(ID); String number = getString(NUMBER); int type = getInt(TYPE); long contactId = getLong(CONTACT_ID); return new ContactNumber(id, number, type, contactId); } } // Deletes contact number by id private int deleteContactNumber(long id) { SQLiteDatabase db = getWritableDatabase(); return db.delete(ContactNumberTable.NAME, ContactNumberTable.Column.ID + " = " + id, null); } // Selects contact numbers by contact id @Nullable private ContactNumberCursorWrapper getContactNumbersByContactId(long contactId) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactNumberTable.Statement.SELECT_BY_CONTACT_ID, new String[]{String.valueOf(contactId)}); return (validate(cursor) ? new ContactNumberCursorWrapper(cursor) : null); } // Searches contact numbers by number value @Nullable private ContactNumberCursorWrapper getContactNumbersByNumber(String number) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactNumberTable.Statement.SELECT_BY_NUMBER, new String[]{number, number, number, number}); return (validate(cursor) ? new ContactNumberCursorWrapper(cursor) : null); } // Searches contact numbers by type and value @Nullable private ContactNumberCursorWrapper getContactNumbersByTypeAndNumber(int numberType, String number) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactNumberTable.Statement.SELECT_BY_TYPE_AND_NUMBER, new String[]{String.valueOf(numberType), number}); return (validate(cursor) ? new ContactNumberCursorWrapper(cursor) : null); } // Searches contact numbers by number value private List<ContactNumber> getContactNumbers(String number) { List<ContactNumber> list = new LinkedList<>(); ContactNumberCursorWrapper cursor = getContactNumbersByNumber(number); if (cursor != null) { do { list.add(cursor.getNumber()); } while (cursor.moveToNext()); cursor.close(); } return list; } // Searches contact numbers by numbers types and values // This method is mainly needed for retrieving actual ContactNumber.id and/or ContactNumber.contactId private List<ContactNumber> getContactNumbers(List<ContactNumber> numbers) { List<ContactNumber> list = new LinkedList<>(); for (ContactNumber number : numbers) { ContactNumberCursorWrapper cursor = getContactNumbersByTypeAndNumber(number.type, number.number); if (cursor != null) { do { list.add(cursor.getNumber()); } while (cursor.moveToNext()); cursor.close(); } } return list; } //---------------------------------------------------------------- // Table of contacts (black/white lists) private static class ContactTable { static final String NAME = "contact"; static class Column { static final String ID = "_id"; static final String NAME = "name"; static final String TYPE = "type"; // black/white type } static class Statement { static final String CREATE = "CREATE TABLE " + ContactTable.NAME + "(" + Column.ID + " INTEGER PRIMARY KEY NOT NULL, " + Column.NAME + " TEXT NOT NULL, " + Column.TYPE + " INTEGER NOT NULL DEFAULT 0 " + ")"; static final String SELECT_BY_TYPE = "SELECT * " + " FROM " + ContactTable.NAME + " WHERE " + Column.TYPE + " = ? " + " ORDER BY " + Column.NAME + " ASC"; static final String SELECT_BY_NAME = "SELECT * " + " FROM " + ContactTable.NAME + " WHERE " + Column.NAME + " = ? "; static final String SELECT_BY_TYPE_AND_NAME = "SELECT * " + " FROM " + ContactTable.NAME + " WHERE " + Column.TYPE + " = ? " + " AND " + Column.NAME + " = ? "; static final String SELECT_BY_ID = "SELECT * " + " FROM " + ContactTable.NAME + " WHERE " + Column.ID + " = ? "; static final String SELECT_BY_FILTER = "SELECT * " + " FROM " + ContactTable.NAME + " WHERE " + Column.TYPE + " = ? " + " AND " + Column.NAME + " LIKE ? " + " ORDER BY " + Column.NAME + " ASC"; } } // The contact public static class Contact { public static final int TYPE_BLACK_LIST = 1; public static final int TYPE_WHITE_LIST = 2; public final long id; public final String name; public final int type; public final List<ContactNumber> numbers; Contact(long id, @NonNull String name, int type, @NonNull List<ContactNumber> numbers) { this.id = id; this.name = name; this.type = type; this.numbers = numbers; } } // Source of the contact public interface ContactSource { Contact getContact(); } // Contact cursor wrapper public class ContactCursorWrapper extends CursorWrapper implements ContactSource { private final int ID; private final int NAME; private final int TYPE; ContactCursorWrapper(Cursor cursor) { super(cursor); cursor.moveToFirst(); ID = cursor.getColumnIndex(ContactTable.Column.ID); NAME = getColumnIndex(ContactTable.Column.NAME); TYPE = getColumnIndex(ContactTable.Column.TYPE); } @Override public Contact getContact() { return getContact(true); } Contact getContact(boolean withNumbers) { long id = getLong(ID); String name = getString(NAME); int type = getInt(TYPE); List<ContactNumber> numbers = new LinkedList<>(); if (withNumbers) { ContactNumberCursorWrapper cursor = getContactNumbersByContactId(id); if (cursor != null) { do { numbers.add(cursor.getNumber()); } while (cursor.moveToNext()); cursor.close(); } } return new Contact(id, name, type, numbers); } } // Searches all contacts by type @Nullable private ContactCursorWrapper getContacts(int contactType) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactTable.Statement.SELECT_BY_TYPE, new String[]{String.valueOf(contactType)}); return (validate(cursor) ? new ContactCursorWrapper(cursor) : null); } // Searches all contacts by type filtering by passed filter @Nullable public ContactCursorWrapper getContacts(int contactType, @Nullable String filter) { if (filter == null) { return getContacts(contactType); } SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactTable.Statement.SELECT_BY_FILTER, new String[]{String.valueOf(contactType), "%" + filter + "%"}); return (validate(cursor) ? new ContactCursorWrapper(cursor) : null); } // Searches contact by name @Nullable public ContactCursorWrapper getContact(String contactName) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactTable.Statement.SELECT_BY_NAME, new String[]{contactName}); return (validate(cursor) ? new ContactCursorWrapper(cursor) : null); } // Searches contact by type and name @Nullable private ContactCursorWrapper getContact(int contactType, String contactName) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactTable.Statement.SELECT_BY_TYPE_AND_NAME, new String[]{String.valueOf(contactType), contactName}); return (validate(cursor) ? new ContactCursorWrapper(cursor) : null); } // Searches contact by id @Nullable public ContactCursorWrapper getContact(long contactId) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( ContactTable.Statement.SELECT_BY_ID, new String[]{String.valueOf(contactId)}); return (validate(cursor) ? new ContactCursorWrapper(cursor) : null); } // Adds a new contact and returns contact id or -1 on error private long addContact(int contactType, @NonNull String contactName) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put(ContactTable.Column.NAME, contactName); values.put(ContactTable.Column.TYPE, contactType); return db.insert(ContactTable.NAME, null, values); } // Adds a contact with numbers and returns contact id or -1 on error. // If adding numbers already belong to some contacts - removes them at first. public long addContact(int contactType, @NonNull String contactName, @NonNull List<ContactNumber> numbers) { if (numbers.size() == 0) return -1; long contactId = -1; SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { // delete existing numbers from contacts deleteContactNumbers(numbers); // try to find existing contact with the same name and type ContactCursorWrapper cursor = getContact(contactType, contactName); if (cursor != null) { Contact contact = cursor.getContact(false); contactId = contact.id; cursor.close(); } // contact was not found if (contactId < 0) { // add a new one contactId = addContact(contactType, contactName); } // add numbers to the contact if (contactId >= 0) { for (ContactNumber number : numbers) { ContentValues values = new ContentValues(); values.put(ContactNumberTable.Column.NUMBER, number.number); values.put(ContactNumberTable.Column.TYPE, number.type); values.put(ContactNumberTable.Column.CONTACT_ID, contactId); if (db.insert(ContactNumberTable.NAME, null, values) < 0) { return -1; } } db.setTransactionSuccessful(); } } finally { db.endTransaction(); } return contactId; } // Deletes contact numbers if they exist. // After that deletes parent contacts if they are empty. private void deleteContactNumbers(List<ContactNumber> numbers) { // get full initialized contact numbers by type and value List<ContactNumber> contactNumbers = getContactNumbers(numbers); // delete found numbers and collect ids of parent contacts Set<Long> contactIds = new TreeSet<>(); for (ContactNumber number : contactNumbers) { if (deleteContactNumber(number.id) > 0) { contactIds.add(number.contactId); } } // check contacts consistency for (Long contactId : contactIds) { // if contact does not have any number ContactNumberCursorWrapper cursor = getContactNumbersByContactId(contactId); if (cursor == null) { // delete it deleteContact(contactId); } else { cursor.close(); } } } // Adds contact with single number public long addContact(int contactType, @NonNull String contactName, @NonNull ContactNumber contactNumber) { List<ContactNumber> numbers = new LinkedList<>(); numbers.add(contactNumber); return addContact(contactType, contactName, numbers); } // Adds contact with single number with default type public long addContact(int contactType, @NonNull String contactName, @Nullable String number) { if (number == null) { number = contactName; } List<ContactNumber> numbers = new LinkedList<>(); numbers.add(new ContactNumber(0, number, 0)); return addContact(contactType, contactName, numbers); } // Deletes all contacts which are specified in container with specified type public int deleteContacts(int contactType, IdentifiersContainer contactIds, @Nullable String filter) { if (contactIds.isEmpty()) return 0; boolean all = contactIds.isAll(); List<String> ids = contactIds.getIdentifiers(new LinkedList<String>()); // build 'WHERE' clause String clause = Common.concatClauses(new String[]{ ContactTable.Column.TYPE + " = " + contactType, Common.getLikeClause(ContactTable.Column.NAME, filter), Common.getInClause(ContactTable.Column.ID, all, ids) }); // delete contacts SQLiteDatabase db = getWritableDatabase(); return db.delete(ContactTable.NAME, clause, null); } // Deletes contact by id public int deleteContact(long contactId) { SQLiteDatabase db = getWritableDatabase(); return db.delete(ContactTable.NAME, ContactTable.Column.ID + " = " + contactId, null); } // Searches contacts by contact numbers (retrieving them by ContactNumber.contactId) private List<Contact> getContacts(List<ContactNumber> numbers, boolean withNumbers) { List<Contact> contacts = new LinkedList<>(); for (ContactNumber contactNumber : numbers) { ContactCursorWrapper cursor = getContact(contactNumber.contactId); if (cursor != null) { contacts.add(cursor.getContact(withNumbers)); cursor.close(); } } return contacts; } // Searches contacts by contact number public List<Contact> getContacts(String number, boolean withNumbers) { List<ContactNumber> numbers = getContactNumbers(number); return getContacts(numbers, withNumbers); } // Searches contact by name and number @Nullable public Contact getContact(String contactName, String number) { List<Contact> contacts = getContacts(number, false); for (Contact contact : contacts) { if (contact.name.equals(contactName)) { return contact; } } return null; } // Moves the contact to the opposite type list public long moveContact(Contact contact) { int type = reverseContactType(contact.type); return addContact(type, contact.name, contact.numbers); } // Reverses passed contact type private int reverseContactType(int type) { return (type == Contact.TYPE_BLACK_LIST ? Contact.TYPE_WHITE_LIST : Contact.TYPE_BLACK_LIST); } //---------------------------------------------------------------- // Table of settings private static class SettingsTable { static final String NAME = "settings"; static class Column { static final String ID = "_id"; static final String NAME = "name"; static final String VALUE = "value"; } static class Statement { static final String CREATE = "CREATE TABLE " + SettingsTable.NAME + "(" + Column.ID + " INTEGER PRIMARY KEY NOT NULL, " + Column.NAME + " TEXT NOT NULL, " + Column.VALUE + " TEXT " + ")"; static final String SELECT_BY_NAME = "SELECT * " + " FROM " + SettingsTable.NAME + " WHERE " + Column.NAME + " = ? "; } } // Settings item private class SettingsItem { final long id; final String name; final String value; SettingsItem(long id, String name, String value) { this.id = id; this.name = name; this.value = value; } } // SettingsItem cursor wrapper private class SettingsItemCursorWrapper extends CursorWrapper { private final int ID; private final int NAME; private final int VALUE; SettingsItemCursorWrapper(Cursor cursor) { super(cursor); cursor.moveToFirst(); ID = cursor.getColumnIndex(SettingsTable.Column.ID); NAME = cursor.getColumnIndex(SettingsTable.Column.NAME); VALUE = cursor.getColumnIndex(SettingsTable.Column.VALUE); } SettingsItem getSettings() { long id = getLong(ID); String name = getString(NAME); String value = getString(VALUE); return new SettingsItem(id, name, value); } } // Selects settings by name @Nullable private SettingsItemCursorWrapper getSettings(@NonNull String name) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.rawQuery( SettingsTable.Statement.SELECT_BY_NAME, new String[]{name}); return (validate(cursor) ? new SettingsItemCursorWrapper(cursor) : null); } // Selects value of settings by name @Nullable public String getSettingsValue(@NonNull String name) { SettingsItemCursorWrapper cursor = getSettings(name); if (cursor != null) { SettingsItem item = cursor.getSettings(); cursor.close(); return item.value; } return null; } // Sets value of settings with specified name public boolean setSettingsValue(@NonNull String name, @NonNull String value) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SettingsTable.Column.VALUE, value); // try to update value int n = db.update(SettingsTable.NAME, values, SettingsTable.Column.NAME + " = ? ", new String[]{name}); if (n == 0) { // try to add name/value values.put(SettingsTable.Column.NAME, name); return db.insert(SettingsTable.NAME, null, values) >= 0; } return true; } }