Merge "Fix crash during boot."
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5478e7f..26a8040 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -32,7 +32,7 @@
     <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Te sun mai târziu."</string>
     <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Nu pot acum. Vorbim mai târziu?"</string>
     <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Răspunsuri rapide"</string>
-    <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Editaţi răspunsurile rapide"</string>
+    <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Editați răspunsurile rapide"</string>
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Răspuns rapid"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesajul a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
@@ -44,7 +44,7 @@
     <string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Dezactivați modul TTY pentru a iniția apeluri video."</string>
     <string name="no_vm_number" msgid="4164780423805688336">"Lipseşte numărul mesageriei vocale"</string>
     <string name="no_vm_number_msg" msgid="1300729501030053828">"Niciun număr de mesagerie vocală nu este stocat pe cardul SIM."</string>
-    <string name="add_vm_number_str" msgid="4676479471644687453">"Adăugaţi numărul"</string>
+    <string name="add_vm_number_str" msgid="4676479471644687453">"Adăugați numărul"</string>
     <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Schimbați aplicația Telefon prestabilită?"</string>
     <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Folosiți <xliff:g id="NEW_APP">%1$s</xliff:g> și nu <xliff:g id="CURRENT_APP">%2$s</xliff:g> ca aplicație de telefonie prestabilită?"</string>
     <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Folosiți <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație de telefonie prestabilită?"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index cdb80cc..8c7d2d4 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -36,7 +36,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回复"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"讯息已发送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
-    <string name="enable_account_preference_title" msgid="2021848090086481720">"通话帐户"</string>
+    <string name="enable_account_preference_title" msgid="2021848090086481720">"通话帐号"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="8504993498756056279">"设备机主仅允许拨打紧急呼救电话。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此应用没有电话权限,无法拨出电话。"</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"要拨打电话,请输入有效的电话号码。"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index e474d7e..af8cb52 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -42,4 +42,7 @@
     <!-- Component name for the notification handler. The presence of this value will disable
          MissedCallNotifierImpl's presentation of missed call/voice notifications [DO NOT TRANSLATE] -->
     <string name="notification_component" translatable="false"></string>
+
+    <!-- Flag indicating whether audio should be routed to speaker when docked -->
+    <bool name="use_speaker_when_docked">true</bool>
 </resources>
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 9deffbe..dab4545 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -767,13 +767,13 @@
             case CallState.NEW:
             case CallState.ABORTED:
             case CallState.DISCONNECTED:
-            case CallState.CONNECTING:
-            case CallState.SELECT_PHONE_ACCOUNT:
                 return CALL_STATE_IDLE;
 
             case CallState.ACTIVE:
                 return CALL_STATE_ACTIVE;
 
+            case CallState.CONNECTING:
+            case CallState.SELECT_PHONE_ACCOUNT:
             case CallState.DIALING:
                 // Yes, this is correctly returning ALERTING.
                 // "Dialing" for BT means that we have sent information to the service provider
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 0228a23..b2ee0bb 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -622,7 +622,8 @@
         return mRingbackRequested;
     }
 
-    boolean isConference() {
+    @VisibleForTesting
+    public boolean isConference() {
         return mIsConference;
     }
 
@@ -758,7 +759,8 @@
 
     }
 
