Increase the photo size to 480x480 or 720x720, depending on device

Bug:6202229

Change-Id: I98b38023585d154eccad302578f796a2318fd5b2
diff --git a/res/values/config.xml b/res/values/config.xml
deleted file mode 100644
index 426fcc2..0000000
--- a/res/values/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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>
-
-    <!--
-      Maximum dimension (height or width) of contact display photos or
-      photos from the social stream.
-    -->
-    <integer name="config_max_display_photo_dim">256</integer>
-
-    <!-- Maximum dimension (height or width) of contact photo thumbnails -->
-    <integer name="config_max_thumbnail_photo_dim">96</integer>
-
-</resources>
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index dfbfebe..1cba421 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1390,11 +1390,8 @@
         StrictMode.setThreadPolicy(
                 new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
 
-        Resources resources = getContext().getResources();
-        mMaxDisplayPhotoDim = resources.getInteger(
-                R.integer.config_max_display_photo_dim);
-        mMaxThumbnailPhotoDim = resources.getInteger(
-                R.integer.config_max_thumbnail_photo_dim);
+        mMaxThumbnailPhotoDim = PhotoProcessor.getMaxThumbnailSize();
+        mMaxDisplayPhotoDim = PhotoProcessor.getMaxDisplayPhotoSize();
 
         mFastScrollingIndexCache = new FastScrollingIndexCache(getContext());
 
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index bfe1c3d..79f9267 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -19,10 +19,8 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.util.Log;
 
@@ -143,10 +141,8 @@
     private boolean processPhoto(ContentValues values) {
         byte[] originalPhoto = values.getAsByteArray(Photo.PHOTO);
         if (originalPhoto != null) {
-            int maxDisplayPhotoDim = mContext.getResources().getInteger(
-                    R.integer.config_max_display_photo_dim);
-            int maxThumbnailPhotoDim = mContext.getResources().getInteger(
-                    R.integer.config_max_thumbnail_photo_dim);
+            int maxDisplayPhotoDim = PhotoProcessor.getMaxDisplayPhotoSize();
+            int maxThumbnailPhotoDim = PhotoProcessor.getMaxThumbnailSize();
             try {
                 PhotoProcessor processor = new PhotoProcessor(
                         originalPhoto, maxDisplayPhotoDim, maxThumbnailPhotoDim);
diff --git a/src/com/android/providers/contacts/PhotoProcessor.java b/src/com/android/providers/contacts/PhotoProcessor.java
index d2a33d2..29b3c9d 100644
--- a/src/com/android/providers/contacts/PhotoProcessor.java
+++ b/src/com/android/providers/contacts/PhotoProcessor.java
@@ -15,9 +15,12 @@
  */
 package com.android.providers.contacts;
 
+import com.android.providers.contacts.util.MemoryUtils;
+
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Matrix;
+import android.os.SystemProperties;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -28,6 +31,38 @@
  */
 /* package-protected */ final class PhotoProcessor {
 
+    /**
+     * The default sizes of a thumbnail/display picture. This is used in {@link #initialize()}
+     */
+    private interface PhotoSizes {
+        /** Size of a thumbnail */
+        public static final int DEFAULT_THUMBNAIL = 96;
+
+        /**
+         * Size of a display photo on memory constrained devices (those are devices with less than
+         * {@link #DEFAULT_LARGE_RAM_THRESHOLD} of reported RAM
+         */
+        public static final int DEFAULT_DISPLAY_PHOTO_MEMORY_CONSTRAINED = 480;
+
+        /**
+         * Size of a display photo on devices with enough ram (those are devices with at least
+         * {@link #DEFAULT_LARGE_RAM_THRESHOLD} of reported RAM
+         */
+        public static final int DEFAULT_DISPLAY_PHOTO_LARGE_MEMORY = 720;
+
+        /**
+         * If the device has less than this amount of RAM, it is considered RAM constrained for
+         * photos
+         */
+        public static final int LARGE_RAM_THRESHOLD = 640 * 1024 * 1024;
+
+        /** If present, overrides the size given in {@link #DEFAULT_THUMBNAIL} */
+        public static final String SYS_PROPERTY_THUMBNAIL_SIZE = "contacts.thumbnail_size";
+
+        /** If present, overrides the size determined for the display photo */
+        public static final String SYS_PROPERTY_DISPLAY_PHOTO_SIZE = "contacts.display_photo_size";
+    }
+
     private final int mMaxDisplayPhotoDim;
     private final int mMaxThumbnailPhotoDim;
     private final boolean mForceCropToSquare;
@@ -139,7 +174,7 @@
             Matrix matrix = new Matrix();
             if (scaleFactor < 1.0f) matrix.setScale(scaleFactor, scaleFactor);
             scaledBitmap = Bitmap.createBitmap(
-                    mOriginal, cropLeft, cropTop, width, height, matrix, false);
+                    mOriginal, cropLeft, cropTop, width, height, matrix, true);
         }
         return scaledBitmap;
     }
@@ -147,15 +182,17 @@
     /**
      * Helper method to compress the given bitmap as a JPEG and return the resulting byte array.
      */
-    private byte[] getCompressedBytes(Bitmap b) throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        boolean compressed = b.compress(Bitmap.CompressFormat.JPEG, 95, baos);
+    private byte[] getCompressedBytes(Bitmap b, int quality) throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final boolean compressed = b.compress(Bitmap.CompressFormat.JPEG, quality, baos);
+        baos.flush();
+        baos.close();
+        byte[] result = baos.toByteArray();
+
         if (!compressed) {
             throw new IOException("Unable to compress image");
         }
-        baos.flush();
-        baos.close();
-        return baos.toByteArray();
+        return result;
     }
 
     /**
@@ -176,14 +213,19 @@
      * Retrieves the compressed display photo as a byte array.
      */
     public byte[] getDisplayPhotoBytes() throws IOException {
-        return getCompressedBytes(mDisplayPhoto);
+        return getCompressedBytes(mDisplayPhoto, 75);
     }
 
     /**
      * Retrieves the compressed thumbnail photo as a byte array.
      */
     public byte[] getThumbnailPhotoBytes() throws IOException {
-        return getCompressedBytes(mThumbnailPhoto);
+        // If there is a higher-resolution picture, we can assume we won't need to upscale the
+        // thumbnail often, so we can compress stronger
+        final boolean hasDisplayPhoto = mDisplayPhoto != null &&
+                (mDisplayPhoto.getWidth() > mThumbnailPhoto.getWidth() ||
+                mDisplayPhoto.getHeight() > mThumbnailPhoto.getHeight());
+        return getCompressedBytes(mThumbnailPhoto, hasDisplayPhoto ? 90 : 95);
     }
 
     /**
@@ -199,4 +241,30 @@
     public int getMaxThumbnailPhotoDim() {
         return mMaxThumbnailPhotoDim;
     }
+
+    /**
+     * Returns the maximum size in pixel of a thumbnail (which has a default that can be overriden
+     * using a system-property)
+     */
+    public static int getMaxThumbnailSize() {
+        final int sysPropThumbSize =
+                SystemProperties.getInt(PhotoSizes.SYS_PROPERTY_THUMBNAIL_SIZE, -1);
+        return sysPropThumbSize == -1 ? PhotoSizes.DEFAULT_THUMBNAIL : sysPropThumbSize;
+    }
+
+    /**
+     * Returns the maximum size in pixel of a display photo (which is determined based
+     * on available RAM or configured using a system-property)
+     */
+    public static int getMaxDisplayPhotoSize() {
+        final int sysPropDisplaySize =
+                SystemProperties.getInt(PhotoSizes.SYS_PROPERTY_DISPLAY_PHOTO_SIZE, -1);
+        if (sysPropDisplaySize != -1) return sysPropDisplaySize;
+
+        final boolean isExpensiveDevice =
+                MemoryUtils.getTotalMemorySize() >= PhotoSizes.LARGE_RAM_THRESHOLD;
+        return isExpensiveDevice
+                ? PhotoSizes.DEFAULT_DISPLAY_PHOTO_LARGE_MEMORY
+                : PhotoSizes.DEFAULT_DISPLAY_PHOTO_MEMORY_CONSTRAINED;
+    }
 }
