Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ private void doSave() {

DBHelper.setLoyaltyCardGroups(mDatabase, viewModel.getLoyaltyCardId(), selectedGroups);

ShortcutHelper.updateShortcuts(this, DBHelper.getLoyaltyCard(this, mDatabase, viewModel.getLoyaltyCardId()));
ShortcutHelper.updateShortcuts(this);

if (viewModel.getDuplicateFromLoyaltyCardId()) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ protected void onResume() {

invalidateOptionsMenu();

ShortcutHelper.updateShortcuts(this, loyaltyCard);
ShortcutHelper.updateShortcuts(this);
}

private void setStateBasedOnImageTypes() {
Expand Down Expand Up @@ -896,7 +896,6 @@ public boolean onOptionsItemSelected(MenuItem item) {
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();

ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
new ListWidget().updateAll(LoyaltyCardViewActivity.this);

// Re-init loyaltyCard with new data from DB
Expand All @@ -922,7 +921,6 @@ public boolean onOptionsItemSelected(MenuItem item) {

DBHelper.deleteLoyaltyCard(database, LoyaltyCardViewActivity.this, loyaltyCardId);

ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
new ListWidget().updateAll(LoyaltyCardViewActivity.this);

finish();
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/java/protect/card_locker/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
Log.d(TAG, "Deleting card: " + loyaltyCard.id)

DBHelper.deleteLoyaltyCard(mDatabase, this@MainActivity, loyaltyCard.id)

ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
}
val tab = groupsTabLayout.getTabAt(selectedTab)
mGroup = tab?.tag
Expand All @@ -177,7 +175,6 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
for (loyaltyCard in mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id)
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1)
ShortcutHelper.removeShortcut(this@MainActivity, loyaltyCard.id)
updateLoyaltyCardList(false)
inputMode.finish()
invalidateOptionsMenu()
Expand Down Expand Up @@ -466,6 +463,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
}

ListWidget().updateAll(mAdapter.mContext)
ShortcutHelper.updateShortcuts(mAdapter.mContext)
}

