321 lines
11 KiB
Java
321 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* 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.android.dialer.speeddial.database;
|
|
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.database.sqlite.SQLiteOpenHelper;
|
|
import android.text.TextUtils;
|
|
import com.android.dialer.common.Assert;
|
|
import com.android.dialer.common.database.Selection;
|
|
import com.android.dialer.speeddial.database.SpeedDialEntry.Channel;
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* {@link SpeedDialEntryDao} implemented as an SQLite database.
|
|
*
|
|
* @see SpeedDialEntryDao
|
|
*/
|
|
public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper
|
|
implements SpeedDialEntryDao {
|
|
|
|
/**
|
|
* If the pinned position is absent, then we need to write an impossible value in the table like
|
|
* -1 so that it doesn't default to 0. When we read this value from the table, we'll translate it
|
|
* to Optional.absent() in the resulting {@link SpeedDialEntry}.
|
|
*/
|
|
private static final int PINNED_POSITION_ABSENT = -1;
|
|
|
|
private static final int DATABASE_VERSION = 2;
|
|
private static final String DATABASE_NAME = "CPSpeedDialEntry";
|
|
|
|
// Column names
|
|
private static final String TABLE_NAME = "speed_dial_entries";
|
|
private static final String ID = "id";
|
|
private static final String PINNED_POSITION = "pinned_position";
|
|
private static final String CONTACT_ID = "contact_id";
|
|
private static final String LOOKUP_KEY = "lookup_key";
|
|
private static final String PHONE_NUMBER = "phone_number";
|
|
private static final String PHONE_TYPE = "phone_type";
|
|
private static final String PHONE_LABEL = "phone_label";
|
|
private static final String PHONE_TECHNOLOGY = "phone_technology";
|
|
|
|
// Column positions
|
|
private static final int POSITION_ID = 0;
|
|
private static final int POSITION_PINNED_POSITION = 1;
|
|
private static final int POSITION_CONTACT_ID = 2;
|
|
private static final int POSITION_LOOKUP_KEY = 3;
|
|
private static final int POSITION_PHONE_NUMBER = 4;
|
|
private static final int POSITION_PHONE_TYPE = 5;
|
|
private static final int POSITION_PHONE_LABEL = 6;
|
|
private static final int POSITION_PHONE_TECHNOLOGY = 7;
|
|
|
|
// Create Table Query
|
|
private static final String CREATE_TABLE_SQL =
|
|
"create table if not exists "
|
|
+ TABLE_NAME
|
|
+ " ("
|
|
+ (ID + " integer primary key, ")
|
|
+ (PINNED_POSITION + " integer, ")
|
|
+ (CONTACT_ID + " integer, ")
|
|
+ (LOOKUP_KEY + " text, ")
|
|
+ (PHONE_NUMBER + " text, ")
|
|
+ (PHONE_TYPE + " integer, ")
|
|
+ (PHONE_LABEL + " text, ")
|
|
+ (PHONE_TECHNOLOGY + " integer ")
|
|
+ ");";
|
|
|
|
private static final String DELETE_TABLE_SQL = "drop table if exists " + TABLE_NAME;
|
|
|
|
public SpeedDialEntryDatabaseHelper(Context context) {
|
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(SQLiteDatabase db) {
|
|
db.execSQL(CREATE_TABLE_SQL);
|
|
}
|
|
|
|
@Override
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
// TODO(calderwoodra): handle upgrades more elegantly
|
|
db.execSQL(DELETE_TABLE_SQL);
|
|
this.onCreate(db);
|
|
}
|
|
|
|
@Override
|
|
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
// TODO(calderwoodra): handle upgrades more elegantly
|
|
this.onUpgrade(db, oldVersion, newVersion);
|
|
}
|
|
|
|
@Override
|
|
public ImmutableList<SpeedDialEntry> getAllEntries() {
|
|
List<SpeedDialEntry> entries = new ArrayList<>();
|
|
|
|
String query = "SELECT * FROM " + TABLE_NAME;
|
|
try (SQLiteDatabase db = getReadableDatabase();
|
|
Cursor cursor = db.rawQuery(query, null)) {
|
|
cursor.moveToPosition(-1);
|
|
while (cursor.moveToNext()) {
|
|
String number = cursor.getString(POSITION_PHONE_NUMBER);
|
|
Channel channel = null;
|
|
if (!TextUtils.isEmpty(number)) {
|
|
channel =
|
|
Channel.builder()
|
|
.setNumber(number)
|
|
.setPhoneType(cursor.getInt(POSITION_PHONE_TYPE))
|
|
.setLabel(Optional.fromNullable(cursor.getString(POSITION_PHONE_LABEL)).or(""))
|
|
.setTechnology(cursor.getInt(POSITION_PHONE_TECHNOLOGY))
|
|
.build();
|
|
}
|
|
|
|
Optional<Integer> pinnedPosition = Optional.of(cursor.getInt(POSITION_PINNED_POSITION));
|
|
if (pinnedPosition.or(PINNED_POSITION_ABSENT) == PINNED_POSITION_ABSENT) {
|
|
pinnedPosition = Optional.absent();
|
|
}
|
|
|
|
SpeedDialEntry entry =
|
|
SpeedDialEntry.builder()
|
|
.setDefaultChannel(channel)
|
|
.setContactId(cursor.getLong(POSITION_CONTACT_ID))
|
|
.setLookupKey(cursor.getString(POSITION_LOOKUP_KEY))
|
|
.setPinnedPosition(pinnedPosition)
|
|
.setId(cursor.getLong(POSITION_ID))
|
|
.build();
|
|
entries.add(entry);
|
|
}
|
|
}
|
|
return ImmutableList.copyOf(entries);
|
|
}
|
|
|
|
@Override
|
|
public ImmutableMap<SpeedDialEntry, Long> insert(ImmutableList<SpeedDialEntry> entries) {
|
|
if (entries.isEmpty()) {
|
|
return ImmutableMap.of();
|
|
}
|
|
|
|
SQLiteDatabase db = getWritableDatabase();
|
|
db.beginTransaction();
|
|
try {
|
|
ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap = insert(db, entries);
|
|
db.setTransactionSuccessful();
|
|
return insertedEntriesToIdsMap;
|
|
} finally {
|
|
db.endTransaction();
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
private ImmutableMap<SpeedDialEntry, Long> insert(
|
|
SQLiteDatabase writeableDatabase, ImmutableList<SpeedDialEntry> entries) {
|
|
ImmutableMap.Builder<SpeedDialEntry, Long> insertedEntriesToIdsMap = ImmutableMap.builder();
|
|
for (SpeedDialEntry entry : entries) {
|
|
Assert.checkArgument(entry.id() == null);
|
|
long id = writeableDatabase.insert(TABLE_NAME, null, buildContentValuesWithoutId(entry));
|
|
if (id == -1L) {
|
|
throw Assert.createUnsupportedOperationFailException(
|
|
"Attempted to insert a row that already exists.");
|
|
}
|
|
// It's impossible to insert two identical entries but this is an important assumption we need
|
|
// to verify because there's an assumption that each entry will correspond to exactly one id.
|
|
// ImmutableMap#put verifies this check for us.
|
|
insertedEntriesToIdsMap.put(entry, id);
|
|
}
|
|
return insertedEntriesToIdsMap.build();
|
|
}
|
|
|
|
@Override
|
|
public long insert(SpeedDialEntry entry) {
|
|
long updateRowId;
|
|
try (SQLiteDatabase db = getWritableDatabase()) {
|
|
updateRowId = db.insert(TABLE_NAME, null, buildContentValuesWithoutId(entry));
|
|
}
|
|
if (updateRowId == -1) {
|
|
throw Assert.createUnsupportedOperationFailException(
|
|
"Attempted to insert a row that already exists.");
|
|
}
|
|
return updateRowId;
|
|
}
|
|
|
|
@Override
|
|
public void update(ImmutableList<SpeedDialEntry> entries) {
|
|
if (entries.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
SQLiteDatabase db = getWritableDatabase();
|
|
db.beginTransaction();
|
|
try {
|
|
update(db, entries);
|
|
db.setTransactionSuccessful();
|
|
} finally {
|
|
db.endTransaction();
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
private void update(SQLiteDatabase writeableDatabase, ImmutableList<SpeedDialEntry> entries) {
|
|
for (SpeedDialEntry entry : entries) {
|
|
int count =
|
|
writeableDatabase.update(
|
|
TABLE_NAME,
|
|
buildContentValuesWithId(entry),
|
|
ID + " = ?",
|
|
new String[] {Long.toString(entry.id())});
|
|
if (count != 1) {
|
|
throw Assert.createUnsupportedOperationFailException(
|
|
"Attempted to update an undetermined number of rows: " + count);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ContentValues buildContentValuesWithId(SpeedDialEntry entry) {
|
|
return buildContentValues(entry, true);
|
|
}
|
|
|
|
private ContentValues buildContentValuesWithoutId(SpeedDialEntry entry) {
|
|
return buildContentValues(entry, false);
|
|
}
|
|
|
|
private ContentValues buildContentValues(SpeedDialEntry entry, boolean includeId) {
|
|
ContentValues values = new ContentValues();
|
|
if (includeId) {
|
|
values.put(ID, entry.id());
|
|
}
|
|
values.put(PINNED_POSITION, entry.pinnedPosition().or(PINNED_POSITION_ABSENT));
|
|
values.put(CONTACT_ID, entry.contactId());
|
|
values.put(LOOKUP_KEY, entry.lookupKey());
|
|
if (entry.defaultChannel() != null) {
|
|
values.put(PHONE_NUMBER, entry.defaultChannel().number());
|
|
values.put(PHONE_TYPE, entry.defaultChannel().phoneType());
|
|
values.put(PHONE_LABEL, entry.defaultChannel().label());
|
|
values.put(PHONE_TECHNOLOGY, entry.defaultChannel().technology());
|
|
}
|
|
return values;
|
|
}
|
|
|
|
@Override
|
|
public void delete(ImmutableList<Long> ids) {
|
|
if (ids.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
try (SQLiteDatabase db = getWritableDatabase()) {
|
|
delete(db, ids);
|
|
}
|
|
}
|
|
|
|
private void delete(SQLiteDatabase writeableDatabase, ImmutableList<Long> ids) {
|
|
List<String> idStrings = new ArrayList<>();
|
|
for (Long id : ids) {
|
|
idStrings.add(Long.toString(id));
|
|
}
|
|
|
|
Selection selection = Selection.builder().and(Selection.column(ID).in(idStrings)).build();
|
|
int count =
|
|
writeableDatabase.delete(
|
|
TABLE_NAME, selection.getSelection(), selection.getSelectionArgs());
|
|
if (count != ids.size()) {
|
|
throw Assert.createUnsupportedOperationFailException(
|
|
"Attempted to delete an undetermined number of rows: " + count);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ImmutableMap<SpeedDialEntry, Long> insertUpdateAndDelete(
|
|
ImmutableList<SpeedDialEntry> entriesToInsert,
|
|
ImmutableList<SpeedDialEntry> entriesToUpdate,
|
|
ImmutableList<Long> entriesToDelete) {
|
|
if (entriesToInsert.isEmpty() && entriesToUpdate.isEmpty() && entriesToDelete.isEmpty()) {
|
|
return ImmutableMap.of();
|
|
}
|
|
SQLiteDatabase db = getWritableDatabase();
|
|
db.beginTransaction();
|
|
try {
|
|
ImmutableMap<SpeedDialEntry, Long> insertedEntriesToIdsMap = insert(db, entriesToInsert);
|
|
update(db, entriesToUpdate);
|
|
delete(db, entriesToDelete);
|
|
db.setTransactionSuccessful();
|
|
return insertedEntriesToIdsMap;
|
|
} finally {
|
|
db.endTransaction();
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deleteAll() {
|
|
SQLiteDatabase db = getWritableDatabase();
|
|
db.beginTransaction();
|
|
try {
|
|
// Passing null into where clause will delete all rows
|
|
db.delete(TABLE_NAME, /* whereClause=*/ null, null);
|
|
db.setTransactionSuccessful();
|
|
} finally {
|
|
db.endTransaction();
|
|
db.close();
|
|
}
|
|
}
|
|
}
|