From 5f8a084909b613b1aebfeffd1f820b7e5504c6bb Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Fri, 21 Feb 2020 18:21:56 +0800 Subject: [PATCH] Keep contextual card dismissal info upon deletion Add a config for contextual dismissal. This config is used to determine whether the existing dismissal timestamp should be kept before an insertion. The new dismissal behaviour design replaces the boolean value(isDismissed) with a long value(dismissedTimestamp) to give this behaviour more flexibility. Utilizing the timestamp allows developers to define new criteria of card's display (e.g cards being dismissed can be shown again after a period of time). While newly inserted data from SettingsIntelligence or other clients may not have dismissal timestamp recorded. Turning on the config gives the capability to persist dismissal timestamp, and more customized behaviour can be created. Bug: 143055685 Test: robotests Change-Id: I9d095955e9c51f2aa3332d49ee230d3ded9ae744 Merged-In: I9d095955e9c51f2aa3332d49ee230d3ded9ae744 (cherry picked from commit 377125bf9637625b9c7529c8dec23119b8fa5873) --- res/values/config.xml | 3 ++ .../contextualcards/CardContentProvider.java | 50 +++++++++++++++++-- tests/robotests/res/values-mcc999/config.xml | 2 + .../CardContentProviderTest.java | 50 +++++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index 2373b253aaa..64d9ab7081f 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -201,6 +201,9 @@ true + + false + com.android.settings.intelligence diff --git a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java index 9627eb03521..75ec651fc00 100644 --- a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java +++ b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java @@ -26,12 +26,16 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Build; import android.os.StrictMode; +import android.util.ArrayMap; import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.android.settings.R; import com.android.settingslib.utils.ThreadUtils; +import java.util.Map; + /** * Provider stores and manages user interaction feedback for homepage contextual cards. */ @@ -40,10 +44,10 @@ public class CardContentProvider extends ContentProvider { public static final String CARD_AUTHORITY = "com.android.settings.homepage.CardContentProvider"; public static final Uri REFRESH_CARD_URI = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(CardContentProvider.CARD_AUTHORITY) - .appendPath(CardDatabaseHelper.CARD_TABLE) - .build(); + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(CardContentProvider.CARD_AUTHORITY) + .appendPath(CardDatabaseHelper.CARD_TABLE) + .build(); public static final Uri DELETE_CARD_URI = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) @@ -81,6 +85,9 @@ public class CardContentProvider extends ContentProvider { final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); int numInserted = 0; final SQLiteDatabase database = mDBHelper.getWritableDatabase(); + final boolean keepDismissalTimestampBeforeDeletion = getContext().getResources() + .getBoolean(R.bool.config_keep_contextual_card_dismissal_timestamp); + final Map dismissedTimeMap = new ArrayMap<>(); try { maybeEnableStrictMode(); @@ -88,9 +95,42 @@ public class CardContentProvider extends ContentProvider { final String table = getTableFromMatch(uri); database.beginTransaction(); - // Here deletion first is avoiding redundant insertion. According to cl/215350754 + if (keepDismissalTimestampBeforeDeletion) { + // Query the existing db and get dismissal info. + final String[] columns = new String[]{CardDatabaseHelper.CardColumns.NAME, + CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP}; + final String selection = + CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " IS NOT NULL"; + try (Cursor cursor = database.query(table, columns, selection, + null/* selectionArgs */, null /* groupBy */, + null /* having */, null /* orderBy */)) { + // Save them to a Map + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + final String cardName = cursor.getString(cursor.getColumnIndex( + CardDatabaseHelper.CardColumns.NAME)); + final long timestamp = cursor.getLong(cursor.getColumnIndex( + CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP)); + dismissedTimeMap.put(cardName, timestamp); + } + } + } + + // Here delete data first to avoid redundant insertion. According to cl/215350754 database.delete(table, null /* whereClause */, null /* whereArgs */); + for (ContentValues value : values) { + if (keepDismissalTimestampBeforeDeletion) { + // Replace dismissedTimestamp in each value if there is an old one. + final String cardName = + value.get(CardDatabaseHelper.CardColumns.NAME).toString(); + if (dismissedTimeMap.containsKey(cardName)) { + // Replace the value of dismissedTimestamp + value.put(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP, + dismissedTimeMap.get(cardName)); + Log.d(TAG, "Replace dismissed time: " + cardName); + } + } + long ret = database.insert(table, null /* nullColumnHack */, value); if (ret != -1L) { numInserted++; diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index dc638a58001..c7173bc61d8 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -72,6 +72,8 @@ false + + true diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/CardContentProviderTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/CardContentProviderTest.java index d13c97c47da..32af5d8597d 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/CardContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/CardContentProviderTest.java @@ -38,6 +38,7 @@ import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) @@ -85,6 +86,25 @@ public class CardContentProviderTest { assertThat(rowsAfterInsert - rowsBeforeInsert).isEqualTo(2); } + @Test + @Config(qualifiers = "mcc999") + public void bulkInsert_keepDismissalTimestamp_shouldHaveTimestamp() { + mResolver.bulkInsert(mUri, generateTwoRowsWithDismissTimestamp()); + + mResolver.bulkInsert(mUri, generateTwoRows()); + + assertThat(queryDismissedTimestamp()).isEqualTo(10001L); + } + + @Test + public void bulkInsert_notKeepDismissalTimestamp_shouldNotHaveTimestamp() { + mResolver.bulkInsert(mUri, generateTwoRowsWithDismissTimestamp()); + + mResolver.bulkInsert(mUri, generateTwoRows()); + + assertThat(queryDismissedTimestamp()).isEqualTo(0L); + } + @Test public void cardData_query() { mResolver.insert(mUri, generateOneRow()); @@ -198,10 +218,40 @@ public class CardContentProviderTest { return twoRows; } + private ContentValues[] generateTwoRowsWithDismissTimestamp() { + final ContentValues[] twoRows = new ContentValues[2]; + twoRows[0] = generateOneRow(); + + final ContentValues values = new ContentValues(); + values.put(CardDatabaseHelper.CardColumns.NAME, "toggle_airplane"); + values.put(CardDatabaseHelper.CardColumns.TYPE, 1); + values.put(CardDatabaseHelper.CardColumns.SCORE, 0.95); + values.put(CardDatabaseHelper.CardColumns.SLICE_URI, + "content://com.android.settings.slices/action/toggle_airplane"); + values.put(CardDatabaseHelper.CardColumns.CATEGORY, 2); + values.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME, "com.android.settings"); + values.put(CardDatabaseHelper.CardColumns.APP_VERSION, 10001); + values.put(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP, 10001L); + twoRows[1] = values; + + return twoRows; + } + private int getRowCount() { final Cursor cr = mResolver.query(mUri, null, null, null); final int count = cr.getCount(); cr.close(); return count; } + + private long queryDismissedTimestamp() { + final String[] columns = {CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP}; + final String selection = CardDatabaseHelper.CardColumns.NAME + "=?"; + final String[] selectionArgs = {"toggle_airplane"}; + final Cursor cr = mResolver.query(mUri, columns, selection, selectionArgs, null); + cr.moveToFirst(); + final long dismissedTimestamp = cr.getLong(0); + cr.close(); + return dismissedTimestamp; + } } \ No newline at end of file