Merge "Keep contextual card dismissal info upon deletion" into rvc-dev
diff --git a/res/values/config.xml b/res/values/config.xml
index 2373b25..64d9ab7 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -201,6 +201,9 @@
     <!-- Whether or not TopLevelSettings should force rounded icon for injected tiles -->
     <bool name="config_force_rounded_icon_TopLevelSettings">true</bool>
 
+    <!-- Whether dismissal timestamp should be kept before deletion -->
+    <bool name="config_keep_contextual_card_dismissal_timestamp">false</bool>
+
     <!-- Settings intelligence package name -->
     <string name="config_settingsintelligence_package_name" translatable="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 9627eb0..75ec651 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.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 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 @@
         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<String, Long> dismissedTimeMap = new ArrayMap<>();
 
         try {
             maybeEnableStrictMode();
@@ -88,9 +95,42 @@
             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 dc638a5..c7173bc 100644
--- a/tests/robotests/res/values-mcc999/config.xml
+++ b/tests/robotests/res/values-mcc999/config.xml
@@ -72,6 +72,8 @@
     <!-- Whether or not extra preview panels should be used for screen zoom setting. -->
     <bool name="config_enable_extra_screen_zoom_preview">false</bool>
 
+    <!-- Whether dismissal timestamp should be kept before deletion -->
+    <bool name="config_keep_contextual_card_dismissal_timestamp">true</bool>
 
     <!-- List of a11y components on the device allowed to be enabled by Settings Slices -->
     <string-array name="config_settings_slices_accessibility_components" translatable="false">
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 d13c97c..32af5d8 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.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(RobolectricTestRunner.class)
@@ -86,6 +87,25 @@
     }
 
     @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());
         final int count = getRowCount();
@@ -198,10 +218,40 @@
         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