-    PhoneAccountHandle getTargetPhoneAccount() {
+    @VisibleForTesting
+    public PhoneAccountHandle getTargetPhoneAccount() {
         return mTargetPhoneAccountHandle;
     }
 
@@ -781,7 +783,8 @@
      *     period since this call was added to the set pending outgoing calls, see
      *     mCreationTimeMillis.
      */
-    long getAgeMillis() {
+    @VisibleForTesting
+    public long getAgeMillis() {
         if (mState == CallState.DISCONNECTED &&
                 (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
                  mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 77c82b7..8a0adfb 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -27,6 +27,7 @@
 import android.telephony.PhoneNumberUtils;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallerInfo;
 
 /**
@@ -34,7 +35,8 @@
  * caller details to the call log. All logging activity will be performed asynchronously in a
  * background thread to avoid blocking on the main thread.
  */
-final class CallLogManager extends CallsManagerListenerBase {
+@VisibleForTesting
+public final class CallLogManager extends CallsManagerListenerBase {
     /**
      * Parameter object to hold the arguments to add a call in the call log DB.
      */
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f223590..b42ca8e 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -261,8 +261,17 @@
     @Override
     public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
         Log.d(this, "onSuccessfulIncomingCall");
-        setCallState(incomingCall, CallState.RINGING,
-                shouldSendToVoicemail ? "directing to voicemail" : "successful incoming call");
+
+        // Only set the incoming call as ringing if it isn't already disconnected.  It is possible
+        // that the connection service disconnected the call before it was even added to Telecom, in
+        // which case it makes no sense to set it back to a ringing state.
+        if (incomingCall.getState() != CallState.DISCONNECTED &&
+                incomingCall.getState() != CallState.DISCONNECTING) {
+            setCallState(incomingCall, CallState.RINGING,
+                    shouldSendToVoicemail ? "directing to voicemail" : "successful incoming call");
+        } else {
+            Log.i(this, "onSuccessfulIncomingCall: call already disconnected.");
+        }
 
         if (hasMaximumRingingCalls() || hasMaximumDialingCalls() || shouldSendToVoicemail) {
             incomingCall.reject(false, null);
@@ -737,7 +746,12 @@
         } else {
             Log.i(this, "%s Starting with speakerphone because car is docked.", call);
         }
-        call.setStartWithSpeakerphoneOn(speakerphoneOn || mDockManager.isDocked());
+
+        final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
+                R.bool.use_speaker_when_docked);
+
+        call.setStartWithSpeakerphoneOn(speakerphoneOn
+                || (useSpeakerWhenDocked && mDockManager.isDocked()));
 
         if (call.isEmergencyCall()) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 8038f62..594d093 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -417,21 +417,12 @@
             return true;
         }
 
-        // Special check for work profiles.
+        // Special check for work profiles, any user could have a profile.
         // Unlike in TelecomServiceImpl, we only care about *profiles* here. We want to make sure
         // that we don't resolve PhoneAccount across *users*, but resolving across *profiles* is
         // fine.
-        if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
-            List<UserInfo> profileUsers =
-                    mUserManager.getProfiles(mCurrentUserHandle.getIdentifier());
-            for (UserInfo profileInfo : profileUsers) {
-                if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
+        return mUserManager.isSameProfileGroup(
+                mCurrentUserHandle.getIdentifier(), phoneAccountUserHandle.getIdentifier());
     }
 
     private List<ResolveInfo> resolveComponent(PhoneAccountHandle phoneAccountHandle) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index fc8977b..28ea05d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -320,7 +320,7 @@
                         Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
                         intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                                 account.getAccountHandle());
-                        Log.i(this, "Sending phone-account intent as user");
+                        Log.i(this, "Sending phone-account registered intent as user");
                         mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                                 PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
                     } finally {
@@ -341,6 +341,19 @@
                             accountHandle.getComponentName().getPackageName());
                     enforceUserHandleMatchesCaller(accountHandle);
                     mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
+
+                    // Broadcast an intent indicating the phone account which was unregistered.
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        Intent intent =
+                                new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED);
+                        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
+                        Log.i(this, "Sending phone-account unregistered intent as user");
+                        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                                PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 } catch (Exception e) {
                     Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
                     throw e;
@@ -530,7 +543,12 @@
             }
 
             synchronized (mLock) {
-                return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+                // Note: We are explicitly checking the calls telecom is tracking rather than
+                // relying on mCallsManager#getCallState(). Since getCallState() relies on the
+                // current state as tracked by PhoneStateBroadcaster, any failure to properly
+                // track the current call state there could result in the wrong ringing state being
+                // reported by this API.
+                return mCallsManager.hasRingingCall();
             }
         }
 
@@ -970,14 +988,9 @@
             return true;
         }
 
-        List<UserHandle> profileUserHandles;
-        if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
-            profileUserHandles = mUserManager.getUserProfiles();
-        } else {
-            // Otherwise, it has to be owned by the current caller's profile.
-            profileUserHandles = new ArrayList<>(1);
-            profileUserHandles.add(Binder.getCallingUserHandle());
-        }
+        // Any user can have profiles now.  Also result from getUserProfiles() includes the calling
+        // user itself.
+        List<UserHandle> profileUserHandles = mUserManager.getUserProfiles();
 
         return profileUserHandles.contains(phoneAccountUserHandle);
     }
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index 5f43ee9..69adaf9 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -23,6 +23,8 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Utilities to deal with the system telephony services. The system telephony services are treated
  * differently from 3rd party services in some situations (emergency calls, audio focus, etc...).