diff --git a/src/com/android/providers/contacts/PhotoStore.java b/src/com/android/providers/contacts/PhotoStore.java
index 5f4b11c..e0b5fb4 100644
--- a/src/com/android/providers/contacts/PhotoStore.java
+++ b/src/com/android/providers/contacts/PhotoStore.java
@@ -194,7 +194,7 @@
                 byte[] photoBytes = photoProcessor.getDisplayPhotoBytes();
                 file = File.createTempFile("img", null, mStorePath);
                 FileOutputStream fos = new FileOutputStream(file);
-                fos.write(photoProcessor.getDisplayPhotoBytes());
+                fos.write(photoBytes);
                 fos.close();
 
                 // Create the DB entry.
diff --git a/src/com/android/providers/contacts/util/MemoryUtils.java b/src/com/android/providers/contacts/util/MemoryUtils.java
new file mode 100644
index 0000000..2ad9c0b
--- /dev/null
+++ b/src/com/android/providers/contacts/util/MemoryUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.providers.contacts.util;
+
+import com.android.internal.util.MemInfoReader;
+
+public final class MemoryUtils {
+    private MemoryUtils() {
+    }
+
+    private static long sTotalMemorySize = -1;
+
+    /**
+     * Returns the amount of RAM available to the Linux kernel, i.e. whatever is left after all the
+     * other chips stake their claim, including GPUs, DSPs, cell radios, and any other greedy chips
+     * (in other words: this is probably less than the "RAM: 1 GB" that was printed on the
+     * box in far too big letters)
+     */
+    public static long getTotalMemorySize() {
+        if (sTotalMemorySize < 0) {
+            MemInfoReader reader = new MemInfoReader();
+            reader.readMemInfo();
+
+            // getTotalSize() returns the "MemTotal" value from /proc/meminfo.
+            sTotalMemorySize = reader.getTotalSize();
+        }
+        return sTotalMemorySize;
+    }
+}
diff --git a/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
index 459fec0..3b7bfc4 100644
--- a/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
+++ b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java
@@ -44,10 +44,9 @@
         Map<PhotoSize, byte[]> photoMap = Maps.newHashMap();
         public PhotoEntry(byte[] original) {
             try {
-                Resources resources = getContext().getResources();
                 PhotoProcessor processor = new PhotoProcessor(original,
-                        resources.getInteger(R.integer.config_max_display_photo_dim),
-                        resources.getInteger(R.integer.config_max_thumbnail_photo_dim));
+                        PhotoProcessor.getMaxDisplayPhotoSize(),
+                        PhotoProcessor.getMaxThumbnailSize());
                 photoMap.put(PhotoSize.ORIGINAL, original);
                 photoMap.put(PhotoSize.DISPLAY_PHOTO, processor.getDisplayPhotoBytes());
                 photoMap.put(PhotoSize.THUMBNAIL, processor.getThumbnailPhotoBytes());