From e8dc0b5d5ee87b658a4bdf8f4285113db89447d8 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Fri, 30 Mar 2018 14:57:12 -0700 Subject: Add SpeedDialEntry database helper. Bug: 36841782 Test: SpeedDialEntryDatabaseHelperTest PiperOrigin-RevId: 191121137 Change-Id: Iab712d5fac56e515bd4e977282656bf80db4337d --- .../dialer/speeddial/database/SpeedDialEntry.java | 116 +++++++++++ .../speeddial/database/SpeedDialEntryDao.java | 57 ++++++ .../database/SpeedDialEntryDatabaseHelper.java | 221 +++++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 java/com/android/dialer/speeddial/database/SpeedDialEntry.java create mode 100644 java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java create mode 100644 java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java (limited to 'java') diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java new file mode 100644 index 000000000..aa90909f1 --- /dev/null +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java @@ -0,0 +1,116 @@ +/* + * 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.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import com.google.auto.value.AutoValue; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** POJO representation of database rows returned by {@link SpeedDialEntryDao}. */ +@AutoValue +public abstract class SpeedDialEntry { + + /** Unique ID */ + public abstract long id(); + + /** @see {@link Contacts#_ID} */ + public abstract long contactId(); + + /** @see {@link Contacts#LOOKUP_KEY} */ + public abstract String lookupKey(); + + /** + * {@link Channel} that is associated with this entry. + * + *

Contacts with multiple channels do not have a default until specified by the user. Once the + * default channel is determined, all calls should be placed to this channel. + */ + @Nullable + public abstract Channel defaultChannel(); + + public abstract Builder toBuilder(); + + public static Builder builder() { + return new AutoValue_SpeedDialEntry.Builder(); + } + + /** Builder class for speed dial entry. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setId(long id); + + public abstract Builder setContactId(long contactId); + + public abstract Builder setLookupKey(String lookupKey); + + public abstract Builder setDefaultChannel(@Nullable Channel defaultChannel); + + public abstract SpeedDialEntry build(); + } + + /** POJO representation of a relevant phone number columns in {@link SpeedDialEntryDao}. */ + @AutoValue + public abstract static class Channel { + + public static final int UNKNOWN = 0; + public static final int VOICE = 1; + public static final int VIDEO = 2; + + /** Whether the Channel is for an audio or video call. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNKNOWN, VOICE, VIDEO}) + public @interface Technology {} + + /** + * Raw phone number as the user entered it. + * + * @see {@link Phone#NUMBER} + */ + public abstract String number(); + + /** + * Label that the user associated with this number like {@link Phone#TYPE_WORK}, {@link + * Phone#TYPE_HOME}, ect. + * + * @see {@link Phone#LABEL} + */ + public abstract String label(); + + public abstract @Technology int technology(); + + public static Builder builder() { + return new AutoValue_SpeedDialEntry_Channel.Builder(); + } + + /** Builder class for {@link Channel}. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setNumber(String number); + + public abstract Builder setLabel(String label); + + public abstract Builder setTechnology(@Technology int technology); + + public abstract Channel build(); + } + } +} diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java new file mode 100644 index 000000000..39cb115c8 --- /dev/null +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDao.java @@ -0,0 +1,57 @@ +/* + * 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 java.util.List; + +/** Interface that databases support speed dial entries should implement. */ +public interface SpeedDialEntryDao { + + /** Return all entries in the database */ + List getAllEntries(); + + /** + * Insert new entries. + * + *

Fails if any of the {@link SpeedDialEntry#id()} already exist. + */ + void insert(List entries); + + /** + * Insert a new entry. + * + *

Fails if the {@link SpeedDialEntry#id()} already exists. + */ + long insert(SpeedDialEntry entry); + + /** + * Updates existing entries based on {@link SpeedDialEntry#id}. + * + *

Fails if the {@link SpeedDialEntry#id()} doesn't exist. + */ + void update(List entries); + + /** + * Delete the passed in entries based on {@link SpeedDialEntry#id}. + * + *

Fails if the {@link SpeedDialEntry#id()} doesn't exist. + */ + void delete(List entries); + + /** Delete all entries in the database. */ + void deleteAll(); +} diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java new file mode 100644 index 000000000..1812dbdc0 --- /dev/null +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java @@ -0,0 +1,221 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +/** {@link SpeedDialEntryDao} implemented as an SQLite database. */ +public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper + implements SpeedDialEntryDao { + + private static final int DATABASE_VERSION = 1; + 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 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_LABEL = "phone_label"; + private static final String PHONE_TYPE = "phone_type"; + + // Column positions + private static final int POSITION_ID = 0; + private static final int POSITION_CONTACT_ID = 1; + private static final int POSITION_LOOKUP_KEY = 2; + private static final int POSITION_PHONE_NUMBER = 3; + private static final int POSITION_PHONE_LABEL = 4; + private static final int POSITION_PHONE_TYPE = 5; + + // Create Table Query + private static final String CREATE_TABLE_SQL = + "create table if not exists " + + TABLE_NAME + + " (" + + (ID + " integer primary key, ") + + (CONTACT_ID + " integer, ") + + (LOOKUP_KEY + " text, ") + + (PHONE_NUMBER + " text, ") + + (PHONE_LABEL + " text, ") + + (PHONE_TYPE + " 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 List getAllEntries() { + List 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()) { + Channel channel = + Channel.builder() + .setNumber(cursor.getString(POSITION_PHONE_NUMBER)) + .setLabel(cursor.getString(POSITION_PHONE_LABEL)) + .setTechnology(cursor.getInt(POSITION_PHONE_TYPE)) + .build(); + if (TextUtils.isEmpty(channel.number())) { + channel = null; + } + SpeedDialEntry entry = + SpeedDialEntry.builder() + .setDefaultChannel(channel) + .setContactId(cursor.getLong(POSITION_CONTACT_ID)) + .setLookupKey(cursor.getString(POSITION_LOOKUP_KEY)) + .setId(cursor.getInt(POSITION_ID)) + .build(); + entries.add(entry); + } + } + return entries; + } + + @Override + public void insert(List entries) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + try { + for (SpeedDialEntry entry : entries) { + if (db.insert(TABLE_NAME, null, buildContentValues(entry)) == -1L) { + throw Assert.createUnsupportedOperationFailException( + "Attempted to insert a row that already exists."); + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + db.close(); + } + } + + @Override + public long insert(SpeedDialEntry entry) { + long updateRowId; + try (SQLiteDatabase db = getWritableDatabase()) { + updateRowId = db.insert(TABLE_NAME, null, buildContentValues(entry)); + } + if (updateRowId == -1) { + throw Assert.createUnsupportedOperationFailException( + "Attempted to insert a row that already exists."); + } + return updateRowId; + } + + @Override + public void update(List entries) { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + try { + for (SpeedDialEntry entry : entries) { + int count = + db.update( + TABLE_NAME, + buildContentValues(entry), + ID + " = ?", + new String[] {Long.toString(entry.id())}); + if (count != 1) { + throw Assert.createUnsupportedOperationFailException( + "Attempted to update an undetermined number of rows: " + count); + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + db.close(); + } + } + + private ContentValues buildContentValues(SpeedDialEntry entry) { + ContentValues values = new ContentValues(); + values.put(ID, entry.id()); + 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_LABEL, entry.defaultChannel().label()); + values.put(PHONE_TYPE, entry.defaultChannel().technology()); + } + return values; + } + + @Override + public void delete(List ids) { + List idStrings = new ArrayList<>(); + for (Long id : ids) { + idStrings.add(Long.toString(id)); + } + + Selection selection = Selection.builder().and(Selection.column(ID).in(idStrings)).build(); + try (SQLiteDatabase db = getWritableDatabase()) { + int count = db.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 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(); + } + } +} -- cgit v1.2.3