Merge "Implement starring of tags." into gingerbread
diff --git a/res/layout/tag_edit_image.xml b/res/layout/tag_edit_image.xml
new file mode 100644
index 0000000..6f9151d
--- /dev/null
+++ b/res/layout/tag_edit_image.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:padding="8dip"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:text="@string/photo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+    />
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+    />
+</LinearLayout>
+
+
+
diff --git a/src/com/android/apps/tag/EditTagActivity.java b/src/com/android/apps/tag/EditTagActivity.java
index 8bc811c..d77395b 100644
--- a/src/com/android/apps/tag/EditTagActivity.java
+++ b/src/com/android/apps/tag/EditTagActivity.java
@@ -125,7 +125,7 @@
      */
     public void addRecord(RecordEditInfo editInfo) {
         mRecords.add(Preconditions.checkNotNull(editInfo));
-        mInflater.inflate(R.layout.tag_divider, mContentRoot);
+        getContentRoot().addView(mInflater.inflate(R.layout.tag_divider, mContentRoot, false));
         getContentRoot().addView(editInfo.getEditView(this, mInflater, mContentRoot));
     }
 
diff --git a/src/com/android/apps/tag/record/ImageRecord.java b/src/com/android/apps/tag/record/ImageRecord.java
index 53cc2b4..4a14959 100644
--- a/src/com/android/apps/tag/record/ImageRecord.java
+++ b/src/com/android/apps/tag/record/ImageRecord.java
@@ -27,6 +27,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.media.ThumbnailUtils;
 import android.nfc.NdefRecord;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -111,9 +112,9 @@
 
     public static NdefRecord newImageRecord(Bitmap bitmap) {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
         byte[] content = out.toByteArray();
-        return MimeRecord.newMimeRecord("image/png", content);
+        return MimeRecord.newMimeRecord("image/jpeg", content);
     }
 
     private static class ImageRecordEditInfo extends RecordEditInfo {
@@ -121,6 +122,8 @@
         private String mCurrentPath;
         private Bitmap mCachedValue;
 
+        public static final int MAX_IMAGE_SIZE = 128;
+
         public ImageRecordEditInfo(Intent intent) {
             super(RECORD_TYPE);
             mIntent = intent;
@@ -145,7 +148,16 @@
 
         private Bitmap getValueInternal() {
             if (mCachedValue == null) {
-                mCachedValue = BitmapFactory.decodeFile(mCurrentPath);
+                Bitmap original = BitmapFactory.decodeFile(mCurrentPath);
+                int width = original.getWidth();
+                int height = original.getHeight();
+                int major = (width > height) ? width : height;
+                if (major > MAX_IMAGE_SIZE) {
+                    double scale = 1.0 * MAX_IMAGE_SIZE / major;
+                    width *= scale;
+                    height *= scale;
+                }
+                mCachedValue = ThumbnailUtils.extractThumbnail(original, width, height);
             }
             return mCachedValue;
         }
@@ -173,9 +185,9 @@
 
         @Override
         public View getEditView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
-            // TODO: make a nicer edit view for images. Right now we just plop the entire image
-            // down. It should also be tappable to select a new image.
-            return new ImageRecord(getValueInternal()).getView(activity, inflater, parent);
+            View result = inflater.inflate(R.layout.tag_edit_image, parent, false);
+            ((ImageView) result.findViewById(R.id.image)).setImageBitmap(getValueInternal());
+            return result;
         }
 
         @Override
diff --git a/tests/src/com/android/apps/tag/provider/ProviderTests.java b/tests/src/com/android/apps/tag/provider/ProviderTests.java
new file mode 100644
index 0000000..1ca0c01
--- /dev/null
+++ b/tests/src/com/android/apps/tag/provider/ProviderTests.java
@@ -0,0 +1,356 @@
+package com.android.apps.tag.provider;
+
+import com.android.apps.tag.provider.TagContract.NdefMessages;
+import com.android.apps.tag.provider.TagContract.NdefRecords;
+import com.android.apps.tag.provider.TagContract.NdefTags;
+import com.android.apps.tag.record.TextRecord;
+import com.google.common.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.UriMatcher;
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NdefTag;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class ProviderTests extends AndroidTestCase {
+
+    public static class ContentValuesCursor extends AbstractCursor {
+
+        private String[] mColumnNames;
+        private ArrayList<ContentValues> mValues;
+
+        public ContentValuesCursor(String[] columnNames, ArrayList<ContentValues> values) {
+            mColumnNames = columnNames;
+            mValues = values;
+        }
+
+        @Override
+        public int getCount() {
+            return mValues.size();
+        }
+
+        @Override
+        public String[] getColumnNames() {
+            return mColumnNames;
+        }
+
+        @Override
+        public String getString(int column) {
+            return mValues.get(mPos).getAsString(mColumnNames[column]);
+        }
+
+        @Override
+        public short getShort(int column) {
+            return mValues.get(mPos).getAsShort(mColumnNames[column]);
+        }
+
+        @Override
+        public int getInt(int column) {
+            return mValues.get(mPos).getAsInteger(mColumnNames[column]);
+        }
+
+        @Override
+        public long getLong(int column) {
+            return mValues.get(mPos).getAsLong(mColumnNames[column]);
+        }
+
+        @Override
+        public float getFloat(int column) {
+            return mValues.get(mPos).getAsFloat(mColumnNames[column]);
+        }
+
+        @Override
+        public double getDouble(int column) {
+            return mValues.get(mPos).getAsDouble(mColumnNames[column]);
+        }
+
+        @Override
+        public boolean isNull(int column) {
+            return mValues.get(mPos).containsKey(mColumnNames[column]) == false;
+        }
+
+        @Override
+        public byte[] getBlob(int column) {
+            return mValues.get(mPos).getAsByteArray(mColumnNames[column]);
+        }
+    }
+
+    public static class TestProvider extends MockContentProvider {
+
+        private static final int NDEF_MESSAGES = 1000;
+        private static final int NDEF_RECORDS = 1002;
+        private static final int NDEF_TAGS = 1004;
+
+        private static final UriMatcher MATCHER;
+
+        static {
+            MATCHER = new UriMatcher(0);
+            String auth = TagContract.AUTHORITY;
+
+            MATCHER.addURI(auth, "ndef_msgs", NDEF_MESSAGES);
+            MATCHER.addURI(auth, "ndef_records", NDEF_RECORDS);
+            MATCHER.addURI(auth, "ndef_tags", NDEF_TAGS);
+        }
+
+        private int nextId = 0;
+        private ArrayList<ContentValues> messages = Lists.newArrayList();
+        private ArrayList<ContentValues> records = Lists.newArrayList();
+        private ArrayList<ContentValues> tags = Lists.newArrayList();
+
+        @Override
+        public boolean onCreate() {
+            return false;
+        }
+
+        @Override
+        public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
+            final int numOperations = operations.size();
+            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+            try {
+                for (int i = 0; i < numOperations; i++) {
+                    final ContentProviderOperation operation = operations.get(i);
+                    results[i] = operation.apply(this, results, i);
+                }
+            } catch (OperationApplicationException e) {
+                fail(e.toString());
+            }
+
+            return results;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            int match = MATCHER.match(uri);
+            switch (match) {
+            case NDEF_MESSAGES: {
+                messages.add(values);
+                break;
+            }
+
+            case NDEF_RECORDS: {
+                records.add(values);
+                break;
+            }
+
+            case NDEF_TAGS: {
+                tags.add(values);
+                break;
+            }
+            }
+
+            return ContentUris.withAppendedId(uri, nextId++);
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+            int match = MATCHER.match(uri);
+            switch (match) {
+            case NDEF_MESSAGES: {
+                return toCursor(messages);
+            }
+
+            case NDEF_RECORDS: {
+                return toCursor(records);
+            }
+
+            case NDEF_TAGS: {
+                return toCursor(tags);
+            }
+            }
+
+            return null;
+        }
+
+        private String[] getColumnNames(ArrayList<ContentValues> values) {
+            if (values.size() < 0) {
+                return null;
+            }
+
+            ContentValues cv = values.get(0);
+
+            final int size = cv.size();
+            String[] columns = new String[size];
+
+            Set<Entry<String, Object>> valueSet = cv.valueSet();
+
+            int index = 0;
+            for (Entry<String, Object> entry : valueSet) {
+                columns[index++] = entry.getKey();
+            }
+
+            return columns;
+        }
+
+        private Cursor toCursor(ArrayList<ContentValues> values) {
+            String[] columnNames = getColumnNames(values);
+            assertNotNull(columnNames);
+            return new ContentValuesCursor(columnNames, values);
+        }
+    }
+
+    public void testOrdinals() throws Exception {
+        // This test creates a NdefTag with three NdefMessage each containing a single
+        // record and ensures the ordinal for the records and messages
+
+        NdefMessage[] msgs = new NdefMessage[3];
+        msgs[0] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("0", Locale.US) });
+        msgs[1] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("1", Locale.US) });
+        msgs[2] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("2", Locale.US) });
+
+        // String typeName, byte[] uid, int nativeHandle, NdefMessage[] messages
+        NdefTag tag = new NdefTag(NdefTag.TARGET_TYPE_4, new byte[] { }, 0, msgs);
+
+        Context context = getContext();
+
+        ArrayList<ContentProviderOperation> ops = NdefTags.toContentProviderOperations(context, tag);
+
+        // There should be seven operations. tag insert, three msg inserts, and three record inserts
+        assertEquals(7, ops.size());
+
+        // Write the operation out to the database
+
+        TestProvider provider = new TestProvider();
+        provider.applyBatch(ops);
+
+        // Now verify the ordinals of the messages and records
+
+        Cursor cursor = provider.query(NdefMessages.CONTENT_URI, null, null, null, null);
+        assertNotNull(cursor);
+        assertEquals(3, cursor.getCount());
+
+        int ordinalIndex = cursor.getColumnIndex(NdefMessages.ORDINAL);
+        assertTrue(ordinalIndex != -1);
+
+        int expectedOrdinal = 0;
+        while (cursor.moveToNext()) {
+            int ordinal = cursor.getInt(ordinalIndex);
+            assertEquals(expectedOrdinal++, ordinal);
+        }
+
+        // Test the records
+        cursor = provider.query(NdefRecords.CONTENT_URI, null, null, null, null);
+        assertNotNull(cursor);
+        assertEquals(3, cursor.getCount());
+
+        ordinalIndex = cursor.getColumnIndex(NdefRecords.ORDINAL);
+        assertTrue(ordinalIndex != -1);
+
+        int bytesIndex = cursor.getColumnIndex(NdefRecords.BYTES);
+        assertTrue(bytesIndex != -1);
+
+        int index = 0;
+        while (cursor.moveToNext()) {
+            assertEquals(index, cursor.getInt(ordinalIndex));
+            assertTrue(Arrays.equals(msgs[index].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+            index++;
+        }
+    }
+
+    public void testSmartPoster() throws Exception {
+
+        NdefMessage posterMessage = new NdefMessage(new NdefRecord[] {
+                TextRecord.newTextRecord("4", Locale.US),
+                TextRecord.newTextRecord("5", Locale.US) });
+
+        NdefRecord poster = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_SMART_POSTER,
+                new byte[] { }, posterMessage.toByteArray());
+
+
+        NdefMessage[] msgs = new NdefMessage[4];
+        msgs[0] = new NdefMessage(new NdefRecord[] {
+                TextRecord.newTextRecord("0", Locale.US),
+                TextRecord.newTextRecord("1", Locale.US)
+                });
+        msgs[1] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("2", Locale.US) });
+        msgs[2] = new NdefMessage(new NdefRecord[] { poster });
+        msgs[3] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("6", Locale.US) });
+
+        // String typeName, byte[] uid, int nativeHandle, NdefMessage[] messages
+        NdefTag tag = new NdefTag(NdefTag.TARGET_TYPE_4, new byte[] { }, 0, msgs);
+
+
+        Context context = getContext();
+
+        ArrayList<ContentProviderOperation> ops = NdefTags.toContentProviderOperations(context, tag);
+
+        // There should be seven operations. tag insert, three msg inserts, and three record inserts
+        assertEquals(12, ops.size());
+
+        // Write the operation out to the database
+
+        TestProvider provider = new TestProvider();
+        provider.applyBatch(ops);
+
+        Cursor cursor = provider.query(NdefMessages.CONTENT_URI, null, null, null, null);
+        assertNotNull(cursor);
+        assertEquals(4, cursor.getCount());
+
+        int ordinalIndex = cursor.getColumnIndex(NdefMessages.ORDINAL);
+        assertTrue(ordinalIndex != -1);
+
+        int expectedOrdinal = 0;
+        while (cursor.moveToNext()) {
+            int ordinal = cursor.getInt(ordinalIndex);
+            assertEquals(expectedOrdinal++, ordinal);
+        }
+
+        // Test the records
+        cursor = provider.query(NdefRecords.CONTENT_URI, null, null, null, null);
+        assertNotNull(cursor);
+        assertEquals(7, cursor.getCount());
+
+        ordinalIndex = cursor.getColumnIndex(NdefRecords.ORDINAL);
+        assertTrue(ordinalIndex != -1);
+
+        int bytesIndex = cursor.getColumnIndex(NdefRecords.BYTES);
+        assertTrue(bytesIndex != -1);
+
+        int index = 0;
+
+        cursor.moveToNext();
+        assertEquals(0, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(msgs[index].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+
+        cursor.moveToNext();
+        assertEquals(1, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(msgs[index].getRecords()[1].getPayload(), cursor.getBlob(bytesIndex)));
+
+        index++;
+
+        cursor.moveToNext();
+        assertEquals(2, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+
+        cursor.moveToNext();
+        assertEquals(3, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+
+        cursor.moveToNext();
+        assertEquals(4, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(posterMessage.getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+
+        cursor.moveToNext();
+        assertEquals(5, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(posterMessage.getRecords()[1].getPayload(), cursor.getBlob(bytesIndex)));
+
+        cursor.moveToNext();
+        assertEquals(6, cursor.getInt(ordinalIndex));
+        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
+    }
+}