Implement new dismissal behaviour of contextual card

Currently, if a contextual card gets dismissed, it will be gone forever.
After this change, all cards being dismissed can be shown again after a
certain amount of time(e.g one day). In order to calculate the amount of
time, CARD_DISMISSED column is replaced with DISMISSED_TIMESTAMP. Once a
card gets dismissed, a timestamp will be recorded for a corresponding
card.

In this change, some methods are moved from CardDatabaseHelper to
ContextualCardFeatureProvider. So OEMs could replace the providers with
their own ones to get cards and have different dismissal behaviours.

Bug: 143055685
Test: rototests
Change-Id: I00ace98991cabcbfcae4fc47a44e9448683d680c
This commit is contained in:
Yi-Ling Chuang
2020-02-18 09:16:10 +08:00
parent baf12eeb6d
commit deea015b65
10 changed files with 193 additions and 105 deletions

View File

@@ -48,7 +48,7 @@ public class CardContentProvider extends ContentProvider {
public static final Uri DELETE_CARD_URI = new Uri.Builder() public static final Uri DELETE_CARD_URI = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT) .scheme(ContentResolver.SCHEME_CONTENT)
.authority(CardContentProvider.CARD_AUTHORITY) .authority(CardContentProvider.CARD_AUTHORITY)
.appendPath(CardDatabaseHelper.CardColumns.CARD_DISMISSED) .appendPath(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP)
.build(); .build();
private static final String TAG = "CardContentProvider"; private static final String TAG = "CardContentProvider";

View File

@@ -16,9 +16,7 @@
package com.android.settings.homepage.contextualcards; package com.android.settings.homepage.contextualcards;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
@@ -31,7 +29,7 @@ import androidx.annotation.VisibleForTesting;
public class CardDatabaseHelper extends SQLiteOpenHelper { public class CardDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "CardDatabaseHelper"; private static final String TAG = "CardDatabaseHelper";
private static final String DATABASE_NAME = "homepage_cards.db"; private static final String DATABASE_NAME = "homepage_cards.db";
private static final int DATABASE_VERSION = 6; private static final int DATABASE_VERSION = 7;
public static final String CARD_TABLE = "cards"; public static final String CARD_TABLE = "cards";
@@ -72,31 +70,32 @@ public class CardDatabaseHelper extends SQLiteOpenHelper {
String APP_VERSION = "app_version"; String APP_VERSION = "app_version";
/** /**
* Decide the card is dismissed or not. * Timestamp of card being dismissed.
*/ */
String CARD_DISMISSED = "card_dismissed"; String DISMISSED_TIMESTAMP = "dismissed_timestamp";
} }
private static final String CREATE_CARD_TABLE = private static final String CREATE_CARD_TABLE =
"CREATE TABLE " + CARD_TABLE + "CREATE TABLE "
"(" + + CARD_TABLE
CardColumns.NAME + + "("
" TEXT NOT NULL PRIMARY KEY, " + + CardColumns.NAME
CardColumns.TYPE + + " TEXT NOT NULL PRIMARY KEY, "
" INTEGER NOT NULL, " + + CardColumns.TYPE
CardColumns.SCORE + + " INTEGER NOT NULL, "
" DOUBLE NOT NULL, " + + CardColumns.SCORE
CardColumns.SLICE_URI + + " DOUBLE NOT NULL, "
" TEXT, " + + CardColumns.SLICE_URI
CardColumns.CATEGORY + + " TEXT, "
" INTEGER DEFAULT 0, " + + CardColumns.CATEGORY
CardColumns.PACKAGE_NAME + + " INTEGER DEFAULT 0, "
" TEXT NOT NULL, " + + CardColumns.PACKAGE_NAME
CardColumns.APP_VERSION + + " TEXT NOT NULL, "
" INTEGER NOT NULL, " + + CardColumns.APP_VERSION
CardColumns.CARD_DISMISSED + + " INTEGER NOT NULL, "
" INTEGER DEFAULT 0 " + + CardColumns.DISMISSED_TIMESTAMP
");"; + " INTEGER"
+ ");";
public CardDatabaseHelper(Context context) { public CardDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -125,32 +124,4 @@ public class CardDatabaseHelper extends SQLiteOpenHelper {
} }
return sCardDatabaseHelper; return sCardDatabaseHelper;
} }
Cursor getContextualCards() {
final SQLiteDatabase db = getReadableDatabase();
final String selection = CardColumns.CARD_DISMISSED + "=0";
return db.query(CARD_TABLE, null /* columns */, selection,
null /* selectionArgs */, null /* groupBy */, null /* having */,
CardColumns.SCORE + " DESC" /* orderBy */);
}
/**
* Mark a specific ContextualCard with dismissal flag in the database to indicate that the
* card has been dismissed.
*
* @param context Context
* @param cardName The card name of the ContextualCard which is dismissed by user.
* @return The number of rows updated
*/
public int markContextualCardAsDismissed(Context context, String cardName) {
final SQLiteDatabase database = getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(CardColumns.CARD_DISMISSED, 1);
final String selection = CardColumns.NAME + "=?";
final String[] selectionArgs = {cardName};
final int rowsUpdated = database.update(CARD_TABLE, values, selection, selectionArgs);
database.close();
context.getContentResolver().notifyChange(CardContentProvider.DELETE_CARD_URI, null);
return rowsUpdated;
}
} }

