Merge "Increase opacity of text on primary call info to 100%." into nyc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 06f5795..c39bd5b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -32,6 +32,7 @@
     <uses-permission android:name="android.permission.READ_PROFILE" />
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.INTERNET" />
@@ -68,8 +69,7 @@
         android:supportsRtl="true"
         android:backupAgent='com.android.dialer.DialerBackupAgent'
         android:usesCleartextTraffic="false"
-        android:forceDeviceEncrypted="true"
-        android:encryptionAware="true">
+        android:forceDeviceEncrypted="false">
 
         <meta-data android:name="com.google.android.backup.api_key"
             android:value="AEdPqrEAAAAIBXgtCEKQ6W0PXVnW-ZVia2KmlV2AxsTw3GjAeQ" />
@@ -85,6 +85,7 @@
             android:icon="@mipmap/ic_launcher_phone"
             android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
             android:resizeableActivity="true"
+            android:encryptionAware="true"
             >
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
@@ -280,10 +281,12 @@
 
         <service
             android:name=".calllog.CallLogNotificationsService"
+            android:encryptionAware="true"
             android:exported="false"
         />
 
-        <receiver android:name=".calllog.MissedCallNotificationReceiver">
+        <receiver android:name=".calllog.MissedCallNotificationReceiver"
+            android:encryptionAware="true">
             <intent-filter>
                 <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
             </intent-filter>
@@ -317,11 +320,6 @@
                   android:resizeableActivity="true">
         </activity>
 
-        <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
-        <receiver android:name="com.android.incallui.NotificationBroadcastReceiver"
-                  android:encryptionAware="true"
-                  android:exported="false" />
-
         <service android:name="com.android.incallui.InCallServiceImpl"
                  android:permission="android.permission.BIND_INCALL_SERVICE"
                  android:encryptionAware="true" >
@@ -333,6 +331,11 @@
             </intent-filter>
         </service>
 
+        <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
+        <receiver android:name="com.android.incallui.NotificationBroadcastReceiver"
+            android:encryptionAware="true"
+            android:exported="false" />
+
         <provider
             android:name=".database.FilteredNumberProvider"
             android:authorities="com.android.dialer.database.filterednumberprovider"
diff --git a/InCallUI/res/values-bs-rBA/strings.xml b/InCallUI/res/values-bs-rBA/strings.xml
index b64df21..21f4360 100644
--- a/InCallUI/res/values-bs-rBA/strings.xml
+++ b/InCallUI/res/values-bs-rBA/strings.xml
@@ -299,9 +299,7 @@
     <skip />
     <!-- no translation found for default_notification_description (4950807644546509965) -->
     <skip />
-    <!-- no translation found for ringtone_title (835582004693335905) -->
-    <!-- no translation found for ringtone_title (5379026328015343686) -->
-    <skip />
+    <string name="ringtone_title" msgid="835582004693335905">"Melodija zvona telefona"</string>
     <!-- no translation found for vibrate_on_ring_title (5019791043398986665) -->
     <skip />
     <!-- no translation found for preference_category_ringtone (6246687516643676729) -->
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index 946e0eb..173fe42 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -100,10 +100,7 @@
         mNotificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mDialerRingtoneManager = new DialerRingtoneManager(
-                new InCallTonePlayer(
-                        AudioModeProvider.getInstance(),
-                        new ToneGeneratorFactory(),
-                        new PausableExecutorImpl()),
+                new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
                 CallList.getInstance());
         mCurrentNotification = NOTIFICATION_NONE;
     }
diff --git a/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java b/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java
index d930a92..3a8b03d 100644
--- a/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java
+++ b/InCallUI/src/com/android/incallui/ringtone/InCallTonePlayer.java
@@ -21,20 +21,14 @@
 
 import android.media.AudioManager;
 import android.media.ToneGenerator;
-import android.provider.MediaStore.Audio;
 import android.support.annotation.Nullable;
-import android.telecom.CallAudioState;
 
