Add tests for ContactsHelperAsync

Change-Id: I6ce3c7523fab97d4dc4d4a23ded84af8ec882e5c
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 44fa654..974fc51 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -29,6 +29,7 @@
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -57,15 +58,22 @@
                 Object cookie);
     }
 
+    /**
+     * Interface to enable stubbing of the call to openInputStream
+     */
+    public interface ContentResolverAdapter {
+        InputStream openInputStream(Context context, Uri uri) throws FileNotFoundException;
+    }
+
     // constants
     private static final int EVENT_LOAD_IMAGE = 1;
 
     /** Handler run on a worker thread to load photo asynchronously. */
     private Handler mThreadHandler;
-    private final TelecomSystem.SyncRoot mLock;
+    private final ContentResolverAdapter mContentResolverAdapter;
 
-    public ContactsAsyncHelper(TelecomSystem.SyncRoot lock) {
-        mLock = lock;
+    public ContactsAsyncHelper(ContentResolverAdapter contentResolverAdapter) {
+        mContentResolverAdapter = contentResolverAdapter;
     }
 
     private static final class WorkerArgs {
@@ -95,8 +103,8 @@
                     InputStream inputStream = null;
                     try {
                         try {
-                            inputStream = args.context.getContentResolver()
-                                    .openInputStream(args.displayPhotoUri);
+                            inputStream = mContentResolverAdapter.openInputStream(
+                                    args.context, args.displayPhotoUri);
                         } catch (Exception e) {
                             Log.e(this, e, "Error opening photo input stream");
                         }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index d28a9d7..6b9a7d7 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -25,8 +25,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.os.UserHandle;
 
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
 /**
  * Top-level Application class for Telecom.
  */
@@ -127,7 +131,14 @@
 
         mMissedCallNotifier = missedCallNotifier;
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
-        mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
+        mContactsAsyncHelper = new ContactsAsyncHelper(
+                new ContactsAsyncHelper.ContentResolverAdapter() {
+                    @Override
+                    public InputStream openInputStream(Context context, Uri uri)
+                            throws FileNotFoundException {
+                        return context.getContentResolver().openInputStream(uri);
+                    }
+                });
         BluetoothManager bluetoothManager = new BluetoothManager(mContext);
         WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
 
diff --git a/tests/res/drawable-xhdpi/contacts_sample_photo.png b/tests/res/drawable-xhdpi/contacts_sample_photo.png
new file mode 100644
index 0000000..6c0ba64
--- /dev/null
+++ b/tests/res/drawable-xhdpi/contacts_sample_photo.png
Binary files differ
diff --git a/tests/res/drawable-xhdpi/contacts_sample_photo_small.png b/tests/res/drawable-xhdpi/contacts_sample_photo_small.png
new file mode 100644
index 0000000..f53602e
--- /dev/null
+++ b/tests/res/drawable-xhdpi/contacts_sample_photo_small.png
Binary files differ
diff --git a/tests/res/values/dimens.xml b/tests/res/values/dimens.xml
new file mode 100644
index 0000000..031f5b4
--- /dev/null
+++ b/tests/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<resources>
+    <dimen name="notification_icon_size">64dp</dimen>
+</resources>
diff --git a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
new file mode 100644
index 0000000..07ae122
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 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
+ */
+
+package com.android.server.telecom.tests;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.android.server.telecom.ContactsAsyncHelper;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+public class ContactsAsyncHelperTest extends TelecomTestCase {
+    private static final Uri SAMPLE_CONTACT_PHOTO_URI = Uri.parse(
+            "android.resource://com.android.server.telecom.tests/"
+                    + R.drawable.contacts_sample_photo);
+
+    private static final Uri SAMPLE_CONTACT_PHOTO_URI_SMALL = Uri.parse(
+            "android.resource://com.android.server.telecom.tests/"
+                    + R.drawable.contacts_sample_photo_small);
+
+    private static final int TOKEN = 4847524;
+    private static final int TEST_TIMEOUT = 500;
+    private static final Object COOKIE = new Object();
+
+    public static class ImageLoadListenerImpl
+            implements ContactsAsyncHelper.OnImageLoadCompleteListener {
+        @Override
+        public void onImageLoadComplete(int token, Drawable photo,
+                Bitmap photoIcon, Object cookie) {
+        }
+    }
+
+    private ImageLoadListenerImpl mListener = spy(new ImageLoadListenerImpl());
+
+    private ContactsAsyncHelper.ContentResolverAdapter mWorkingContentResolverAdapter =
+            new ContactsAsyncHelper.ContentResolverAdapter() {
+                @Override
+                public InputStream openInputStream(Context context, Uri uri)
+                        throws FileNotFoundException {
+                    return context.getContentResolver().openInputStream(uri);
+                }
+            };
+
+    private ContactsAsyncHelper.ContentResolverAdapter mNullContentResolverAdapter =
+            new ContactsAsyncHelper.ContentResolverAdapter() {
+                @Override
+                public InputStream openInputStream(Context context, Uri uri)
+                        throws FileNotFoundException {
+                    return null;
+                }
+            };
+
+    @Override
+    public void setUp() throws Exception {
+        mContext = getTestContext();
+        super.setUp();
+    }
+
+    public void testEmptyUri() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+        try {
+            cah.startObtainPhotoAsync(TOKEN, mContext, null, mListener, COOKIE);
+        } catch (IllegalStateException e) {
+            // expected to fail
+        }
+        verify(mListener, timeout(TEST_TIMEOUT).never()).onImageLoadComplete(anyInt(),
+                any(Drawable.class), any(Bitmap.class), anyObject());
+    }
+
+    public void testNullReturnFromOpenInputStream() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                isNull(Drawable.class), isNull(Bitmap.class), eq(COOKIE));
+    }
+
+    public void testImageScaling() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
+
+        ArgumentCaptor<Drawable> photoCaptor = ArgumentCaptor.forClass(Drawable.class);
+        ArgumentCaptor<Bitmap> iconCaptor = ArgumentCaptor.forClass(Bitmap.class);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                photoCaptor.capture(), iconCaptor.capture(), eq(COOKIE));
+
+        Bitmap capturedPhoto = ((BitmapDrawable) photoCaptor.getValue()).getBitmap();
+        assertTrue(getExpectedPhoto(SAMPLE_CONTACT_PHOTO_URI).sameAs(capturedPhoto));
+        int iconSize = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.notification_icon_size);
+        assertTrue(iconSize >= iconCaptor.getValue().getHeight());
+        assertTrue(iconSize >= iconCaptor.getValue().getWidth());
+    }
+
+    public void testNoScaling() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI_SMALL,
+                mListener, COOKIE);
+
+        ArgumentCaptor<Drawable> photoCaptor = ArgumentCaptor.forClass(Drawable.class);
+        ArgumentCaptor<Bitmap> iconCaptor = ArgumentCaptor.forClass(Bitmap.class);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                photoCaptor.capture(), iconCaptor.capture(), eq(COOKIE));
+
+        Bitmap capturedPhoto = ((BitmapDrawable) photoCaptor.getValue()).getBitmap();
+        assertTrue(getExpectedPhoto(SAMPLE_CONTACT_PHOTO_URI_SMALL).sameAs(capturedPhoto));
+        assertTrue(capturedPhoto.sameAs(iconCaptor.getValue()));
+    }
+
+    private Bitmap getExpectedPhoto(Uri uri) {
+        InputStream is;
+        try {
+            is = mContext.getContentResolver().openInputStream(uri);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+
+        Drawable d = Drawable.createFromStream(is, uri.toString());
+        return ((BitmapDrawable) d).getBitmap();
+    }
+}