private fun processParseResultList(
Expand Down
112 changes: 36 additions & 76 deletions app/src/main/java/protect/card_locker/ShortcutHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;

import androidx.annotation.VisibleForTesting;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.IconCompat;

import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

class ShortcutHelper {
// Android documentation says that no more than 5 shortcuts
// are supported. However, that may be too many, as not all
// launcher will show all 5. Instead, the number is limited
// to 3 here, so that the most recent shortcut has a good
// chance of being shown.
private static final int MAX_SHORTCUTS = 3;
/**
* This variable controls the maximum number of shortcuts available.
* It is made public only to make testing easier and should not be
* manually modified. We use -1 here as a default value to check if
* the value has been set either manually by the test scenario or
* automatically in the `updateShortcuts` function.
* Its actual value will be set based on the maximum amount of shortcuts
* declared by the launcher via `getMaxShortcutCountPerActivity`.
*/
@VisibleForTesting
public static int maxShortcuts = -1;

// https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html
private static final int ADAPTIVE_BITMAP_SCALE = 1;
Expand All @@ -35,86 +39,42 @@ class ShortcutHelper {
private static final int ADAPTIVE_BITMAP_IMAGE_SIZE = ADAPTIVE_BITMAP_VISIBLE_SIZE + 5 * ADAPTIVE_BITMAP_SCALE;

/**
* Add a card to the app shortcuts, and maintain a list of the most
* recently used cards. If there is already a shortcut for the card,
* the card is marked as the most recently used card. If adding this
* card exceeds the max number of shortcuts, then the least recently
* used card shortcut is discarded.
* Update the dynamic shortcut list with the most recently viewed cards
* based on the lastUsed field. Archived cards are excluded from the shortcuts
* list. The list keeps at most maxShortcuts number of elements.
*/
static void updateShortcuts(Context context, LoyaltyCard card) {
if (card.archiveStatus == 1) {
// Don't add archived card to menu
return;
static void updateShortcuts(Context context) {
if (maxShortcuts == -1) {
maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context);
}

LinkedList<ShortcutInfoCompat> list = new LinkedList<>(ShortcutManagerCompat.getDynamicShortcuts(context));

LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
Cursor loyaltyCardCursor = DBHelper.getLoyaltyCardCursor(
database,
"",
null,
DBHelper.LoyaltyCardOrder.LastUsed,
DBHelper.LoyaltyCardOrderDirection.Ascending,
DBHelper.LoyaltyCardArchiveFilter.Unarchived
);

String shortcutId = Integer.toString(card.id);

// Sort the shortcuts by rank, so working with the relative order will be easier.
// This sorts so that the lowest rank is first.
Collections.sort(list, Comparator.comparingInt(ShortcutInfoCompat::getRank));

Integer foundIndex = null;

for (int index = 0; index < list.size(); index++) {
if (list.get(index).getId().equals(shortcutId)) {
// Found the item already
foundIndex = index;
break;
}
}

if (foundIndex != null) {
// If the item is already found, then the list needs to be
// reordered, so that the selected item now has the lowest
// rank, thus letting it survive longer.
ShortcutInfoCompat found = list.remove(foundIndex.intValue());
list.addFirst(found);
} else {
// The item is new to the list. We add it and trim the list later.
ShortcutInfoCompat shortcut = createShortcutBuilder(context, card).build();
list.addFirst(shortcut);
}

LinkedList<ShortcutInfoCompat> finalList = new LinkedList<>();
int rank = 0;

// The ranks are now updated; the order in the list is the rank.
for (int index = 0; index < list.size(); index++) {
ShortcutInfoCompat prevShortcut = list.get(index);

LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, Integer.parseInt(prevShortcut.getId()));
while (rank < maxShortcuts && loyaltyCardCursor.moveToNext()) {
int id = loyaltyCardCursor.getInt(loyaltyCardCursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
LoyaltyCard loyaltyCard = DBHelper.getLoyaltyCard(context, database, id);

// skip outdated cards that no longer exist
if (loyaltyCard != null) {
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
.setRank(rank)
.build();
ShortcutInfoCompat updatedShortcut = createShortcutBuilder(context, loyaltyCard)
.setRank(rank)
.build();

finalList.addLast(updatedShortcut);
rank++;

// trim the list
if (rank >= MAX_SHORTCUTS) {
break;
}
}
finalList.addLast(updatedShortcut);
rank++;
}

ShortcutManagerCompat.setDynamicShortcuts(context, finalList);
}

/**
* Remove the given card id from the app shortcuts, if such a
* shortcut exists.
*/
static void removeShortcut(Context context, int cardId) {
ShortcutManagerCompat.removeDynamicShortcuts(context, Collections.singletonList(Integer.toString(cardId)));
}

static @NotNull
Bitmap createAdaptiveBitmap(@NotNull Bitmap in, int paddingColor) {
Bitmap ret = Bitmap.createBitmap(ADAPTIVE_BITMAP_SIZE, ADAPTIVE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
Expand Down
124 changes: 124 additions & 0 deletions app/src/test/java/protect/card_locker/ShortcutHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package protect.card_locker;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import android.app.Activity;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;

import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;

import com.google.zxing.BarcodeFormat;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;

import java.math.BigDecimal;
import java.util.Comparator;

@RunWith(RobolectricTestRunner.class)
public class ShortcutHelperTest {
private Activity mActivity;
private SQLiteDatabase mDatabase;
private int id1;
private int id2;
private int id3;
private int id4;

@Before
public void setUp() {
mActivity = Robolectric.setupActivity(MainActivity.class);
mDatabase = TestHelpers.getEmptyDb(mActivity).getWritableDatabase();

long now = System.currentTimeMillis();
id1 = (int) DBHelper.insertLoyaltyCard(mDatabase, "store1", "note1", null, null, new BigDecimal("0"), null, "cardId1", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), null, Color.BLACK, 0, now,0);
id2 = (int) DBHelper.insertLoyaltyCard(mDatabase, "store2", "note2", null, null, new BigDecimal("0"), null, "cardId2", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), null, Color.BLACK, 0, now + 10,0);
id3 = (int) DBHelper.insertLoyaltyCard(mDatabase, "store3", "note3", null, null, new BigDecimal("0"), null, "cardId3", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), null, Color.BLACK, 0, now + 20,0);
id4 = (int) DBHelper.insertLoyaltyCard(mDatabase, "store4", "note4", null, null, new BigDecimal("0"), null, "cardId4", null, CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A), null, Color.BLACK, 0, now + 30,0);

ShortcutHelper.maxShortcuts = 3;
}

private Integer[] getShortcutIds(Context context) {
return ShortcutManagerCompat.getDynamicShortcuts(context)
.stream()
.sorted(Comparator.comparingInt(ShortcutInfoCompat::getRank))
.map(shortcut -> Integer.parseInt(shortcut.getId()))
.toArray(Integer[]::new);
}

@Test
public void onArchiveUnarchive() {
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();

Activity mainActivity = (Activity) activityController.get();

activityController.pause();
activityController.resume();

assertEquals(3, ShortcutManagerCompat.getDynamicShortcuts(mainActivity).stream().count());

Integer[] ids = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id4, id3, id2}, ids);

DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, id4, 1);

activityController.pause();
activityController.resume();

Integer[] idsAfterArchive = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id3, id2, id1}, idsAfterArchive);

DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, id4, 0);

activityController.pause();
activityController.resume();

Integer[] idsAfterUnarchive = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id4, id3, id2}, idsAfterUnarchive);
}

@Test
public void onAddRemoveFavorite() {
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();

Activity mainActivity = (Activity) activityController.get();

activityController.pause();
activityController.resume();

assertEquals(3, ShortcutManagerCompat.getDynamicShortcuts(mainActivity).stream().count());

Integer[] ids = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id4, id3, id2}, ids);

DBHelper.updateLoyaltyCardStarStatus(mDatabase, id1, 1);

activityController.pause();
activityController.resume();

Integer[] idsAfterFav = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id1, id4, id3}, idsAfterFav);

DBHelper.updateLoyaltyCardStarStatus(mDatabase, id1, 0);

activityController.pause();
activityController.resume();

Integer[] idsAfterUnfav = getShortcutIds(mainActivity);

assertArrayEquals(new Integer[] {id4, id3, id2}, idsAfterUnfav);
}
}