-import com.android.contacts.common.testing.NeededForTesting;
-import com.android.incallui.AudioModeProvider;
 import com.android.incallui.Log;
 import com.android.incallui.async.PausableExecutor;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import javax.annotation.concurrent.NotThreadSafe;
-
 /**
  * Class responsible for playing in-call related tones in a background thread. This class only
  * allows one tone to be played at a time.
@@ -45,7 +39,6 @@
 
     public static final int VOLUME_RELATIVE_HIGH_PRIORITY = 80;
 
-    private final AudioModeProvider mAudioModeProvider;
     private final ToneGeneratorFactory mToneGeneratorFactory;
     private final PausableExecutor mExecutor;
     private @Nullable CountDownLatch mNumPlayingTones;
@@ -53,23 +46,19 @@
     /**
      * Creates a new InCallTonePlayer.
      *
-     * @param audioModeProvider the {@link AudioModeProvider} used to determine through which stream
-     * to play tones.
      * @param toneGeneratorFactory the {@link ToneGeneratorFactory} used to create
      * {@link ToneGenerator}s.
      * @param executor the {@link PausableExecutor} used to play tones in a background thread.
      * @throws NullPointerException if audioModeProvider, toneGeneratorFactory, or executor are
      * {@code null}.
      */
-    public InCallTonePlayer(AudioModeProvider audioModeProvider,
-            ToneGeneratorFactory toneGeneratorFactory, PausableExecutor executor) {
-        mAudioModeProvider = Preconditions.checkNotNull(audioModeProvider);
+    public InCallTonePlayer(ToneGeneratorFactory toneGeneratorFactory, PausableExecutor executor) {
         mToneGeneratorFactory = Preconditions.checkNotNull(toneGeneratorFactory);
         mExecutor = Preconditions.checkNotNull(executor);
     }
 
     /**
-     * @return {@code true} if a tone is currently playing, {@code false} otherwise
+     * @return {@code true} if a tone is currently playing, {@code false} otherwise.
      */
     public boolean isPlayingTone() {
         return mNumPlayingTones != null && mNumPlayingTones.getCount() > 0;
@@ -79,8 +68,8 @@
      * Plays the given tone in a background thread.
      *
      * @param tone the tone to play.
-     * @throws IllegalStateException if a tone is already playing
-     * @throws IllegalArgumentException if the tone is invalid
+     * @throws IllegalStateException if a tone is already playing.
+     * @throws IllegalArgumentException if the tone is invalid.
      */
     public void play(int tone) {
         if (isPlayingTone()) {
@@ -97,26 +86,24 @@
     }
 
     private ToneGeneratorInfo getToneGeneratorInfo(int tone) {
-        int stream = getPlaybackStream();
         switch (tone) {
             case TONE_CALL_WAITING:
+                /*
+                 * Call waiting tones play until they're stopped either by the user accepting or
+                 * declining the call so the tone length is set at what's effectively forever. The
+                 * tone is played at a high priority volume and through STREAM_VOICE_CALL since it's
+                 * call related and using that stream will route it through bluetooth devices
+                 * appropriately.
+                 */
                 return new ToneGeneratorInfo(ToneGenerator.TONE_SUP_CALL_WAITING,
                         VOLUME_RELATIVE_HIGH_PRIORITY,
                         Integer.MAX_VALUE,
-                        stream);
+                        AudioManager.STREAM_VOICE_CALL);
             default:
                 throw new IllegalArgumentException("Bad tone: " + tone);
         }
     }
 
-    private int getPlaybackStream() {
-        if (mAudioModeProvider.getAudioMode() == CallAudioState.ROUTE_BLUETOOTH) {
-            // TODO (maxwelb): b/26932998 play through bluetooth
-            // return AudioManager.STREAM_BLUETOOTH_SCO;
-        }
-        return AudioManager.STREAM_VOICE_CALL;
-    }
-
     private void playOnBackgroundThread(ToneGeneratorInfo info) {
         ToneGenerator toneGenerator = null;
         try {
diff --git a/InCallUI/tests/src/com/android/incallui/ringtone/InCallTonePlayerTest.java b/InCallUI/tests/src/com/android/incallui/ringtone/InCallTonePlayerTest.java
index 59611f7..bde5c50 100644
--- a/InCallUI/tests/src/com/android/incallui/ringtone/InCallTonePlayerTest.java
+++ b/InCallUI/tests/src/com/android/incallui/ringtone/InCallTonePlayerTest.java
@@ -21,7 +21,6 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.incallui.AudioModeProvider;
 import com.android.incallui.async.PausableExecutor;
 import com.android.incallui.async.SingleProdThreadExecutor;
 
@@ -32,7 +31,6 @@
 @SmallTest
 public class InCallTonePlayerTest extends AndroidTestCase {
 
-    @Mock private AudioModeProvider mAudioModeProvider;
     @Mock private ToneGeneratorFactory mToneGeneratorFactory;
     @Mock private ToneGenerator mToneGenerator;
     private InCallTonePlayer mInCallTonePlayer;
@@ -52,8 +50,7 @@
         Mockito.when(mToneGeneratorFactory.newInCallToneGenerator(Mockito.anyInt(),
                 Mockito.anyInt())).thenReturn(mToneGenerator);
         mExecutor = new SingleProdThreadExecutor();
-        mInCallTonePlayer = new InCallTonePlayer(mAudioModeProvider, mToneGeneratorFactory,
-                mExecutor);
+        mInCallTonePlayer = new InCallTonePlayer(mToneGeneratorFactory, mExecutor);
     }
 
     @Override
@@ -92,10 +89,6 @@
         } catch (IllegalStateException e) {}
     }
 
-    public void testPlay_BlueToothStream() {
-        // TODO (maxwelb): b/26932998 play through bluetooth
-    }
-
     public void testPlay_VoiceCallStream() throws InterruptedException {
         mInCallTonePlayer.play(InCallTonePlayer.TONE_CALL_WAITING);
         mExecutor.awaitMilestoneForTesting();
diff --git a/build-app.gradle b/build-app.gradle
index 9e24136..2ea4376 100644
--- a/build-app.gradle
+++ b/build-app.gradle
@@ -12,6 +12,11 @@
         manifest.srcFile 'AndroidManifest.xml'
         res.srcDirs = ['res']
     }
+
+    sourceSets.androidTest {
+        java.srcDirs = ['tests/src']
+        res.srcDirs = ['test/res']
+    }
 }
 
 dependencies {
diff --git a/res/drawable-hdpi/ic_share_white_24dp.png b/res/drawable-hdpi/ic_share_white_24dp.png
new file mode 100644
index 0000000..b09a692
--- /dev/null
+++ b/res/drawable-hdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_share_white_24dp.png b/res/drawable-mdpi/ic_share_white_24dp.png
new file mode 100644
index 0000000..e944fd7
--- /dev/null
+++ b/res/drawable-mdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_share_white_24dp.png b/res/drawable-xhdpi/ic_share_white_24dp.png
new file mode 100644
index 0000000..22a8783
--- /dev/null
+++ b/res/drawable-xhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_share_white_24dp.png b/res/drawable-xxhdpi/ic_share_white_24dp.png
new file mode 100644
index 0000000..a35b3cd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_share_white_24dp.png b/res/drawable-xxxhdpi/ic_share_white_24dp.png
new file mode 100644
index 0000000..e351c7b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_share_white_24dp.png
Binary files differ
diff --git a/res/layout/voicemail_playback_layout.xml b/res/layout/voicemail_playback_layout.xml
index 984a52b..178e888 100644
--- a/res/layout/voicemail_playback_layout.xml
+++ b/res/layout/voicemail_playback_layout.xml
@@ -102,6 +102,19 @@
             android:tint="@color/voicemail_icon_tint"
             android:contentDescription="@string/call_log_trash_voicemail" />
 
+        <Space android:id="@+id/space_before_share_voicemail"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="gone" />
+
+        <ImageButton android:id="@+id/share_voicemail"
+            style="@style/VoicemailPlaybackLayoutButtonStyle"
+            android:src="@drawable/ic_share_white_24dp"
+            android:tint="@color/voicemail_icon_tint"
+            android:contentDescription="@string/call_log_share_voicemail"
+            android:visibility="gone" />
+
         <Space android:id="@+id/space_before_archive_voicemail"
             android:layout_width="0dp"
             android:layout_height="match_parent"
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 405ce3b..bc05f12 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -138,9 +138,7 @@
     <skip />
     <!-- no translation found for action_menu_call_history_description (9018442816219748968) -->
     <skip />
-    <!-- no translation found for action_menu_overflow_description (2303272250613084574) -->
-    <!-- no translation found for action_menu_overflow_description (2295659037509008453) -->
-    <skip />
+    <string name="action_menu_overflow_description" msgid="2303272250613084574">"Više opcija"</string>
     <!-- no translation found for action_menu_dialpad_button (1425910318049008136) -->
     <skip />
     <!-- no translation found for menu_show_outgoing_only (1965570298133301970) -->
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index dc7a508..5516a24 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -222,7 +222,7 @@
     <string name="list_delimeter" msgid="4571593167738725100">"、 "</string>
     <string name="display_options_title" msgid="7812852361055667468">"显示选项"</string>
     <string name="sounds_and_vibration_title" msgid="1692290115642160845">"提示音和振动"</string>
-    <string name="accessibility_settings_title" msgid="6068141142874046249">"无障碍功能"</string>
+    <string name="accessibility_settings_title" msgid="6068141142874046249">"辅助功能"</string>
     <string name="ringtone_title" msgid="760362035635084653">"手机铃声"</string>
     <string name="vibrate_on_ring_title" msgid="3362916460327555241">"有来电时响铃并振动"</string>
     <string name="dtmf_tone_enable_title" msgid="6571449695997521615">"拨号键盘提示音"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 830af65..b8f8679 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -87,6 +87,9 @@
     <!-- Menu item used to archive a voicemail. [CHAR LIMIT=30] -->
     <string name="call_log_archive_voicemail">Archive voicemail</string>
 
+    <!-- Menu item used to send a voicemail through other applications [CHAR LIMIT=30] -->
+    <string name="call_log_share_voicemail">Share voicemail</string>
+
     <!-- Text for snackbar to undo a voicemail delete. [CHAR LIMIT=30] -->
     <string name="snackbar_voicemail_deleted">Voicemail deleted</string>
 
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 58a0474..d063fef 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.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.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 6abf241..1892631 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 @@
     /**
      * 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 @@
             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 @@
         }
 
         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 @@
         @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 @@
         @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 a9dfd44..98d02d0 100644
--- a/src/com/android/dialer/calllog/MissedCallNotifier.java
+++ b/src/com/android/dialer/calllog/MissedCallNotifier.java
@@ -21,6 +21,7 @@
 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,15 @@
 
 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.compat.UserManagerCompat;
 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 +97,7 @@
         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 +114,11 @@
                     : 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 +141,6 @@
                 .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))
@@ -146,8 +154,8 @@
                 // sensitive notification information.
                 .setPublicVersion(publicBuilder.build());
 
-        // Add additional actions when there is only 1 missed call, like call-back and SMS.
-        if (count == 1) {
+        // Add additional actions when there is only 1 missed call and the user isn't locked
+        if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) {
             if (!TextUtils.isEmpty(number)
                     && !TextUtils.equals(
                     number, mContext.getString(R.string.handle_restricted))) {
@@ -161,7 +169,6 @@
                             createSendSmsFromNotificationPendingIntent(number));
                 }
             }
-            //TODO: add photo
         }
 
         Notification notification = builder.build();
@@ -175,21 +182,24 @@
         AsyncTask.execute(new Runnable() {
             @Override
             public void run() {
-                // Clear the list of new missed calls from the call log.
-                ContentValues values = new ContentValues();
-                values.put(Calls.NEW, 0);
-                values.put(Calls.IS_READ, 1);
-                StringBuilder where = new StringBuilder();
-                where.append(Calls.NEW);
-                where.append(" = 1 AND ");
-                where.append(Calls.TYPE);
-                where.append(" = ?");
-                try {
-                    mContext.getContentResolver().update(Calls.CONTENT_URI, values,
-                            where.toString(), new String[]{ Integer.toString(Calls.
-                            MISSED_TYPE) });
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG, "ContactsProvider update command failed", e);
+                // Call log is only accessible when unlocked. If that's the case, clear the list of
+                // new missed calls from the call log.
+                if (UserManagerCompat.isUserUnlocked(mContext)) {
+                    ContentValues values = new ContentValues();
+                    values.put(Calls.NEW, 0);
+                    values.put(Calls.IS_READ, 1);
+                    StringBuilder where = new StringBuilder();
+                    where.append(Calls.NEW);
+                    where.append(" = 1 AND ");
+                    where.append(Calls.TYPE);
+                    where.append(" = ?");
+                    try {
+                        mContext.getContentResolver().update(Calls.CONTENT_URI, values,
+                                where.toString(), new String[]{Integer.toString(Calls.
+                                        MISSED_TYPE)});
+                    } catch (IllegalArgumentException e) {
+                        Log.w(TAG, "ContactsProvider update command failed", e);
+                    }
                 }
                 getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
             }
diff --git a/src/com/android/dialer/contactinfo/ContactPhotoLoader.java b/src/com/android/dialer/contactinfo/ContactPhotoLoader.java
new file mode 100644
index 0000000..f36c438
--- /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 0000000..ec0a6cc
--- /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/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
index 0cbe7f2..8c869d1 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -275,7 +275,11 @@
     private ImageButton mPlaybackSpeakerphone;
     private ImageButton mDeleteButton;
     private ImageButton mArchiveButton;
+    private ImageButton mShareButton;
+
     private Space mArchiveSpace;
+    private Space mShareSpace;
+
     private TextView mStateText;
     private TextView mPositionText;
     private TextView mTotalDurationText;
@@ -304,6 +308,12 @@
             updateArchiveUI(mVoicemailUri);
             updateArchiveButton(mVoicemailUri);
         }
+
+        if (ObjectFactory.isVoicemailShareEnabled(mContext)) {
+            // Show share button and space before it
+            mShareSpace.setVisibility(View.VISIBLE);
+            mShareButton.setVisibility(View.VISIBLE);
+        }
     }
 
     @Override
@@ -315,7 +325,11 @@
         mPlaybackSpeakerphone = (ImageButton) findViewById(R.id.playback_speakerphone);
         mDeleteButton = (ImageButton) findViewById(R.id.delete_voicemail);
         mArchiveButton =(ImageButton) findViewById(R.id.archive_voicemail);
+        mShareButton = (ImageButton) findViewById(R.id.share_voicemail);
+
         mArchiveSpace = (Space) findViewById(R.id.space_before_archive_voicemail);
+        mShareSpace = (Space) findViewById(R.id.space_before_share_voicemail);
+
         mStateText = (TextView) findViewById(R.id.playback_state_text);
         mPositionText = (TextView) findViewById(R.id.playback_position_text);
         mTotalDurationText = (TextView) findViewById(R.id.total_duration_text);
diff --git a/src/com/android/dialerbind/ObjectFactory.java b/src/com/android/dialerbind/ObjectFactory.java
index 935c9f7..9f75e39 100644
--- a/src/com/android/dialerbind/ObjectFactory.java
+++ b/src/com/android/dialerbind/ObjectFactory.java
@@ -48,6 +48,10 @@
         return false;
     }
 
+    public static boolean isVoicemailShareEnabled(Context context) {
+        return false;
+    }
+
     @Nullable
     public static ExtendedBlockingButtonRenderer newExtendedBlockingButtonRenderer(
             Context context, ExtendedBlockingButtonRenderer.Listener listener) {
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 0000000..42a5ae9
--- /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