summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTa-wei Yen <twyen@google.com>2016-03-08 00:19:23 +0000
committerandroid-build-merger <android-build-merger@google.com>2016-03-08 00:19:23 +0000
commite70be0ca00a5ed4699f1a91759de2905e8bcefdc (patch)
treeab12861931359addfe933385a7d6873e17e6ffd2
parent8fa2c9a9611511942a6c94c9590bde659dbb7ff8 (diff)
parent5d635baea2d0510c5edc9df26681d802bc91c443 (diff)
Merge "Add contact photo for missed call notifications" into nyc-dev
am: 5d635baea2 * commit '5d635baea2d0510c5edc9df26681d802bc91c443': Add contact photo for missed call notifications
-rw-r--r--build-app.gradle5
-rw-r--r--src/com/android/dialer/DialtactsActivity.java9
-rw-r--r--src/com/android/dialer/calllog/CallLogNotificationsHelper.java24
-rw-r--r--src/com/android/dialer/calllog/MissedCallNotifier.java14
-rw-r--r--src/com/android/dialer/contactinfo/ContactPhotoLoader.java120
-rw-r--r--src/com/android/dialer/util/Assert.java36
-rw-r--r--tests/src/com/android/dialer/contactinfo/ContactPhotoLoaderTest.java106
7 files changed, 288 insertions, 26 deletions
diff --git a/build-app.gradle b/build-app.gradle
index 9e24136ae..2ea437619 100644
--- a/build-app.gradle
+++ b/build-app.gradle
@@ -12,6 +12,11 @@ android {
manifest.srcFile 'AndroidManifest.xml'
res.srcDirs = ['res']
}
+
+ sourceSets.androidTest {
+ java.srcDirs = ['tests/src']
+ res.srcDirs = ['test/res']
+ }
}
dependencies {
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 58a0474d1..d063fef5a 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -16,9 +16,6 @@
package com.android.dialer;
-import com.android.dialer.voicemail.VoicemailArchiveActivity;
-import com.google.common.annotations.VisibleForTesting;
-
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.ActivityNotFoundException;
@@ -65,7 +62,6 @@ import com.android.contacts.common.interactions.TouchPointManager;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.widget.FloatingActionButtonController;
-import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.calllog.CallLogActivity;
import com.android.dialer.calllog.CallLogFragment;
import com.android.dialer.database.DialerDatabaseHelper;
@@ -85,18 +81,19 @@ import com.android.dialer.list.SpeedDialFragment;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
import com.android.dialer.settings.DialerSettingsActivity;
+import com.android.dialer.util.Assert;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.IntentUtil.CallIntentBuilder;
import com.android.dialer.util.TelecomUtil;
+import com.android.dialer.voicemail.VoicemailArchiveActivity;
import com.android.dialer.widget.ActionBarController;
import com.android.dialer.widget.SearchEditTextLayout;
import com.android.dialerbind.DatabaseHelperManager;
import com.android.dialerbind.ObjectFactory;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.animation.AnimationListenerAdapter;
-
-import junit.framework.Assert;
+import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
index 6abf241b4..189263199 100644
--- a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
+++ b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java
@@ -16,9 +16,7 @@
package com.android.dialer.calllog;
-import static android.Manifest.permission.READ_CALL_LOG;
-import static android.Manifest.permission.READ_CONTACTS;
-
+import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
@@ -111,7 +109,7 @@ public class CallLogNotificationsHelper {
/**
* Given a number and number information (presentation and country ISO), get
* {@link ContactInfo}. If the name is empty but we have a special presentation, display that.
- * Otherwise attempt to look it up in the database or the cache.
+ * Otherwise attempt to look it up in the cache.
* If that fails, fall back to displaying the number.
*/
public @NonNull ContactInfo getContactInfo(@Nullable String number, int numberPresentation,
@@ -136,13 +134,7 @@ public class CallLogNotificationsHelper {
return contactInfo;
}
- // 2. Personal ContactsProvider phonelookup query.
- contactInfo.name = mNameLookupQuery.query(number);
- if (!TextUtils.isEmpty(contactInfo.name)) {
- return contactInfo;
- }
-
- // 3. Look it up in the cache.
+ // 2. Look it up in the cache.
ContactInfo cachedContactInfo = mContactInfoHelper.lookupNumber(number, countryIso);
if (cachedContactInfo != null && !TextUtils.isEmpty(cachedContactInfo.name)) {
@@ -150,13 +142,13 @@ public class CallLogNotificationsHelper {
}
if (!TextUtils.isEmpty(contactInfo.formattedNumber)) {
- // 4. If we cannot lookup the contact, use the formatted number instead.
+ // 3. If we cannot lookup the contact, use the formatted number instead.
contactInfo.name = contactInfo.formattedNumber;
} else if (!TextUtils.isEmpty(number)) {
- // 5. If number can't be formatted, use number.
+ // 4. If number can't be formatted, use number.
contactInfo.name = number;
} else {
- // 6. Otherwise, it's unknown number.
+ // 5. Otherwise, it's unknown number.
contactInfo.name = mContext.getResources().getString(R.string.unknown);
}
return contactInfo;
@@ -259,7 +251,7 @@ public class CallLogNotificationsHelper {
@Override
@Nullable
public List<NewCall> query(int type) {
- if (!PermissionsUtil.hasPermission(mContext, READ_CALL_LOG)) {
+ if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) {
Log.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup.");
return null;
}
@@ -338,7 +330,7 @@ public class CallLogNotificationsHelper {
@Override
@Nullable
public String query(@Nullable String number) {
- if (!PermissionsUtil.hasPermission(mContext, READ_CONTACTS)) {
+ if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CONTACTS)) {
Log.w(TAG, "No READ_CONTACTS permission, returning null for name lookup.");
return null;
}
diff --git a/src/com/android/dialer/calllog/MissedCallNotifier.java b/src/com/android/dialer/calllog/MissedCallNotifier.java
index a9dfd442f..c422dd58d 100644
--- a/src/com/android/dialer/calllog/MissedCallNotifier.java
+++ b/src/com/android/dialer/calllog/MissedCallNotifier.java
@@ -21,6 +21,7 @@ import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.provider.CallLog.Calls;
import android.text.TextUtils;
@@ -28,13 +29,14 @@ import android.util.Log;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.util.PhoneNumberHelper;
-import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
import com.android.dialer.DialtactsActivity;
+import com.android.dialer.R;
+import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
+import com.android.dialer.contactinfo.ContactPhotoLoader;
import com.android.dialer.list.ListsFragment;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.IntentUtil.CallIntentBuilder;
-import com.android.dialer.R;
import java.util.List;
@@ -94,6 +96,7 @@ public class MissedCallNotifier {
NewCall newestCall = useCallLog ? newCalls.get(0) : null;
long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis();
+ Notification.Builder builder = new Notification.Builder(mContext);
// Display the first line of the notification:
// 1 missed call: <caller name || handle>
// More than 1 missed call: <number of calls> + "missed calls"
@@ -110,6 +113,11 @@ public class MissedCallNotifier {
: R.string.notification_missedCallTitle;
expandedText = contactInfo.name;
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo);
+ Bitmap photoIcon = loader.loadPhotoIcon();
+ if (photoIcon != null) {
+ builder.setLargeIcon(photoIcon);
+ }
} else {
titleResId = R.string.notification_missedCallsTitle;
expandedText =
@@ -132,7 +140,6 @@ public class MissedCallNotifier {
.setDeleteIntent(createClearMissedCallsPendingIntent());
// Create the notification suitable for display when sensitive information is showing.
- Notification.Builder builder = new Notification.Builder(mContext);
builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
.setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
.setContentTitle(mContext.getText(titleResId))
@@ -161,7 +168,6 @@ public class MissedCallNotifier {
createSendSmsFromNotificationPendingIntent(number));
}
}
- //TODO: add photo
}
Notification notification = builder.build();
diff --git a/src/com/android/dialer/contactinfo/ContactPhotoLoader.java b/src/com/android/dialer/contactinfo/ContactPhotoLoader.java
new file mode 100644
index 000000000..f36c438f6
--- /dev/null
+++ b/src/com/android/dialer/contactinfo/ContactPhotoLoader.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 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.dialer.contactinfo;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.provider.MediaStore;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+import android.util.Log;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
+import com.android.dialer.R;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.calllog.ContactInfoHelper;
+import com.android.dialer.util.Assert;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+import java.io.IOException;
+/**
+ * Class to create the appropriate contact icon from a ContactInfo.
+ * This class is for synchronous, blocking calls to generate bitmaps, while
+ * ContactCommons.ContactPhotoManager is to cache, manage and update a ImageView asynchronously.
+ */
+public class ContactPhotoLoader {
+
+ private static final String TAG = "ContactPhotoLoader";
+
+ private final Context mContext;
+ private final ContactInfo mContactInfo;
+
+ public ContactPhotoLoader(Context context, ContactInfo contactInfo) {
+ mContext = Preconditions.checkNotNull(context);
+ mContactInfo = Preconditions.checkNotNull(contactInfo);
+ }
+
+ /**
+ * Create a contact photo icon bitmap appropriate for the ContactInfo.
+ */
+ public Bitmap loadPhotoIcon() {
+ Assert.assertNotUiThread("ContactPhotoLoader#loadPhotoIcon called on UI thread");
+ int photoSize = mContext.getResources().getDimensionPixelSize(R.dimen.contact_photo_size);
+ return drawableToBitmap(getIcon(), photoSize, photoSize);
+ }
+
+ @VisibleForTesting
+ Drawable getIcon() {
+ Drawable drawable = createPhotoIconDrawable();
+ if (drawable == null) {
+ drawable = createLetterTileDrawable();
+ }
+ return drawable;
+ }
+
+ /**
+ * @return a {@link Drawable} of circular photo icon if the photo can be loaded, {@code null}
+ * otherwise.
+ */
+ @Nullable
+ private Drawable createPhotoIconDrawable() {
+ if (mContactInfo.photoUri == null) {
+ return null;
+ }
+ try {
+ Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(),
+ mContactInfo.photoUri);
+ final RoundedBitmapDrawable drawable =
+ RoundedBitmapDrawableFactory.create(mContext.getResources(), bitmap);
+ drawable.setAntiAlias(true);
+ drawable.setCornerRadius(bitmap.getHeight() / 2);
+ return drawable;
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * @return a {@link LetterTileDrawable} based on the ContactInfo.
+ */
+ private Drawable createLetterTileDrawable() {
+ LetterTileDrawable drawable = new LetterTileDrawable(mContext.getResources());
+ drawable.setIsCircular(true);
+ ContactInfoHelper helper =
+ new ContactInfoHelper(mContext, GeoUtil.getCurrentCountryIso(mContext));
+ if (helper.isBusiness(mContactInfo.sourceType)) {
+ drawable.setContactType(LetterTileDrawable.TYPE_BUSINESS);
+ }
+ drawable.setLetterAndColorFromContactDetails(mContactInfo.name, mContactInfo.lookupKey);
+ return drawable;
+ }
+
+ private static Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+}
diff --git a/src/com/android/dialer/util/Assert.java b/src/com/android/dialer/util/Assert.java
new file mode 100644
index 000000000..ec0a6ccb6
--- /dev/null
+++ b/src/com/android/dialer/util/Assert.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.dialer.util;
+
+import android.os.Looper;
+
+public class Assert {
+ public static void assertNotUiThread(String msg) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new AssertionError(msg);
+ }
+ }
+
+ public static void assertNotNull(Object object, String msg) {
+ if (object == null) {
+ throw new AssertionError(object);
+ }
+ }
+
+ public static void assertNotNull(Object object) {
+ assertNotNull(object, null);
+ }
+}
diff --git a/tests/src/com/android/dialer/contactinfo/ContactPhotoLoaderTest.java b/tests/src/com/android/dialer/contactinfo/ContactPhotoLoaderTest.java
new file mode 100644
index 000000000..42a5ae966
--- /dev/null
+++ b/tests/src/com/android/dialer/contactinfo/ContactPhotoLoaderTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 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.dialer.contactinfo;
+
+import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.test.AndroidTestCase;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
+import com.android.dialer.calllog.ContactInfo;
+import com.android.dialer.calllog.ContactInfoHelper;
+import com.android.dialer.tests.R;
+
+public class ContactPhotoLoaderTest extends InstrumentationTestCase {
+
+ private Context mContext;
+
+ @Override
+ public void setUp() {
+ mContext = getInstrumentation().getTargetContext();
+ }
+
+ public void testConstructor() {
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, new ContactInfo());
+ }
+
+ public void testConstructor_NullContext() {
+ try {
+ ContactPhotoLoader loader = new ContactPhotoLoader(null, new ContactInfo());
+ fail();
+ } catch (NullPointerException e) {
+ //expected
+ }
+ }
+
+ public void testConstructor_NullContactInfo() {
+ try {
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, null);
+ fail();
+ } catch (NullPointerException e) {
+ //expected
+ }
+ }
+
+ public void testGetIcon_Photo() {
+ ContactInfo info = getTestContactInfo();
+ info.photoUri = getResourceUri(R.drawable.phone_icon);
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, info);
+ assertTrue(loader.getIcon() instanceof RoundedBitmapDrawable);
+ }
+
+ public void testGetIcon_Photo_Invalid() {
+ ContactInfo info = getTestContactInfo();
+ info.photoUri = Uri.parse("file://invalid/uri");
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, info);
+ //Should fall back to LetterTileDrawable
+ assertTrue(loader.getIcon() instanceof LetterTileDrawable);
+ }
+
+ public void testGetIcon_LetterTile() {
+ ContactInfo info = getTestContactInfo();
+ ContactPhotoLoader loader = new ContactPhotoLoader(mContext, info);
+ assertTrue(loader.getIcon() instanceof LetterTileDrawable);
+ }
+
+ private Uri getResourceUri(int resId) {
+ Context testContext = getInstrumentation().getContext();
+ Resources resources = testContext.getResources();
+
+ assertNotNull(resources.getDrawable(resId));
+ return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ + testContext.getPackageName()
+ + '/' + resId);
+ }
+
+ private ContactInfo getTestContactInfo() {
+ ContactInfo info = new ContactInfo();
+ info.name = "foo";
+ info.lookupKey = "bar";
+ return info;
+ }
+} \ No newline at end of file