diff options
author | Ta-wei Yen <twyen@google.com> | 2016-03-08 00:19:23 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2016-03-08 00:19:23 +0000 |
commit | e70be0ca00a5ed4699f1a91759de2905e8bcefdc (patch) | |
tree | ab12861931359addfe933385a7d6873e17e6ffd2 | |
parent | 8fa2c9a9611511942a6c94c9590bde659dbb7ff8 (diff) | |
parent | 5d635baea2d0510c5edc9df26681d802bc91c443 (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.gradle | 5 | ||||
-rw-r--r-- | src/com/android/dialer/DialtactsActivity.java | 9 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/CallLogNotificationsHelper.java | 24 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/MissedCallNotifier.java | 14 | ||||
-rw-r--r-- | src/com/android/dialer/contactinfo/ContactPhotoLoader.java | 120 | ||||
-rw-r--r-- | src/com/android/dialer/util/Assert.java | 36 | ||||
-rw-r--r-- | tests/src/com/android/dialer/contactinfo/ContactPhotoLoaderTest.java | 106 |
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 |