From ce1acb83f0e107e2f701018519b6c472ab6c1730 Mon Sep 17 00:00:00 2001 From: Branden Archer Date: Tue, 26 Sep 2017 22:52:32 -0400 Subject: [PATCH] Add card shortcuts for most recently used cards This adds app shortcuts for the most recently used cards. When a new card is accessed, it is added to the shortcuts list. When the list exceeds its maximum size, the least recently used shortcut is discarded. Android limits the maximum number of shortcuts to 5, however it recommends in its documentation to limit this to 4. This commit limits this to 3, however, as that is aesthetically pleasing. --- app/build.gradle | 2 +- .../card_locker/LoyaltyCardViewActivity.java | 3 + .../protect/card_locker/MainActivity.java | 8 + .../protect/card_locker/ShortcutHelper.java | 147 ++++++++++++++++++ app/src/main/res/drawable-nodpi/circle.png | Bin 0 -> 13494 bytes 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/protect/card_locker/ShortcutHelper.java create mode 100644 app/src/main/res/drawable-nodpi/circle.png diff --git a/app/build.gradle b/app/build.gradle index 0bcaebec4..98cf5bc1a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ android { defaultConfig { applicationId "protect.card_locker" minSdkVersion 17 - targetSdkVersion 23 + targetSdkVersion 25 versionCode 14 versionName "0.13" } diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 3bd10c4ed..89d004e44 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -354,6 +354,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity DBHelper db = new DBHelper(LoyaltyCardViewActivity.this); db.deleteLoyaltyCard(loyaltyCardId); + + ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId); + finish(); dialog.dismiss(); } diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java index 7eb745794..2bf8dafaa 100644 --- a/app/src/main/java/protect/card_locker/MainActivity.java +++ b/app/src/main/java/protect/card_locker/MainActivity.java @@ -7,7 +7,10 @@ import android.content.ClipboardManager; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; import android.database.Cursor; +import android.os.Build; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -27,6 +30,7 @@ import android.widget.Toast; import com.google.common.collect.ImmutableMap; import java.util.Calendar; +import java.util.List; import java.util.Map; import protect.card_locker.intro.IntroActivity; @@ -93,10 +97,14 @@ public class MainActivity extends AppCompatActivity LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected); Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class); + i.setAction(""); final Bundle b = new Bundle(); b.putInt("id", loyaltyCard.id); b.putBoolean("view", true); i.putExtras(b); + + ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i); + startActivity(i); } }); diff --git a/app/src/main/java/protect/card_locker/ShortcutHelper.java b/app/src/main/java/protect/card_locker/ShortcutHelper.java new file mode 100644 index 000000000..0b9dbc137 --- /dev/null +++ b/app/src/main/java/protect/card_locker/ShortcutHelper.java @@ -0,0 +1,147 @@ +package protect.card_locker; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Build; + +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; + + /** + * 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. + */ + @TargetApi(25) + static void updateShortcuts(Context context, LoyaltyCard card, Intent intent) + { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) + { + ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); + LinkedList list = new LinkedList<>(shortcutManager.getDynamicShortcuts()); + + 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, new Comparator() + { + @Override + public int compare(ShortcutInfo o1, ShortcutInfo o2) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) + { + return o1.getRank() - o2.getRank(); + } + else + { + return 0; + } + } + }); + + 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. + ShortcutInfo found = list.remove(foundIndex.intValue()); + list.addFirst(found); + } + else + { + // The item is new to the list. First, we need to trim the list + // until it is able to accept a new item, then the item is + // inserted. + while(list.size() >= MAX_SHORTCUTS) + { + list.pollLast(); + } + + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, Integer.toString(card.id)) + .setShortLabel(card.store) + .setLongLabel(card.store) + .setIntent(intent) + .build(); + + list.addFirst(shortcut); + } + + LinkedList finalList = new LinkedList<>(); + + // The ranks are now updated; the order in the list is the rank. + for(int index = 0; index < list.size(); index++) + { + ShortcutInfo prevShortcut = list.get(index); + + ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId()) + .setShortLabel(prevShortcut.getShortLabel()) + .setLongLabel(prevShortcut.getLongLabel()) + .setIntent(prevShortcut.getIntent()) + .setIcon(Icon.createWithResource(context, R.drawable.circle)) + .setRank(index) + .build(); + + finalList.addLast(updatedShortcut); + } + + shortcutManager.setDynamicShortcuts(finalList); + } + } + + /** + * Remove the given card id from the app shortcuts, if such a + * shortcut exists. + */ + @TargetApi(25) + static void removeShortcut(Context context, int cardId) + { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) + { + ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); + List list = shortcutManager.getDynamicShortcuts(); + + String shortcutId = Integer.toString(cardId); + + for(int index = 0; index < list.size(); index++) + { + if(list.get(index).getId().equals(shortcutId)) + { + list.remove(index); + break; + } + } + + shortcutManager.setDynamicShortcuts(list); + } + } +} diff --git a/app/src/main/res/drawable-nodpi/circle.png b/app/src/main/res/drawable-nodpi/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd4dd58b0b3549ec843ba9874673a4b6b600c5c GIT binary patch literal 13494 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