From bcb15f99dff4e01343bb7470135f5392a65a01f4 Mon Sep 17 00:00:00 2001 From: Ta-wei Yen Date: Mon, 7 Mar 2016 15:29:13 -0800 Subject: Add contact photo for missed call notifications + ContactPhotoLoader to create the appropriate icon from a ContactInfo - NameLookupQuery in CallLogNotificationsHelper#getContactInfo To show a photo the name is not enough. Full query need to be made to retrieve the photoUri. + class Assert in util + Gradle directory setup for dialer tests (Note: this is just for project setup in Android Studio, tests are still not runnable in gradle) Bug:27276108 Change-Id: I0ed2147f2bb60454fe5a5ad6c25fe99727441880 --- src/com/android/dialer/DialtactsActivity.java | 9 +- .../dialer/calllog/CallLogNotificationsHelper.java | 24 ++--- .../android/dialer/calllog/MissedCallNotifier.java | 14 ++- .../dialer/contactinfo/ContactPhotoLoader.java | 120 +++++++++++++++++++++ src/com/android/dialer/util/Assert.java | 36 +++++++ 5 files changed, 177 insertions(+), 26 deletions(-) create mode 100644 src/com/android/dialer/contactinfo/ContactPhotoLoader.java create mode 100644 src/com/android/dialer/util/Assert.java (limited to 'src/com/android') diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java index e775b0ad1..025d3ebdd 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 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: // More than 1 missed call: + "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); + } +} -- cgit v1.2.3