@@ -45,7 +47,8 @@
      * account are not expected to be displayed in the UI, so the description, etc are not
      * populated.
      */
-    static PhoneAccount getDefaultEmergencyPhoneAccount() {
+    @VisibleForTesting
+    public static PhoneAccount getDefaultEmergencyPhoneAccount() {
         return PhoneAccount.builder(DEFAULT_EMERGENCY_PHONE_ACCOUNT_HANDLE, "E")
                 .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
                         PhoneAccount.CAPABILITY_CALL_PROVIDER |
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 8f451b5..f7e2191 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -184,7 +184,7 @@
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.setClass(mContext, PrimaryCallReceiver.class);
         Log.d(this, "Sending broadcast as user to CallReceiver");
-        mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
         return true;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
new file mode 100644
index 0000000..a1e58ee
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.tests;
+
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.CallLog;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallLogManager;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.TelephonyUtil;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+
+public class CallLogManagerTest extends TelecomTestCase {
+
+    private CallLogManager mCallLogManager;
+    private IContentProvider mContentProvider;
+    private PhoneAccountHandle mDefaultAccountHandle;
+
+    private static final Uri TEL_PHONEHANDLE = Uri.parse("tel:5555551234");
+
+    private static final PhoneAccountHandle EMERGENCY_ACCT_HANDLE = TelephonyUtil
+            .getDefaultEmergencyPhoneAccount()
+            .getAccountHandle();
+
+    private static final int NO_VIDEO_STATE = VideoProfile.STATE_AUDIO_ONLY;
+    private static final int BIDIRECTIONAL_VIDEO_STATE = VideoProfile.STATE_BIDIRECTIONAL;
+    private static final String POST_DIAL_STRING = ";12345";
+    private static final String TEST_PHONE_ACCOUNT_ID= "testPhoneAccountId";
+
+    private static final int TEST_TIMEOUT_MILLIS = 100;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        mCallLogManager = new CallLogManager(mContext);
+        mContentProvider = mContext.getContentResolver().acquireProvider("test");
+        mDefaultAccountHandle = new PhoneAccountHandle(
+                new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
+                TEST_PHONE_ACCOUNT_ID
+        );
+
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        UserInfo userInfo = new UserInfo(UserHandle.USER_CURRENT, "test", 0);
+        when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
+        when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class)))
+                .thenReturn(false);
+        when(userManager.getUsers(any(Boolean.class)))
+                .thenReturn(Collections.singletonList(userInfo));
+    }
+
+    public void testDontLogCancelledCall() {
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.CANCELED,
+                false, // isConference
+                false, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.DIALING, CallState.DISCONNECTED);
+        verifyNoInsertion();
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.DIALING, CallState.ABORTED);
+        verifyNoInsertion();
+    }
+
+    public void testDontLogChoosingAccountCall() {
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                false, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.SELECT_PHONE_ACCOUNT,
+                CallState.DISCONNECTED);
+        verifyNoInsertion();
+    }
+
+    public void testDontLogCallsFromEmergencyAccount() {
+        mComponentContextFixture.putBooleanResource(R.bool.allow_emergency_numbers_in_call_log,
+                false);
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                false, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                EMERGENCY_ACCT_HANDLE, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+        verifyNoInsertion();
+    }
+
+    public void testLogCallDirectionOutgoing() {
+        Call fakeOutgoingCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                false, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+                Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+    }
+
+    public void testLogCallDirectionIncoming() {
+        Call fakeIncomingCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeIncomingCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+                Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
+    }
+
+    public void testLogCallDirectionMissed() {
+        Call fakeMissedCall = makeFakeCall(
+                DisconnectCause.MISSED, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+
+        mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+                Integer.valueOf(CallLog.Calls.MISSED_TYPE));
+    }
+
+    public void testCreationTimeAndAge() {
+        long currentTime = System.currentTimeMillis();
+        long duration = 1000L;
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                false, // isIncoming
+                currentTime, // creationTimeMillis
+                duration, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsLong(CallLog.Calls.DATE),
+                Long.valueOf(currentTime));
+        assertEquals(insertedValues.getAsLong(CallLog.Calls.DURATION),
+                Long.valueOf(duration / 1000));
+    }
+
+    public void testLogPhoneAccountId() {
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsString(CallLog.Calls.PHONE_ACCOUNT_ID),
+                TEST_PHONE_ACCOUNT_ID);
+    }
+
+    public void testLogCorrectPhoneNumber() {
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertEquals(insertedValues.getAsString(CallLog.Calls.NUMBER),
+                TEL_PHONEHANDLE.getSchemeSpecificPart());
+        assertEquals(insertedValues.getAsString(CallLog.Calls.POST_DIAL_DIGITS), POST_DIAL_STRING);
+    }
+
+    public void testLogCallVideoFeatures() {
+        Call fakeVideoCall = makeFakeCall(
+                DisconnectCause.OTHER, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                BIDIRECTIONAL_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING // postDialDigits
+        );
+        mCallLogManager.onCallStateChanged(fakeVideoCall, CallState.ACTIVE, CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture();
+        assertTrue((insertedValues.getAsInteger(CallLog.Calls.FEATURES)
+                & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO);
+    }
+
+    private void verifyNoInsertion() {
+        try {
+            verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).never()).insert(any(String.class),
+                    any(Uri.class), any(ContentValues.class));
+        } catch (android.os.RemoteException e) {
+            fail("Remote exception occurred during test execution");
+        }
+    }
+
+    private ContentValues verifyInsertionWithCapture() {
+        ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+        try {
+            verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS)).insert(any(String.class),
+                    eq(CallLog.Calls.CONTENT_URI), captor.capture());
+        } catch (android.os.RemoteException e) {
+            fail("Remote exception occurred during test execution");
+        }
+
+        return captor.getValue();
+    }
+
+
+    private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming,
+            long creationTimeMillis, long ageMillis, Uri callHandle,
+            PhoneAccountHandle phoneAccountHandle, int callVideoState,
+            String postDialDigits) {
+        Call fakeCall = mock(Call.class);
+        when(fakeCall.getDisconnectCause()).thenReturn(
+                new DisconnectCause(disconnectCauseCode));
+        when(fakeCall.isConference()).thenReturn(isConference);
+        when(fakeCall.isIncoming()).thenReturn(isIncoming);
+        when(fakeCall.getCreationTimeMillis()).thenReturn(creationTimeMillis);
+        when(fakeCall.getAgeMillis()).thenReturn(ageMillis);
+        when(fakeCall.getOriginalHandle()).thenReturn(callHandle);
+        when(fakeCall.getTargetPhoneAccount()).thenReturn(phoneAccountHandle);
+        when(fakeCall.getVideoStateHistory()).thenReturn(callVideoState);
+        when(fakeCall.getPostDialDigits()).thenReturn(postDialDigits);
+
+        return fakeCall;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 3294923..5a932d9 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -187,7 +187,7 @@
                 @Override
                 protected IContentProvider acquireProvider(Context c, String name) {
                     Log.i(this, "acquireProvider %s", name);
-                    return mock(IContentProvider.class);
+                    return mContentProvider;
                 }
 
                 @Override
@@ -198,7 +198,7 @@
                 @Override
                 protected IContentProvider acquireUnstableProvider(Context c, String name) {
                     Log.i(this, "acquireUnstableProvider %s", name);
-                    return mock(IContentProvider.class);
+                    return mContentProvider;
                 }
 
                 @Override
@@ -354,6 +354,7 @@
     private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
     private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
     private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
+    private final IContentProvider mContentProvider = mock(IContentProvider.class);
     private final Configuration mResourceConfiguration = new Configuration();
 
     private TelecomManager mTelecomManager = null;
@@ -442,6 +443,10 @@
         });
     }
 
+    public void putBooleanResource(int id, boolean value) {
+        when(mResources.getBoolean(eq(id))).thenReturn(value);
+    }
+
     public void setTelecomManager(TelecomManager telecomManager) {
         mTelecomManager = telecomManager;
     }