View File

@@ -16,10 +16,25 @@
package com.android.settings.homepage.contextualcards; package com.android.settings.homepage.contextualcards;
import android.content.Context;
import android.database.Cursor;
import androidx.slice.Slice; import androidx.slice.Slice;
/** Feature provider for the contextual card feature. */ /** Feature provider for the contextual card feature. */
public interface ContextualCardFeatureProvider { public interface ContextualCardFeatureProvider {
/** Get contextual cards from the card provider */
Cursor getContextualCards();
/**
* Mark a specific {@link ContextualCard} as dismissed with dismissal signal in the database
* to indicate that the card has been dismissed.
*
* @param context Context
* @param cardName The card name of the ContextualCard which is dismissed by user.
* @return The number of rows updated
*/
int markCardAsDismissed(Context context, String cardName);
/** Log package when user clicks contextual notification channel card. */ /** Log package when user clicks contextual notification channel card. */
void logNotificationPackage(Slice slice); void logNotificationPackage(Slice slice);

View File

@@ -18,10 +18,19 @@ package com.android.settings.homepage.contextualcards;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static com.android.settings.homepage.contextualcards.CardDatabaseHelper.CARD_TABLE;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.format.DateUtils;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice; import androidx.slice.Slice;
import androidx.slice.SliceMetadata; import androidx.slice.SliceMetadata;
import androidx.slice.core.SliceAction; import androidx.slice.core.SliceAction;
@@ -30,16 +39,46 @@ import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice; import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.CustomSliceRegistry;
import com.android.settingslib.utils.ThreadUtils;
import java.util.Set; import java.util.Set;
public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureProvider { public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureProvider {
private static final String TAG = "ContextualCardFeatureProvider";
private final Context mContext; private final Context mContext;
public ContextualCardFeatureProviderImpl(Context context) { public ContextualCardFeatureProviderImpl(Context context) {
mContext = context; mContext = context;
} }
@Override
public Cursor getContextualCards() {
final SQLiteDatabase db = CardDatabaseHelper.getInstance(mContext).getReadableDatabase();
//TODO(b/149542061): Make the dismissal duration configurable.
final long threshold = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
final String selection = CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " < ? OR "
+ CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " IS NULL";
final String[] selectionArgs = {String.valueOf(threshold)};
final Cursor cursor = db.query(CARD_TABLE, null /* columns */, selection,
selectionArgs /* selectionArgs */, null /* groupBy */, null /* having */,
CardDatabaseHelper.CardColumns.SCORE + " DESC" /* orderBy */);
ThreadUtils.postOnBackgroundThread(() -> resetDismissedTime(threshold));
return cursor;
}
@Override
public int markCardAsDismissed(Context context, String cardName) {
final SQLiteDatabase db = CardDatabaseHelper.getInstance(mContext).getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP, System.currentTimeMillis());
final String selection = CardDatabaseHelper.CardColumns.NAME + "=?";
final String[] selectionArgs = {cardName};
final int rowsUpdated = db.update(CARD_TABLE, values, selection, selectionArgs);
context.getContentResolver().notifyChange(CardContentProvider.DELETE_CARD_URI, null);
return rowsUpdated;
}
@Override @Override
public void logNotificationPackage(Slice slice) { public void logNotificationPackage(Slice slice) {
if (slice == null || !slice.getUri().equals( if (slice == null || !slice.getUri().equals(
@@ -62,4 +101,20 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP
prefs.edit().putStringSet(ContextualNotificationChannelSlice.PREF_KEY_INTERACTED_PACKAGES, prefs.edit().putStringSet(ContextualNotificationChannelSlice.PREF_KEY_INTERACTED_PACKAGES,
newInteractedPackages).apply(); newInteractedPackages).apply();
} }
@VisibleForTesting
int resetDismissedTime(long threshold) {
final SQLiteDatabase database =
CardDatabaseHelper.getInstance(mContext).getWritableDatabase();
final ContentValues values = new ContentValues();
values.putNull(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP);
final String selection = CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " < ? AND "
+ CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " IS NOT NULL";
final String[] selectionArgs = {String.valueOf(threshold)};
final int rowsUpdated = database.update(CARD_TABLE, values, selection, selectionArgs);
if (Build.IS_DEBUGGABLE) {
Log.d(TAG, "Reset " + rowsUpdated + " records of dismissed time.");
}
return rowsUpdated;
}
} }

View File

@@ -178,7 +178,9 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
@VisibleForTesting @VisibleForTesting
Cursor getContextualCardsFromProvider() { Cursor getContextualCardsFromProvider() {
return CardDatabaseHelper.getInstance(mContext).getContextualCards(); final ContextualCardFeatureProvider cardFeatureProvider =
FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext);
return cardFeatureProvider.getContextualCards();
} }
@VisibleForTesting @VisibleForTesting

View File

@@ -25,9 +25,9 @@ import android.text.TextUtils;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.homepage.contextualcards.CardDatabaseHelper;
import com.android.settings.homepage.contextualcards.ContextualCard; import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardController; import com.android.settings.homepage.contextualcards.ContextualCardController;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog;
import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener; import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener;
import com.android.settings.homepage.contextualcards.logging.ContextualCardLogUtils; import com.android.settings.homepage.contextualcards.logging.ContextualCardLogUtils;
@@ -68,8 +68,9 @@ public class SliceContextualCardController implements ContextualCardController {
@Override @Override
public void onDismissed(ContextualCard card) { public void onDismissed(ContextualCard card) {
ThreadUtils.postOnBackgroundThread(() -> { ThreadUtils.postOnBackgroundThread(() -> {
final CardDatabaseHelper dbHelper = CardDatabaseHelper.getInstance(mContext); final ContextualCardFeatureProvider cardFeatureProvider =
dbHelper.markContextualCardAsDismissed(mContext, card.getName()); FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext);
cardFeatureProvider.markCardAsDismissed(mContext, card.getName());
}); });
showFeedbackDialog(card); showFeedbackDialog(card);

View File

@@ -18,13 +18,10 @@ package com.android.settings.homepage.contextualcards;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import com.android.settings.intelligence.ContextualCardProto;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -32,9 +29,6 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class CardDatabaseHelperTest { public class CardDatabaseHelperTest {
@@ -46,7 +40,7 @@ public class CardDatabaseHelperTest {
public void setUp() { public void setUp() {
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mCardDatabaseHelper = CardDatabaseHelper.getInstance(mContext); mCardDatabaseHelper = CardDatabaseHelper.getInstance(mContext);
mDatabase = mCardDatabaseHelper.getWritableDatabase(); mDatabase = mCardDatabaseHelper.getReadableDatabase();
} }
@After @After
@@ -69,44 +63,10 @@ public class CardDatabaseHelperTest {
CardDatabaseHelper.CardColumns.CATEGORY, CardDatabaseHelper.CardColumns.CATEGORY,
CardDatabaseHelper.CardColumns.PACKAGE_NAME, CardDatabaseHelper.CardColumns.PACKAGE_NAME,
CardDatabaseHelper.CardColumns.APP_VERSION, CardDatabaseHelper.CardColumns.APP_VERSION,
CardDatabaseHelper.CardColumns.CARD_DISMISSED, CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP,
}; };
assertThat(columnNames).isEqualTo(expectedNames); assertThat(columnNames).isEqualTo(expectedNames);
cursor.close(); cursor.close();
} }
@Test
public void getContextualCards_shouldSortByScore() {
insertFakeCard(mDatabase, "card1", 1, "uri1");
insertFakeCard(mDatabase, "card2", 0, "uri2");
insertFakeCard(mDatabase, "card3", 10, "uri3");
// Should sort as 3,1,2
try (final Cursor cursor = CardDatabaseHelper.getInstance(mContext).getContextualCards()) {
assertThat(cursor.getCount()).isEqualTo(3);
final List<ContextualCard> cards = new ArrayList<>();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
cards.add(new ContextualCard(cursor));
}
assertThat(cards.get(0).getName()).isEqualTo("card3");
assertThat(cards.get(1).getName()).isEqualTo("card1");
assertThat(cards.get(2).getName()).isEqualTo("card2");
}
}
private static void insertFakeCard(SQLiteDatabase db, String name, double score, String uri) {
final ContentValues value = new ContentValues();
value.put(CardDatabaseHelper.CardColumns.NAME, name);
value.put(CardDatabaseHelper.CardColumns.SCORE, score);
value.put(CardDatabaseHelper.CardColumns.SLICE_URI, uri);
value.put(CardDatabaseHelper.CardColumns.TYPE, ContextualCard.CardType.SLICE);
value.put(CardDatabaseHelper.CardColumns.CATEGORY,
ContextualCardProto.ContextualCard.Category.DEFAULT.getNumber());
value.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME,
RuntimeEnvironment.application.getPackageName());
value.put(CardDatabaseHelper.CardColumns.APP_VERSION, 1);
db.insert(CardDatabaseHelper.CARD_TABLE, null, value);
}
} }

View File

@@ -28,10 +28,14 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import android.annotation.Nullable;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.ArraySet; import android.util.ArraySet;
@@ -46,6 +50,7 @@ import androidx.slice.widget.SliceLiveData;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.intelligence.ContextualCardProto;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -54,6 +59,8 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
import java.util.Set; import java.util.Set;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -62,6 +69,8 @@ public class ContextualCardFeatureProviderImplTest {
private Context mContext; private Context mContext;
private ContextualCardFeatureProviderImpl mImpl; private ContextualCardFeatureProviderImpl mImpl;
private SharedPreferences mSharedPreferences; private SharedPreferences mSharedPreferences;
private CardDatabaseHelper mCardDatabaseHelper;
private SQLiteDatabase mDatabase;
@Before @Before
public void setUp() { public void setUp() {
@@ -70,13 +79,55 @@ public class ContextualCardFeatureProviderImplTest {
mSharedPreferences = mContext.getSharedPreferences(PREFS, MODE_PRIVATE); mSharedPreferences = mContext.getSharedPreferences(PREFS, MODE_PRIVATE);
// Set-up specs for SliceMetadata. // Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
mCardDatabaseHelper = CardDatabaseHelper.getInstance(mContext);
mDatabase = mCardDatabaseHelper.getWritableDatabase();
} }
@After @After
public void tearDown() { public void tearDown() {
CardDatabaseHelper.getInstance(mContext).close();
CardDatabaseHelper.sCardDatabaseHelper = null;
removeInteractedPackageFromSharedPreference(); removeInteractedPackageFromSharedPreference();
} }
@Test
public void getContextualCards_shouldSortByScore() {
insertFakeCard(mDatabase, "card1", 1, "uri1", 1000L);
insertFakeCard(mDatabase, "card2", 0, "uri2", 1000L);
insertFakeCard(mDatabase, "card3", 10, "uri3", 1000L);
// Should sort as 3,1,2
try (Cursor cursor = mImpl.getContextualCards()) {
assertThat(cursor.getCount()).isEqualTo(3);
final List<ContextualCard> cards = new ArrayList<>();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
cards.add(new ContextualCard(cursor));
}
assertThat(cards.get(0).getName()).isEqualTo("card3");
assertThat(cards.get(1).getName()).isEqualTo("card1");
assertThat(cards.get(2).getName()).isEqualTo("card2");
}
}
@Test
public void resetDismissedTime_durationExpired_shouldResetToNull() {
insertFakeCard(mDatabase, "card1", 1, "uri1", 100L);
final long threshold = 1000L;
final int rowsUpdated = mImpl.resetDismissedTime(threshold);
assertThat(rowsUpdated).isEqualTo(1);
}
@Test
public void resetDismissedTime_durationNotExpired_shouldNotUpdate() {
insertFakeCard(mDatabase, "card1", 1, "uri1", 1111L);
final long threshold = 1000L;
final int rowsUpdated = mImpl.resetDismissedTime(threshold);
assertThat(rowsUpdated).isEqualTo(0);
}
@Test @Test
public void logNotificationPackage_isContextualNotificationChannel_shouldLogPackage() { public void logNotificationPackage_isContextualNotificationChannel_shouldLogPackage() {
final String packageName = "com.android.test.app"; final String packageName = "com.android.test.app";
@@ -101,6 +152,28 @@ public class ContextualCardFeatureProviderImplTest {
assertThat(interactedPackages.contains(packageName)).isFalse(); assertThat(interactedPackages.contains(packageName)).isFalse();
} }
private static void insertFakeCard(
SQLiteDatabase db, String name, double score, String uri, @Nullable Long time) {
final ContentValues value = new ContentValues();
value.put(CardDatabaseHelper.CardColumns.NAME, name);
value.put(CardDatabaseHelper.CardColumns.SCORE, score);
value.put(CardDatabaseHelper.CardColumns.SLICE_URI, uri);
value.put(CardDatabaseHelper.CardColumns.TYPE, ContextualCard.CardType.SLICE);
value.put(CardDatabaseHelper.CardColumns.CATEGORY,
ContextualCardProto.ContextualCard.Category.DEFAULT.getNumber());
value.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME,
RuntimeEnvironment.application.getPackageName());
value.put(CardDatabaseHelper.CardColumns.APP_VERSION, 1);
if (time == null) {
value.putNull(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP);
} else {
value.put(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP, time);
}
db.insert(CardDatabaseHelper.CARD_TABLE, null, value);
}
private Slice buildSlice(Uri sliceUri, String packageName) { private Slice buildSlice(Uri sliceUri, String packageName) {
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, packageName); args.putString(AppInfoBase.ARG_PACKAGE_NAME, packageName);

View File

@@ -38,10 +38,13 @@ import com.android.settings.R;
import com.android.settings.homepage.contextualcards.CardContentProvider; import com.android.settings.homepage.contextualcards.CardContentProvider;
import com.android.settings.homepage.contextualcards.CardDatabaseHelper; import com.android.settings.homepage.contextualcards.CardDatabaseHelper;
import com.android.settings.homepage.contextualcards.ContextualCard; import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProviderImpl;
import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog;
import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -78,10 +81,18 @@ public class SliceContextualCardControllerTest {
mResolver = mContext.getContentResolver(); mResolver = mContext.getContentResolver();
mController = spy(new SliceContextualCardController(mContext)); mController = spy(new SliceContextualCardController(mContext));
mFeatureFactory = FakeFeatureFactory.setupForTest(); mFeatureFactory = FakeFeatureFactory.setupForTest();
final ContextualCardFeatureProvider cardFeatureProvider =
new ContextualCardFeatureProviderImpl(mContext);
mFeatureFactory.mContextualCardFeatureProvider = cardFeatureProvider;
}
@After
public void tearDown() {
CardDatabaseHelper.getInstance(mContext).close();
} }
@Test @Test
public void onDismissed_cardShouldBeMarkedAsDismissed() { public void onDismissed_cardShouldBeMarkedAsDismissedWithTimestamp() {
final Uri providerUri = CardContentProvider.REFRESH_CARD_URI; final Uri providerUri = CardContentProvider.REFRESH_CARD_URI;
mResolver.insert(providerUri, generateOneRow()); mResolver.insert(providerUri, generateOneRow());
doNothing().when(mController).showFeedbackDialog(any(ContextualCard.class)); doNothing().when(mController).showFeedbackDialog(any(ContextualCard.class));
@@ -89,15 +100,15 @@ public class SliceContextualCardControllerTest {
final ContextualCard card = getTestSliceCard(); final ContextualCard card = getTestSliceCard();
mController.onDismissed(card); mController.onDismissed(card);
final String[] columns = {CardDatabaseHelper.CardColumns.CARD_DISMISSED}; final String[] columns = {CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP};
final String selection = CardDatabaseHelper.CardColumns.NAME + "=?"; final String selection = CardDatabaseHelper.CardColumns.NAME + "=?";
final String[] selectionArgs = {TEST_CARD_NAME}; final String[] selectionArgs = {TEST_CARD_NAME};
final Cursor cr = mResolver.query(providerUri, columns, selection, selectionArgs, null); final Cursor cr = mResolver.query(providerUri, columns, selection, selectionArgs, null);
cr.moveToFirst(); cr.moveToFirst();
final int qryDismissed = cr.getInt(0); final long dismissedTimestamp = cr.getLong(0);
cr.close(); cr.close();
assertThat(qryDismissed).isEqualTo(1); assertThat(dismissedTimestamp).isNotEqualTo(0L);
verify(mFeatureFactory.metricsFeatureProvider).action(any(), verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_CONTEXTUAL_CARD_DISMISS), any(String.class)); eq(SettingsEnums.ACTION_CONTEXTUAL_CARD_DISMISS), any(String.class));
} }
@@ -172,7 +183,7 @@ public class SliceContextualCardControllerTest {
values.put(CardDatabaseHelper.CardColumns.CATEGORY, 2); values.put(CardDatabaseHelper.CardColumns.CATEGORY, 2);
values.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME, "com.android.settings"); values.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME, "com.android.settings");
values.put(CardDatabaseHelper.CardColumns.APP_VERSION, 10001); values.put(CardDatabaseHelper.CardColumns.APP_VERSION, 10001);
values.put(CardDatabaseHelper.CardColumns.CARD_DISMISSED, 0); values.put(CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP, 0L);
return values; return values;
} }

