diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a93a40cf7..f9c1e7a43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ android:name="android.permission.READ_EXTERNAL_STORAGE"/> + + android:windowSoftInputMode="stateHidden" + android:exported="true"/> - - - - - - - - - - - - - 0 && barcodeTypeField.getText().length() > 0) { String formatString = barcodeTypeField.getText().toString(); @@ -274,6 +302,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity { String store = storeFieldEdit.getText().toString(); String note = noteFieldEdit.getText().toString(); + boolean shouldAddShortcut = shortcutCheckbox.isChecked(); String cardId = cardIdFieldView.getText().toString(); String barcodeType = barcodeTypeField.getText().toString(); @@ -296,19 +325,45 @@ public class LoyaltyCardViewActivity extends AppCompatActivity } else { - db.insertLoyaltyCard(store, note, cardId, barcodeType); + loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType); + } + + if(shouldAddShortcut) + { + addShortcut(loyaltyCardId, store); } finish(); } + private void addShortcut(int id, String name) + { + Intent shortcutIntent = new Intent(this, LoyaltyCardViewActivity.class); + shortcutIntent.setAction(Intent.ACTION_MAIN); + // Prevent instances of the view activity from piling up; if one exists let this + // one replace it. + shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + Bundle bundle = new Bundle(); + bundle.putInt("id", id); + bundle.putBoolean("view", true); + shortcutIntent.putExtras(bundle); + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource + .fromContext(this, R.mipmap.ic_launcher)); + intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + // Do not duplicate the shortcut if it is already there + intent.putExtra("duplicate", false); + getApplicationContext().sendBroadcast(intent); + + Toast.makeText(this, R.string.addedShortcut, Toast.LENGTH_LONG).show(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { - final Bundle b = getIntent().getExtras(); - final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false); - final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false); - if(viewLoyaltyCard) { getMenuInflater().inflate(R.menu.card_view_menu, menu); @@ -332,9 +387,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity { int id = item.getItemId(); - final Bundle b = getIntent().getExtras(); - final int loyaltyCardId = b != null ? b.getInt("id") : 0; - switch(id) { case android.R.id.home: diff --git a/app/src/main/java/protect/card_locker/ShortcutHelper.java b/app/src/main/java/protect/card_locker/ShortcutHelper.java index 0b9dbc137..5773e5432 100644 --- a/app/src/main/java/protect/card_locker/ShortcutHelper.java +++ b/app/src/main/java/protect/card_locker/ShortcutHelper.java @@ -103,10 +103,16 @@ class ShortcutHelper { ShortcutInfo prevShortcut = list.get(index); + Intent shortcutIntent = prevShortcut.getIntent(); + + // Prevent instances of the view activity from piling up; if one exists let this + // one replace it. + shortcutIntent.setFlags(shortcutIntent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP); + ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId()) .setShortLabel(prevShortcut.getShortLabel()) .setLongLabel(prevShortcut.getLongLabel()) - .setIntent(prevShortcut.getIntent()) + .setIntent(shortcutIntent) .setIcon(Icon.createWithResource(context, R.drawable.circle)) .setRank(index) .build(); diff --git a/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetConfigure.java b/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetConfigure.java deleted file mode 100644 index 1f611876f..000000000 --- a/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetConfigure.java +++ /dev/null @@ -1,130 +0,0 @@ -package protect.card_locker.appwidget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import protect.card_locker.DBHelper; -import protect.card_locker.LoyaltyCard; -import protect.card_locker.LoyaltyCardCursorAdapter; -import protect.card_locker.R; - -/** - * The configuration screen for the CardAppWidgetProvider widget. - */ -public class CardAppWidgetConfigure extends AppCompatActivity -{ - static final String TAG = "LoyaltyCardLocker"; - - private static final String PREFS_NAME - = "protect.card_locker.appwidget.CardAppWidgetProvider"; - private static final String PREF_PREFIX_KEY = "prefix_"; - - int appWidgetId_ = AppWidgetManager.INVALID_APPWIDGET_ID; - - @Override - public void onCreate(Bundle bundle) - { - super.onCreate(bundle); - - // Set the result to CANCELED. This will cause the widget host to cancel - // out of the widget placement if they press the back button. - setResult(RESULT_CANCELED); - - setContentView(R.layout.main_activity); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - toolbar.setVisibility(View.GONE); - - setTitle(R.string.selectCardTitle); - - // Find the widget id from the intent. - Intent intent = getIntent(); - Bundle extras = intent.getExtras(); - if (extras != null) - { - appWidgetId_ = extras.getInt( - AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - } - - // If they gave us an intent without the widget id, just bail. - if (appWidgetId_ == AppWidgetManager.INVALID_APPWIDGET_ID) - { - finish(); - } - - final DBHelper db = new DBHelper(this); - - // If there are no cards, bail - if(db.getLoyaltyCardCount() == 0) - { - finish(); - } - - final ListView cardList = (ListView) findViewById(R.id.list); - cardList.setVisibility(View.VISIBLE); - - Cursor cardCursor = db.getLoyaltyCardCursor(); - - final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor); - cardList.setAdapter(adapter); - - cardList.setOnItemClickListener(new AdapterView.OnItemClickListener() - { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - Context context = CardAppWidgetConfigure.this; - Cursor selected = (Cursor) parent.getItemAtPosition(position); - LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected); - - Log.d(TAG, "Saving card " + loyaltyCard.store + "," + loyaltyCard.id + " at " + appWidgetId_); - - // Save the association of the card to the widget - saveIdPref(context, appWidgetId_, loyaltyCard.id); - - // Push widget update to surface with newly set association - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - CardAppWidgetProvider.updateAppWidget(context, appWidgetManager, appWidgetId_); - - // Make sure we pass back the original appWidgetId - Intent resultValue = new Intent(); - resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId_); - setResult(RESULT_OK, resultValue); - finish(); - } - }); - } - - // Write the prefix to the SharedPreferences object for this widget - static void saveIdPref(Context context, int appWidgetId, int id) - { - SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit(); - prefs.putInt(PREF_PREFIX_KEY + appWidgetId, id); - prefs.commit(); - } - - // Read the prefix from the SharedPreferences object for this widget. - // If there is no preference saved, get the default from a resource - static Integer loadIdPref(Context context, int appWidgetId) - { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0); - int id = prefs.getInt(PREF_PREFIX_KEY + appWidgetId, -1); - if(id >= 0) - { - return id; - } - else - { - return null; - } - } -} diff --git a/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetProvider.java b/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetProvider.java deleted file mode 100644 index 162e6ff90..000000000 --- a/app/src/main/java/protect/card_locker/appwidget/CardAppWidgetProvider.java +++ /dev/null @@ -1,79 +0,0 @@ -package protect.card_locker.appwidget; - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.Context; -import android.content.Intent; - -import android.os.Bundle; -import android.util.Log; -import android.widget.RemoteViews; - -import protect.card_locker.DBHelper; -import protect.card_locker.LoyaltyCard; -import protect.card_locker.LoyaltyCardViewActivity; -import protect.card_locker.R; - -public class CardAppWidgetProvider extends AppWidgetProvider -{ - private static final String TAG = "LoyaltyCardLocker"; - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) - { - Log.d(TAG, "CardAppWidgetProvider onUpdate"); - // For each widget that needs an update, get the text that we should display: - // - Create a RemoteViews object for it - // - Set the text in the RemoteViews object - // - Tell the AppWidgetManager to show that views object for the widget. - for (int appWidgetId : appWidgetIds) - { - updateAppWidget(context, appWidgetManager, appWidgetId); - } - } - - static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) - { - Log.d(TAG, "updateAppWidget appWidgetId=" + appWidgetId); - - LoyaltyCard card = null; - DBHelper db = new DBHelper(context); - - Integer id = CardAppWidgetConfigure.loadIdPref(context, appWidgetId); - if(id != null) - { - Log.d(TAG, "updateAppWidget Retrieved id " + id); - card = db.getLoyaltyCard(id); - } - - if(card != null) - { - Log.d(TAG, "updateAppWidget Updating widget " + appWidgetId + " to load " + card.store); - - // Construct the RemoteViews object. It takes the package name (in our case, it's our - // package, but it needs this because on the other side it's the widget host inflating - // the layout from our package). - RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider); - views.setTextViewText(R.id.title, card.store); - - // Launch the view activity when clicked - Intent intent = new Intent(context, LoyaltyCardViewActivity.class); - Bundle extras = new Bundle(); - extras.putInt("id", id); - extras.putBoolean("view", true); - intent.putExtras(extras); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); - views.setOnClickPendingIntent(R.id.widget, pendingIntent); - - // Tell the widget manager - appWidgetManager.updateAppWidget(appWidgetId, views); - } - else - { - Log.d(TAG, "updateAppWidget, no card ID associated with widget " + appWidgetId - + ", ignoring update"); - } - } -} diff --git a/app/src/main/res/layout/appwidget_provider.xml b/app/src/main/res/layout/appwidget_provider.xml deleted file mode 100644 index 1c8c550e4..000000000 --- a/app/src/main/res/layout/appwidget_provider.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/loyalty_card_view_activity.xml b/app/src/main/res/layout/loyalty_card_view_activity.xml index 8e2a6da93..940a869d7 100644 --- a/app/src/main/res/layout/loyalty_card_view_activity.xml +++ b/app/src/main/res/layout/loyalty_card_view_activity.xml @@ -146,6 +146,55 @@ android:background="@color/inputBorder" /> + + + + + + + + + + + + + + + Store Note + Add Shortcut Card ID Barcode Type @@ -26,12 +27,12 @@ OK Copy ID to clipboard Send… + Added shortcut Edit Loyalty Card Add Loyalty Card View Loyalty Card Scan Card\'s Barcode - Select Card Image of card\'s barcode diff --git a/app/src/main/res/xml/appwidget_provider.xml b/app/src/main/res/xml/appwidget_provider.xml deleted file mode 100644 index 24c3b655d..000000000 --- a/app/src/main/res/xml/appwidget_provider.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/test/java/protect/card_locker/DatabaseTest.java b/app/src/test/java/protect/card_locker/DatabaseTest.java index 7eab1444d..7d577469e 100644 --- a/app/src/test/java/protect/card_locker/DatabaseTest.java +++ b/app/src/test/java/protect/card_locker/DatabaseTest.java @@ -36,7 +36,8 @@ public class DatabaseTest public void addRemoveOneGiftCard() { assertEquals(0, db.getLoyaltyCardCount()); - boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString()); + long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString()); + boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -56,7 +57,8 @@ public class DatabaseTest @Test public void updateGiftCard() { - boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString()); + long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString()); + boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -86,7 +88,8 @@ public class DatabaseTest @Test public void emptyGiftCardValues() { - boolean result = db.insertLoyaltyCard("", "", "", ""); + long id = db.insertLoyaltyCard("", "", "", ""); + boolean result = (id != -1); assertTrue(result); assertEquals(1, db.getLoyaltyCardCount()); @@ -107,8 +110,9 @@ public class DatabaseTest // that they are sorted for(int index = CARDS_TO_ADD-1; index >= 0; index--) { - boolean result = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index, + long id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index, BarcodeFormat.UPC_A.toString()); + boolean result = (id != -1); assertTrue(result); } diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java index 7f96e5519..858e37145 100644 --- a/app/src/test/java/protect/card_locker/ImportExportTest.java +++ b/app/src/test/java/protect/card_locker/ImportExportTest.java @@ -65,7 +65,8 @@ public class ImportExportTest { String storeName = String.format("store, \"%4d", index); String note = String.format("note, \"%4d", index); - boolean result = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE); + long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE); + boolean result = (id != -1); assertTrue(result); } diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java index 81320604c..6f7b56731 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.View; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; @@ -29,6 +30,7 @@ import org.robolectric.android.controller.ActivityController; import java.io.IOException; +import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -87,6 +89,7 @@ public class LoyaltyCardViewActivityTest */ private void saveLoyaltyCardWithArguments(final Activity activity, final String store, final String note, + final boolean addShortcut, final String cardId, final String barcodeType, boolean creatingNewCard) @@ -103,18 +106,25 @@ public class LoyaltyCardViewActivityTest final EditText storeField = (EditText) activity.findViewById(R.id.storeNameEdit); final EditText noteField = (EditText) activity.findViewById(R.id.noteEdit); + final CheckBox shortcutCheckbox = (CheckBox) activity.findViewById(R.id.shortcutCheckbox); final TextView cardIdField = (TextView) activity.findViewById(R.id.cardIdView); final TextView barcodeTypeField = (TextView) activity.findViewById(R.id.barcodeType); storeField.setText(store); noteField.setText(note); + shortcutCheckbox.setChecked(addShortcut); cardIdField.setText(cardId); barcodeTypeField.setText(barcodeType); + ShortcutAddedReceiver shortcutAddedReceiver = new ShortcutAddedReceiver(); + shortcutAddedReceiver.registerReceiver(activity); + assertEquals(false, activity.isFinishing()); shadowOf(activity).clickMenuItem(R.id.action_save); assertEquals(true, activity.isFinishing()); + shortcutAddedReceiver.unregisterReceiver(activity); + assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -122,6 +132,28 @@ public class LoyaltyCardViewActivityTest assertEquals(note, card.note); assertEquals(cardId, card.cardId); assertEquals(barcodeType, card.barcodeType); + + Intent shortcutRequest = shortcutAddedReceiver.lastRequest(); + + if(addShortcut) + { + assertNotNull(shortcutRequest); + + String name = shortcutRequest.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + assertEquals(card.store, name); + + Intent startIntent = shortcutRequest.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + assertNotNull(startIntent); + Bundle startBundle = startIntent.getExtras(); + assertNotNull(startBundle); + + assertEquals(card.id, startBundle.getInt("id", -1)); + assertEquals(true, startBundle.getBoolean("view", false)); + } + else + { + assertNull(shortcutRequest); + } } /** @@ -184,6 +216,8 @@ public class LoyaltyCardViewActivityTest checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store); checkFieldProperties(activity, R.id.storeNameView, viewVisibility, store); checkFieldProperties(activity, R.id.noteEdit, editVisibility, note); + checkFieldProperties(activity, R.id.shortcutBorder, editVisibility, null); + checkFieldProperties(activity, R.id.shortcutTablerow, editVisibility, null); checkFieldProperties(activity, R.id.noteView, viewVisibility, note); checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId); checkFieldProperties(activity, R.id.cardIdDivider, cardId.isEmpty() ? View.GONE : View.VISIBLE, null); @@ -274,7 +308,30 @@ public class LoyaltyCardViewActivityTest checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE); // Save and check the gift card - saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, BARCODE_TYPE, true); + saveLoyaltyCardWithArguments(activity, "store", "note", false, BARCODE_DATA, BARCODE_TYPE, true); + } + + @Test + public void startWithoutParametersCaptureBarcodeCreateLoyaltyCardSaveShortcut() throws IOException + { + registerMediaStoreIntentHandler(); + + ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create(); + activityController.start(); + activityController.visible(); + activityController.resume(); + + Activity activity = (Activity)activityController.get(); + + checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", ""); + + // Complete barcode capture successfully + captureBarcodeWithResult(activity, R.id.captureButton, true); + + checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE); + + // Save and check the gift card + saveLoyaltyCardWithArguments(activity, "store", "note", true, BARCODE_DATA, BARCODE_TYPE, true); } @Test diff --git a/app/src/test/java/protect/card_locker/ShortcutAddedReceiver.java b/app/src/test/java/protect/card_locker/ShortcutAddedReceiver.java new file mode 100644 index 000000000..d29c17eac --- /dev/null +++ b/app/src/test/java/protect/card_locker/ShortcutAddedReceiver.java @@ -0,0 +1,39 @@ +package protect.card_locker; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +public class ShortcutAddedReceiver extends BroadcastReceiver +{ + public static final String SHORTCUT_ADD_REQUEST = "com.android.launcher.action.INSTALL_SHORTCUT"; + + private Intent _request = null; + + @Override + public void onReceive(Context context, Intent intent) + { + _request = intent; + } + + public void registerReceiver(Context context) + { + context.registerReceiver(this, new IntentFilter(SHORTCUT_ADD_REQUEST)); + } + + public void unregisterReceiver(Context context) + { + context.unregisterReceiver(this); + } + + public Intent lastRequest() + { + return _request; + } + + public void reset() + { + _request = null; + } +}