From e841eabd475f515b96349b85b48ae00ac6603d3f Mon Sep 17 00:00:00 2001 From: zachh Date: Thu, 9 Nov 2017 18:33:11 -0800 Subject: Added content provider for PhoneLookupHistory. Bug: 34672501 Test: yes PiperOrigin-RevId: 175243488 Change-Id: Iec3b5eb0e81f6e6cc04c64c3ea65c9c7fcb33fe3 --- java/com/android/dialer/constants/Constants.java | 3 + .../dialer/constants/aospdialer/ConstantsImpl.java | 6 + .../constants/googledialer/ConstantsImpl.java | 6 + .../phonelookup/database/AndroidManifest.xml | 28 +++ .../PhoneLookupHistoryContentProvider.java | 243 +++++++++++++++++++++ .../database/PhoneLookupHistoryDatabaseHelper.java | 90 ++++++++ .../contract/PhoneLookupHistoryContract.java | 62 ++++++ 7 files changed, 438 insertions(+) create mode 100644 java/com/android/dialer/phonelookup/database/AndroidManifest.xml create mode 100644 java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java create mode 100644 java/com/android/dialer/phonelookup/database/PhoneLookupHistoryDatabaseHelper.java create mode 100644 java/com/android/dialer/phonelookup/database/contract/PhoneLookupHistoryContract.java (limited to 'java') diff --git a/java/com/android/dialer/constants/Constants.java b/java/com/android/dialer/constants/Constants.java index a0c11f5a8..644dd6b8f 100644 --- a/java/com/android/dialer/constants/Constants.java +++ b/java/com/android/dialer/constants/Constants.java @@ -57,6 +57,9 @@ public abstract class Constants { @NonNull public abstract String getAnnotatedCallLogProviderAuthority(); + @NonNull + public abstract String getPhoneLookupHistoryProviderAuthority(); + public abstract String getUserAgent(Context context); @NonNull diff --git a/java/com/android/dialer/constants/aospdialer/ConstantsImpl.java b/java/com/android/dialer/constants/aospdialer/ConstantsImpl.java index c332e8281..e7eab68b1 100644 --- a/java/com/android/dialer/constants/aospdialer/ConstantsImpl.java +++ b/java/com/android/dialer/constants/aospdialer/ConstantsImpl.java @@ -42,6 +42,12 @@ public class ConstantsImpl extends Constants { return "com.android.dialer.annotatedcalllog"; } + @NonNull + @Override + public String getPhoneLookupHistoryProviderAuthority() { + return "com.android.dialer.phonelookuphistory"; + } + @Override public String getUserAgent(Context context) { return null; diff --git a/java/com/android/dialer/constants/googledialer/ConstantsImpl.java b/java/com/android/dialer/constants/googledialer/ConstantsImpl.java index 003f748af..8580fce83 100644 --- a/java/com/android/dialer/constants/googledialer/ConstantsImpl.java +++ b/java/com/android/dialer/constants/googledialer/ConstantsImpl.java @@ -44,6 +44,12 @@ public class ConstantsImpl extends Constants { return "com.google.android.dialer.annotatedcalllog"; } + @NonNull + @Override + public String getPhoneLookupHistoryProviderAuthority() { + return "com.google.android.dialer.phonelookuphistory"; + } + @Override public String getUserAgent(Context context) { StringBuilder userAgent = new StringBuilder("GoogleDialer "); diff --git a/java/com/android/dialer/phonelookup/database/AndroidManifest.xml b/java/com/android/dialer/phonelookup/database/AndroidManifest.xml new file mode 100644 index 000000000..5645133f1 --- /dev/null +++ b/java/com/android/dialer/phonelookup/database/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java new file mode 100644 index 000000000..27041e746 --- /dev/null +++ b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryContentProvider.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2017 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.phonelookup.database; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; + +/** + * {@link ContentProvider} for the PhoneLookupHistory. + * + *

Operations may run against the entire table using the URI: + * + *

+ *   content://com.android.dialer.phonelookuphistory/PhoneLookupHistory
+ * 
+ * + *

Or against an individual row keyed by normalized number where the number is the last component + * in the URI path, for example: + * + *

+ *     content://com.android.dialer.phonelookuphistory/PhoneLookupHistory/+11234567890
+ * 
+ */ +public class PhoneLookupHistoryContentProvider extends ContentProvider { + + /** + * We sometimes run queries where we potentially pass numbers into a where clause using the + * (?,?,?,...) syntax. The maximum number of host parameters is 999, so that's the maximum size + * this table can be. See https://www.sqlite.org/limits.html for more details. + */ + private static final int MAX_ROWS = 999; + + // For operations against: content://com.android.dialer.phonelookuphistory/PhoneLookupHistory + private static final int PHONE_LOOKUP_HISTORY_TABLE_CODE = 1; + // For operations against: content://com.android.dialer.phonelookuphistory/PhoneLookupHistory/+123 + private static final int PHONE_LOOKUP_HISTORY_TABLE_ID_CODE = 2; + + private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + static { + uriMatcher.addURI( + PhoneLookupHistoryContract.AUTHORITY, + PhoneLookupHistory.TABLE, + PHONE_LOOKUP_HISTORY_TABLE_CODE); + uriMatcher.addURI( + PhoneLookupHistoryContract.AUTHORITY, + PhoneLookupHistory.TABLE + "/*", // The last path component should be a normalized number + PHONE_LOOKUP_HISTORY_TABLE_ID_CODE); + } + + private PhoneLookupHistoryDatabaseHelper databaseHelper; + + @Override + public boolean onCreate() { + databaseHelper = new PhoneLookupHistoryDatabaseHelper(getContext(), MAX_ROWS); + return true; + } + + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + @Nullable String[] projection, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables(PhoneLookupHistory.TABLE); + int match = uriMatcher.match(uri); + switch (match) { + case PHONE_LOOKUP_HISTORY_TABLE_ID_CODE: + queryBuilder.appendWhere( + PhoneLookupHistory.NORMALIZED_NUMBER + + "=" + + DatabaseUtils.sqlEscapeString(uri.getLastPathSegment())); + // fall through + case PHONE_LOOKUP_HISTORY_TABLE_CODE: + Cursor cursor = + queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + if (cursor == null) { + LogUtil.w("PhoneLookupHistoryContentProvider.query", "cursor was null"); + return null; + } + cursor.setNotificationUri( + getContext().getContentResolver(), PhoneLookupHistory.CONTENT_URI); + return cursor; + default: + throw new IllegalArgumentException("Unknown uri: " + uri); + } + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return PhoneLookupHistory.CONTENT_ITEM_TYPE; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + // Javadoc states values is not nullable, even though it is annotated as such (a bug)! + Assert.checkArgument(values != null); + + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + int match = uriMatcher.match(uri); + switch (match) { + case PHONE_LOOKUP_HISTORY_TABLE_CODE: + Assert.checkArgument( + !TextUtils.isEmpty(values.getAsString(PhoneLookupHistory.NORMALIZED_NUMBER)), + "You must specify a normalized number when inserting"); + break; + case PHONE_LOOKUP_HISTORY_TABLE_ID_CODE: + String normalizedNumberFromUri = uri.getLastPathSegment(); + String normalizedNumberFromValues = + values.getAsString(PhoneLookupHistory.NORMALIZED_NUMBER); + Assert.checkArgument( + normalizedNumberFromValues == null + || normalizedNumberFromValues.equals(normalizedNumberFromUri), + "NORMALIZED_NUMBER from values %s does not match normalized number from URI: %s", + LogUtil.sanitizePhoneNumber(normalizedNumberFromValues), + LogUtil.sanitizePhoneNumber(normalizedNumberFromUri)); + if (normalizedNumberFromValues == null) { + values.put(PhoneLookupHistory.NORMALIZED_NUMBER, normalizedNumberFromUri); + } + break; + default: + throw new IllegalArgumentException("Unknown uri: " + uri); + } + // Note: The id returned for a successful insert isn't actually part of the table. + long id = database.insert(PhoneLookupHistory.TABLE, null, values); + if (id < 0) { + LogUtil.w( + "PhoneLookupHistoryContentProvider.insert", + "error inserting row with number: %s", + LogUtil.sanitizePhoneNumber(values.getAsString(PhoneLookupHistory.NORMALIZED_NUMBER))); + return null; + } + Uri insertedUri = + PhoneLookupHistory.CONTENT_URI + .buildUpon() + .appendEncodedPath(values.getAsString(PhoneLookupHistory.NORMALIZED_NUMBER)) + .build(); + notifyChange(insertedUri); + return insertedUri; + } + + @Override + public int delete( + @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + final int match = uriMatcher.match(uri); + switch (match) { + case PHONE_LOOKUP_HISTORY_TABLE_CODE: + break; + case PHONE_LOOKUP_HISTORY_TABLE_ID_CODE: + Assert.checkArgument(selection == null, "Do not specify selection when deleting by number"); + Assert.checkArgument( + selectionArgs == null, "Do not specify selection args when deleting by number"); + String number = uri.getLastPathSegment(); + Assert.checkArgument(!TextUtils.isEmpty(number), "error parsing number from uri: %s", uri); + selection = PhoneLookupHistory.NORMALIZED_NUMBER + "= ?"; + selectionArgs = new String[] {number}; + break; + default: + throw new IllegalArgumentException("Unknown uri: " + uri); + } + int rows = database.delete(PhoneLookupHistory.TABLE, selection, selectionArgs); + if (rows == 0) { + LogUtil.w("PhoneLookupHistoryContentProvider.delete", "no rows deleted"); + return rows; + } + notifyChange(uri); + return rows; + } + + @Override + public int update( + @NonNull Uri uri, + @Nullable ContentValues values, + @Nullable String selection, + @Nullable String[] selectionArgs) { + // Javadoc states values is not nullable, even though it is annotated as such (a bug)! + Assert.checkArgument(values != null); + + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + int match = uriMatcher.match(uri); + switch (match) { + case PHONE_LOOKUP_HISTORY_TABLE_CODE: + break; + case PHONE_LOOKUP_HISTORY_TABLE_ID_CODE: + Assert.checkArgument( + !values.containsKey(PhoneLookupHistory.NORMALIZED_NUMBER), + "Do not specify number in values when updating by number"); + Assert.checkArgument(selection == null, "Do not specify selection when updating by ID"); + Assert.checkArgument( + selectionArgs == null, "Do not specify selection args when updating by ID"); + selection = PhoneLookupHistory.NORMALIZED_NUMBER + "= ?"; + selectionArgs = new String[] {uri.getLastPathSegment()}; + break; + default: + throw new IllegalArgumentException("Unknown uri: " + uri); + } + int rows = database.update(PhoneLookupHistory.TABLE, values, selection, selectionArgs); + if (rows == 0) { + LogUtil.w("PhoneLookupHistoryContentProvider.update", "no rows updated"); + return rows; + } + notifyChange(uri); + return rows; + } + + private void notifyChange(Uri uri) { + getContext().getContentResolver().notifyChange(uri, null); + } +} diff --git a/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryDatabaseHelper.java b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryDatabaseHelper.java new file mode 100644 index 000000000..70d88cee4 --- /dev/null +++ b/java/com/android/dialer/phonelookup/database/PhoneLookupHistoryDatabaseHelper.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 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.phonelookup.database; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.SystemClock; +import com.android.dialer.common.LogUtil; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; +import java.util.Locale; + +/** {@link SQLiteOpenHelper} for the PhoneLookupHistory database. */ +class PhoneLookupHistoryDatabaseHelper extends SQLiteOpenHelper { + private final int maxRows; + + PhoneLookupHistoryDatabaseHelper(Context appContext, int maxRows) { + super(appContext, "phone_lookup_history.db", null, 1); + this.maxRows = maxRows; + } + + private static final String CREATE_TABLE_SQL = + "create table if not exists " + + PhoneLookupHistory.TABLE + + " (" + + (PhoneLookupHistory.NORMALIZED_NUMBER + " text primary key not null, ") + + (PhoneLookupHistory.PHONE_LOOKUP_INFO + " blob not null, ") + + (PhoneLookupHistory.LAST_MODIFIED + " long not null") + + ");"; + + /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */ + private static final String CREATE_TRIGGER_SQL = + "create trigger delete_old_rows after insert on " + + PhoneLookupHistory.TABLE + + " when (select count(*) from " + + PhoneLookupHistory.TABLE + + ") > %d" + + " begin delete from " + + PhoneLookupHistory.TABLE + + " where " + + PhoneLookupHistory.NORMALIZED_NUMBER + + " in (select " + + PhoneLookupHistory.NORMALIZED_NUMBER + + " from " + + PhoneLookupHistory.TABLE + + " order by " + + PhoneLookupHistory.LAST_MODIFIED + + " limit (select count(*)-%d" + + " from " + + PhoneLookupHistory.TABLE + + " )); end;"; + + private static final String CREATE_INDEX_ON_LAST_MODIFIED_SQL = + "create index last_modified_index on " + + PhoneLookupHistory.TABLE + + " (" + + PhoneLookupHistory.LAST_MODIFIED + + ");"; + + @Override + public void onCreate(SQLiteDatabase db) { + LogUtil.enterBlock("PhoneLookupHistoryDatabaseHelper.onCreate"); + long startTime = SystemClock.uptimeMillis(); + db.execSQL(CREATE_TABLE_SQL); + db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows)); + db.execSQL(CREATE_INDEX_ON_LAST_MODIFIED_SQL); + // TODO(zachh): Consider logging impression. + LogUtil.i( + "PhoneLookupHistoryDatabaseHelper.onCreate", + "took: %dms", + SystemClock.uptimeMillis() - startTime); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} +} diff --git a/java/com/android/dialer/phonelookup/database/contract/PhoneLookupHistoryContract.java b/java/com/android/dialer/phonelookup/database/contract/PhoneLookupHistoryContract.java new file mode 100644 index 000000000..f8e108496 --- /dev/null +++ b/java/com/android/dialer/phonelookup/database/contract/PhoneLookupHistoryContract.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 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.phonelookup.database.contract; + +import android.net.Uri; +import com.android.dialer.constants.Constants; + +/** Contract for the PhoneLookupHistory content provider. */ +public class PhoneLookupHistoryContract { + public static final String AUTHORITY = Constants.get().getPhoneLookupHistoryProviderAuthority(); + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); + + /** PhoneLookupHistory table. */ + public static final class PhoneLookupHistory { + + public static final String TABLE = "PhoneLookupHistory"; + + /** The content URI for this table. */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(PhoneLookupHistoryContract.CONTENT_URI, TABLE); + + /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone_lookup_history"; + + /** + * The phone number's E164 representation if it has one, or otherwise normalized number if it + * cannot be normalized to E164. Required, primary key for the table. + * + *

Type: TEXT + */ + public static final String NORMALIZED_NUMBER = "normalized_number"; + + /** + * The {@link com.android.dialer.phonelookup.PhoneLookupInfo} proto for the number. Required. + * + *

Type: BLOB + */ + public static final String PHONE_LOOKUP_INFO = "phone_lookup_info"; + + /** + * Epoch time in milliseconds this entry was last modified. Required. + * + *

Type: INTEGER (long) + */ + public static final String LAST_MODIFIED = "last_modified"; + } +} -- cgit v1.2.3