View File

@@ -66,7 +66,6 @@ public class FakeFeatureFactory extends FeatureFactory {
public final UserFeatureProvider userFeatureProvider; public final UserFeatureProvider userFeatureProvider;
public final AssistGestureFeatureProvider assistGestureFeatureProvider; public final AssistGestureFeatureProvider assistGestureFeatureProvider;
public final AccountFeatureProvider mAccountFeatureProvider; public final AccountFeatureProvider mAccountFeatureProvider;
public final ContextualCardFeatureProvider mContextualCardFeatureProvider;
public final BluetoothFeatureProvider mBluetoothFeatureProvider; public final BluetoothFeatureProvider mBluetoothFeatureProvider;
public final AwareFeatureProvider mAwareFeatureProvider; public final AwareFeatureProvider mAwareFeatureProvider;
public final FaceFeatureProvider mFaceFeatureProvider; public final FaceFeatureProvider mFaceFeatureProvider;
@@ -74,6 +73,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public PanelFeatureProvider panelFeatureProvider; public PanelFeatureProvider panelFeatureProvider;
public SlicesFeatureProvider slicesFeatureProvider; public SlicesFeatureProvider slicesFeatureProvider;
public SearchFeatureProvider searchFeatureProvider; public SearchFeatureProvider searchFeatureProvider;
public ContextualCardFeatureProvider mContextualCardFeatureProvider;
/** /**
* Call this in {@code @Before} method of the test class to use fake factory. * Call this in {@code @Before} method of the test class to use fake factory.