am d1920097: (-s ours) am a0018a3f: (-s ours) Import translations. DO NOT MERGE

* commit 'd19200974d25a9eb7dc369d019b94ed067114c0c':
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9569c1d..4055b44 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,7 +16,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.dialer"
-    coreApp="true">
+    coreApp="true"
+    android:versionCode="20210"
+    android:versionName="2.21">
 
     <uses-sdk
         android:minSdkVersion="23"
@@ -172,6 +174,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="com.android.contacts.common.dialog.CallSubjectDialog"
+                  android:theme="@style/Theme.CallSubjectDialogTheme"
+                  android:windowSoftInputMode="stateVisible|adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+            </intent-filter>
+        </activity>
+
         <!-- Backwards compatibility: "Phone" from Gingerbread and earlier -->
         <activity-alias android:name="DialtactsActivity"
             android:targetActivity=".DialtactsActivity"
diff --git a/res/layout/call_log_list_item_actions.xml b/res/layout/call_log_list_item_actions.xml
index b427206..16a712b 100644
--- a/res/layout/call_log_list_item_actions.xml
+++ b/res/layout/call_log_list_item_actions.xml
@@ -105,6 +105,20 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/call_with_note_action"
+        style="@style/CallLogActionStyle">
+
+        <ImageView
+            style="@style/CallLogActionIconStyle"
+            android:src="@drawable/ic_call_note_white_24dp" />
+
+        <TextView
+            style="@style/CallLogActionTextStyle"
+            android:text="@string/call_with_a_note" />
+
+    </LinearLayout>
+
+    <LinearLayout
         android:id="@+id/details_action"
         style="@style/CallLogActionStyle">
 
diff --git a/res/menu/dialpad_options.xml b/res/menu/dialpad_options.xml
index f0399a8..63fca07 100644
--- a/res/menu/dialpad_options.xml
+++ b/res/menu/dialpad_options.xml
@@ -23,5 +23,8 @@
         android:id="@+id/menu_add_wait"
         android:title="@string/add_wait"
         android:showAsAction="withText" />
-
+    <item
+        android:id="@+id/menu_call_with_note"
+        android:title="@string/call_with_a_note"
+        android:showAsAction="withText" />
 </menu>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index e2f0ff5..d8f4aa8 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -17,4 +17,5 @@
 <resources>
     <item type="id" name="context_menu_copy_to_clipboard" />
     <item type="id" name="context_menu_edit_before_call" />
+    <item type="id" name="settings_header_sounds_and_vibration" />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eec9b0c..5e2476d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -813,4 +813,7 @@
 
     <!-- Shown as a prompt to turn on the phone permission to allow a call to be placed -->
     <string name="permission_place_call">To place a call,\n turn on the Phone permission.</string>
+
+    <!-- Shown as a message that notifies the user that the Phone app cannot write to system settings, which is why the system settings app is being launched directly instead.-->
+    <string name="toast_cannot_write_system_settings">Phone app does not have permission to write to system settings.</string>
 </resources>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index c6ec8e1..56f2cb1 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -50,6 +50,7 @@
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.calllog.CallDetailHistoryAdapter;
 import com.android.dialer.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener;
 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
@@ -162,7 +163,7 @@
                     new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details));
 
             String lookupKey = contactUri == null ? null
-                    : ContactInfoHelper.getLookupKeyFromUri(contactUri);
+                    : UriUtils.getLookupKeyFromUri(contactUri);
 
             final boolean isBusiness = mContactInfoHelper.isBusiness(firstDetails.sourceType);
 
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 98f34b5..69cc146 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -608,7 +608,8 @@
     public void onClick(View view) {
         switch (view.getId()) {
             case R.id.floating_action_button:
-                if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
+                if (mListsFragment.getCurrentTabIndex()
+                        == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) {
                     DialerUtils.startActivityWithErrorToast(
                             this,
                             IntentUtil.getNewContactIntent(),
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 9609e4d..5a87bc8 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -22,6 +22,8 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.support.v7.widget.RecyclerView;
 import android.os.Bundle;
 import android.os.Trace;
@@ -65,8 +67,7 @@
  * Adapter class to fill in data for the Call Log.
  */
 public class CallLogAdapter extends GroupingListAdapter
-        implements ViewTreeObserver.OnPreDrawListener,
-                CallLogGroupBuilder.GroupCreator,
+        implements CallLogGroupBuilder.GroupCreator,
                 VoicemailPlaybackPresenter.OnVoicemailDeletedListener {
 
     /** Interface used to initiate a refresh of the content. */
@@ -96,7 +97,6 @@
     private final ContactInfoHelper mContactInfoHelper;
     private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
     private final CallFetcher mCallFetcher;
-    private ViewTreeObserver mViewTreeObserver = null;
 
     protected ContactInfoCache mContactInfoCache;
 
@@ -294,16 +294,6 @@
                 }
             };
 
-    @Override
-    public boolean onPreDraw() {
-        // We only wanted to listen for the first draw (and this is it).
-        unregisterPreDrawListener();
-        if (PermissionsUtil.hasContactsPermissions(mContext)) {
-            mContactInfoCache.start();
-        }
-        return true;
-    }
-
     public CallLogAdapter(
             Context context,
             CallFetcher callFetcher,
@@ -375,21 +365,14 @@
         }
     }
 
-    /**
-     * Stop receiving onPreDraw() notifications.
-     */
-    private void unregisterPreDrawListener() {
-        if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) {
-            mViewTreeObserver.removeOnPreDrawListener(this);
-        }
-        mViewTreeObserver = null;
-    }
-
     public void invalidateCache() {
         mContactInfoCache.invalidate();
+    }
 
-        // Restart the request-processing thread after the next draw.
-        unregisterPreDrawListener();
+    public void startCache() {
+        if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) {
+            mContactInfoCache.start();
+        }
     }
 
     public void pauseCache() {
@@ -545,13 +528,16 @@
         views.rowId = c.getLong(CallLogQuery.ID);
         // Store values used when the actions ViewStub is inflated on expansion.
         views.number = number;
+        views.displayNumber = details.displayNumber;
         views.numberPresentation = numberPresentation;
         views.callType = c.getInt(CallLogQuery.CALL_TYPE);
         views.accountHandle = accountHandle;
         views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
         // Stash away the Ids of the calls so that we can support deleting a row in the call log.
         views.callIds = getCallIds(c, count);
-
+        views.isBusiness = mContactInfoHelper.isBusiness(info.sourceType);
+        views.numberType = (String) Phone.getTypeLabel(mContext.getResources(), details.numberType,
+                details.numberLabel);
         // Default case: an item in the call log.
         views.primaryActionView.setVisibility(View.VISIBLE);
 
@@ -582,15 +568,9 @@
             nameForDefaultImage = info.name;
         }
         views.setPhoto(info.photoId, info.photoUri, info.lookupUri, nameForDefaultImage,
-                isVoicemailNumber, mContactInfoHelper.isBusiness(info.sourceType));
+                isVoicemailNumber, views.isBusiness);
 
         mCallLogListItemHelper.setPhoneCallDetails(views, details);
-
-        // Listen for the first draw
-        if (mViewTreeObserver == null) {
-            mViewTreeObserver = views.rootView.getViewTreeObserver();
-            mViewTreeObserver.addOnPreDrawListener(this);
-        }
     }
 
     @Override
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 59e2c7f..e7b7764 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -338,6 +338,7 @@
         }
         mHasReadCallLogPermission = hasReadCallLogPermission;
         refreshData();
+        mAdapter.startCache();
     }
 
     @Override
diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
index 361e1c7..0fa5e6d 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.calllog;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.Intent;
@@ -29,18 +30,16 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
-import android.view.ViewTreeObserver;
 import android.widget.QuickContactBadge;
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.dialog.CallSubjectDialog;
 import com.android.contacts.common.testing.NeededForTesting;
 import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.R;
-import com.android.dialer.calllog.CallLogAsyncTaskUtil;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.PhoneNumberUtil;
 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
@@ -80,6 +79,7 @@
     public View addToExistingContactButtonView;
     public View sendMessageView;
     public View detailsButtonView;
+    public View callWithNoteButtonView;
 
     /**
      * The row Id for the first call associated with the call log entry.  Used as a key for the
@@ -100,12 +100,22 @@
     public String number;
 
     /**
+     * The formatted phone number to display.
+     */
+    public String displayNumber;
+
+    /**
      * The phone number presentation for the current call log entry.  Cached here as the call back
      * intent is set only when the actions ViewStub is inflated.
      */
     public int numberPresentation;
 
     /**
+     * The type of the phone number (e.g. main, work, etc).
+     */
+    public String numberType;
+
+    /**
      * The type of call for the current call log entry.  Cached here as the call back
      * intent is set only when the actions ViewStub is inflated.
      */
@@ -131,6 +141,11 @@
     public CharSequence nameOrNumber;
 
     /**
+     * Whether this row is for a business or not.
+     */
+    public boolean isBusiness;
+
+    /**
      * The contact info for the contact displayed in this list item.
      */
     public ContactInfo info;
@@ -245,6 +260,9 @@
 
             detailsButtonView = actionsView.findViewById(R.id.details_action);
             detailsButtonView.setOnClickListener(this);
+
+            callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action);
+            callWithNoteButtonView.setOnClickListener(this);
         }
 
         bindActionButtons();
@@ -349,6 +367,13 @@
         sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number));
 
         mCallLogListItemHelper.setActionContentDescriptions(this);
+
+        boolean supportsCallSubject =
+                mTelecomCallLogCache.doesAccountSupportCallSubject(accountHandle);
+        boolean isVoicemailNumber =
+                mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
+        callWithNoteButtonView.setVisibility(
+                supportsCallSubject && !isVoicemailNumber ? View.VISIBLE : View.GONE);
     }
 
     /**
@@ -403,7 +428,7 @@
 
         String lookupKey = null;
         if (contactUri != null) {
-            lookupKey = ContactInfoHelper.getLookupKeyFromUri(contactUri);
+            lookupKey = UriUtils.getLookupKeyFromUri(contactUri);
         }
 
         DefaultImageRequest request = new DefaultImageRequest(
@@ -423,6 +448,19 @@
         if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) {
             mVoicemailPrimaryActionButtonClicked = true;
             mExpandCollapseListener.onClick(primaryActionView);
+        } else if (view.getId() == R.id.call_with_note_action) {
+            CallSubjectDialog.start(
+                    (Activity) mContext,
+                    info.photoId,
+                    info.photoUri,
+                    info.lookupUri,
+                    (String) nameOrNumber /* top line of contact view in call subject dialog */,
+                    isBusiness,
+                    number, /* callable number used for ACTION_CALL intent */
+                    TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact
+                                                                           view in dialog. */
+                    numberType, /* phone number type (e.g. mobile) in second line of contact view */
+                    accountHandle);
         } else {
             final IntentProvider intentProvider = (IntentProvider) view.getTag();
             if (intentProvider != null) {
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 9a660e1..2e07a03 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -298,6 +298,10 @@
      */
     public void updateCallLogContactInfo(String number, String countryIso, ContactInfo updatedInfo,
             ContactInfo callLogInfo) {
+        if (!PermissionsUtil.hasPermission(mContext, android.Manifest.permission.WRITE_CALL_LOG)) {
+            return;
+        }
+
         final ContentValues values = new ContentValues();
         boolean needsUpdate = false;
 
@@ -390,24 +394,6 @@
     }
 
     /**
-     * Parses the given URI to determine the original lookup key of the contact.
-     */
-    public static String getLookupKeyFromUri(Uri lookupUri) {
-        // Would be nice to be able to persist the lookup key somehow to avoid having to parse
-        // the uri entirely just to retrieve the lookup key, but every uri is already parsed
-        // once anyway to check if it is an encoded JSON uri, so this has negligible effect
-        // on performance.
-        if (lookupUri != null && !UriUtils.isEncodedContactUri(lookupUri)) {
-            final List<String> segments = lookupUri.getPathSegments();
-            // This returns the third path segment of the uri, where the lookup key is located.
-            // See {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
-            return (segments.size() < 3) ? null : Uri.encode(segments.get(2));
-        } else {
-            return null;
-        }
-    }
-
-    /**
      * Returns the contact information stored in an entry of the call log.
      *
      * @param c A cursor pointing to an entry in the call log.
diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java
index 7eaa523..143d13e 100644
--- a/src/com/android/dialer/calllog/PhoneAccountUtils.java
+++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java
@@ -84,6 +84,21 @@
     }
 
     /**
+     * Determine whether a phone account supports call subjects.
+     *
+     * @return {@code true} if call subjects are supported, {@code false} otherwise.
+     */
+    public static boolean getAccountSupportsCallSubject(Context context,
+            PhoneAccountHandle accountHandle) {
+        TelecomManager telecomManager =
+                (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+        final PhoneAccount account = telecomManager.getPhoneAccount(accountHandle);
+
+        return account == null ? false :
+                account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
+    }
+
+    /**
      * Retrieve the account metadata, but if the account does not exist or the device has only a
      * single registered and enabled account, return null.
      */
diff --git a/src/com/android/dialer/calllog/TelecomCallLogCache.java b/src/com/android/dialer/calllog/TelecomCallLogCache.java
index ec1d241..7071669 100644
--- a/src/com/android/dialer/calllog/TelecomCallLogCache.java
+++ b/src/com/android/dialer/calllog/TelecomCallLogCache.java
@@ -52,6 +52,7 @@
             new HashMap<>();
     private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new HashMap<>();
     private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new HashMap<>();
+    private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new HashMap<>();
 
     private boolean mHasCheckedForVideoEnabled;
     private boolean mIsVideoEnabled;
@@ -64,6 +65,7 @@
         mVoicemailQueryCache.clear();
         mPhoneAccountLabelCache.clear();
         mPhoneAccountColorCache.clear();
+        mPhoneAccountCallWithNoteCache.clear();
 
         mHasCheckedForVideoEnabled = false;
         mIsVideoEnabled = false;
@@ -121,4 +123,22 @@
         }
         return mIsVideoEnabled;
     }
+
+    /**
+     * Determines if the PhoneAccount supports specifying a call subject (i.e. calling with a note)
+     * for outgoing calls.
+     *
+     * @param accountHandle The PhoneAccount handle.
+     * @return {@code true} if calling with a note is supported, {@code false} otherwise.
+     */
+    public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
+        if (mPhoneAccountCallWithNoteCache.containsKey(accountHandle)) {
+            return mPhoneAccountCallWithNoteCache.get(accountHandle);
+        } else {
+            Boolean supportsCallWithNote =
+                    PhoneAccountUtils.getAccountSupportsCallSubject(mContext, accountHandle);
+            mPhoneAccountCallWithNoteCache.put(accountHandle, supportsCallWithNote);
+            return supportsCallWithNote;
+        }
+    }
 }
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index d35abd7..3792a1d 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -50,6 +50,7 @@
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -67,7 +68,9 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.dialog.CallSubjectDialog;
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.contacts.common.util.PhoneNumberFormatter;
 import com.android.contacts.common.util.StopWatch;
@@ -80,7 +83,6 @@
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.IntentUtil;
 import com.android.phone.common.CallLogAsync;
-import com.android.phone.common.HapticFeedback;
 import com.android.phone.common.animation.AnimUtils;
 import com.android.phone.common.dialpad.DialpadKeyButton;
 import com.android.phone.common.dialpad.DialpadView;
@@ -206,9 +208,6 @@
     // determines if we want to playback local DTMF tones.
     private boolean mDTMFToneEnabled;
 
-    // Vibration (haptic feedback) for dialer key presses.
-    private final HapticFeedback mHaptic = new HapticFeedback();
-
     /** Identifier for the "Add Call" intent extra. */
     private static final String ADD_CALL_MODE_KEY = "add_call_mode";
 
@@ -328,13 +327,6 @@
 
         mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
 
-        try {
-            mHaptic.init(getActivity(),
-                         getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
-        } catch (Resources.NotFoundException nfe) {
-             Log.e(TAG, "Vibrate control bool missing.", nfe);
-        }
-
         mProhibitedPhoneNumberRegexp = getResources().getString(
                 R.string.config_prohibited_phone_number_regexp);
 
@@ -649,9 +641,6 @@
 
         stopWatch.lap("dtwd");
 
-        // Retrieve the haptic feedback setting.
-        mHaptic.checkSystemSetting();
-
         stopWatch.lap("hptc");
 
         mPressedDialpadKeys.clear();
@@ -785,7 +774,7 @@
                 break;
         }
 
-        mHaptic.vibrate();
+        getView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
         mDigits.onKeyDown(keyCode, event);
 
@@ -896,9 +885,12 @@
 
                 boolean enable = !isDigitsEmpty();
                 for (int i = 0; i < menu.size(); i++) {
-                    menu.getItem(i).setEnabled(enable);
+                    MenuItem item = menu.getItem(i);
+                    item.setEnabled(enable);
+                    if (item.getItemId() == R.id.menu_call_with_note) {
+                        item.setVisible(CallUtil.isCallWithSubjectSupported(getContext()));
+                    }
                 }
-
                 super.show();
             }
         };
@@ -911,7 +903,7 @@
     public void onClick(View view) {
         switch (view.getId()) {
             case R.id.dialpad_floating_action_button:
-                mHaptic.vibrate();
+                view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                 handleDialButtonPressed();
                 break;
             case R.id.deleteButton: {
@@ -1470,6 +1462,10 @@
             case R.id.menu_add_wait:
                 updateDialString(WAIT);
                 return true;
+            case R.id.menu_call_with_note:
+                CallSubjectDialog.start(getActivity(), mDigits.getText().toString());
+                hideAndClearDialpad(false);
+                return true;
             default:
                 return false;
         }
diff --git a/src/com/android/dialer/settings/DefaultRingtonePreference.java b/src/com/android/dialer/settings/DefaultRingtonePreference.java
index c12e717..a174381 100644
--- a/src/com/android/dialer/settings/DefaultRingtonePreference.java
+++ b/src/com/android/dialer/settings/DefaultRingtonePreference.java
@@ -16,12 +16,17 @@
 
 package com.android.dialer.settings;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.preference.RingtonePreference;
+import android.provider.Settings;
 import android.util.AttributeSet;
+import android.widget.Toast;
+
+import com.android.dialer.R;
 
 /**
  * RingtonePreference which doesn't show default ringtone setting.
@@ -44,6 +49,13 @@
 
     @Override
     protected void onSaveRingtone(Uri ringtoneUri) {
+        if (!Settings.System.canWrite(getContext())) {
+            Toast.makeText(
+                    getContext(),
+                    getContext().getResources().getString(R.string.toast_cannot_write_system_settings),
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
         RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri);
     }
 
diff --git a/src/com/android/dialer/settings/DialerSettingsActivity.java b/src/com/android/dialer/settings/DialerSettingsActivity.java
index d54053b..c459d35 100644
--- a/src/com/android/dialer/settings/DialerSettingsActivity.java
+++ b/src/com/android/dialer/settings/DialerSettingsActivity.java
@@ -1,23 +1,22 @@
 package com.android.dialer.settings;
 
-import com.google.common.collect.Lists;
-
-import android.content.ComponentName;
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
-import android.os.UserHandle;
+import android.os.Process;
 import android.os.UserManager;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceManager;
-import android.preference.PreferenceActivity.Header;
+import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
+import android.util.Log;
 import android.view.MenuItem;
+import android.widget.Toast;
 
-import com.android.dialer.DialtactsActivity;
+import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.R;
 
 import java.util.List;
@@ -26,8 +25,6 @@
 
     protected SharedPreferences mPreferences;
 
-    private static final int OWNER_HANDLE_ID = 0;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -44,6 +41,7 @@
         Header soundSettingsHeader = new Header();
         soundSettingsHeader.titleRes = R.string.sounds_and_vibration_title;
         soundSettingsHeader.fragment = SoundSettingsFragment.class.getName();
+        soundSettingsHeader.id = R.id.settings_header_sounds_and_vibration;
         target.add(soundSettingsHeader);
 
         Header quickResponseSettingsHeader = new Header();
@@ -91,6 +89,24 @@
     }
 
     @Override
+    public void onHeaderClick(Header header, int position) {
+        if (header.id == R.id.settings_header_sounds_and_vibration) {
+            // If we don't have the permission to write to system settings, go to system sound
+            // settings instead. Otherwise, perform the super implementation (which launches our
+            // own preference fragment.
+            if (!Settings.System.canWrite(this)) {
+                Toast.makeText(
+                        this,
+                        getResources().getString(R.string.toast_cannot_write_system_settings),
+                        Toast.LENGTH_SHORT).show();
+                startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
+                return;
+            }
+        }
+        super.onHeaderClick(header, position);
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == android.R.id.home) {
             onBackPressed();
diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java
index 7fc9394..8384700 100644
--- a/src/com/android/dialer/settings/SoundSettingsFragment.java
+++ b/src/com/android/dialer/settings/SoundSettingsFragment.java
@@ -16,7 +16,9 @@
 
 package com.android.dialer.settings;
 
+import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.Intent;
 import android.media.RingtoneManager;
 import android.os.Bundle;
 import android.os.Handler;
@@ -30,7 +32,10 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
+import android.view.MenuItem;
+import android.widget.Toast;
 
+import com.android.contacts.common.util.PermissionsUtil;
 import com.android.dialer.R;
 import com.android.phone.common.util.SettingsUtil;
 
@@ -127,6 +132,13 @@
     public void onResume() {
         super.onResume();
 
+        if (!Settings.System.canWrite(getContext())) {
+            // If the user launches this setting fragment, then toggles the WRITE_SYSTEM_SETTINGS
+            // AppOp, then close the fragment since there is nothing useful to do.
+            getActivity().onBackPressed();
+            return;
+        }
+
         if (mVibrateWhenRinging != null) {
             mVibrateWhenRinging.setChecked(shouldVibrateWhenRinging());
         }
@@ -143,6 +155,14 @@
      */
     @Override
     public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (!Settings.System.canWrite(getContext())) {
+            // A user shouldn't be able to get here, but this protects against monkey crashes.
+            Toast.makeText(
+                    getContext(),
+                    getResources().getString(R.string.toast_cannot_write_system_settings),
+                    Toast.LENGTH_SHORT).show();
+            return true;
+        }
         if (preference == mVibrateWhenRinging) {
             boolean doVibrate = (Boolean) objValue;
             Settings.System.putInt(getActivity().getContentResolver(),
@@ -161,6 +181,13 @@
      */
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (!Settings.System.canWrite(getContext())) {
+            Toast.makeText(
+                    getContext(),
+                    getResources().getString(R.string.toast_cannot_write_system_settings),
+                    Toast.LENGTH_SHORT).show();
+            return true;
+        }
         if (preference == mPlayDtmfTone) {
             Settings.System.putInt(getActivity().getContentResolver(),
                     Settings.System.DTMF_TONE_WHEN_DIALING,
diff --git a/src/com/android/dialer/util/IntentUtil.java b/src/com/android/dialer/util/IntentUtil.java
index 7ed99c9..2ce3bd1 100644
--- a/src/com/android/dialer/util/IntentUtil.java
+++ b/src/com/android/dialer/util/IntentUtil.java
@@ -146,7 +146,7 @@
     }
 
     public static Intent getSendSmsIntent(CharSequence phoneNumber) {
-        return new Intent(Intent.ACTION_VIEW, Uri.parse(SMS_URI_PREFIX + phoneNumber));
+        return new Intent(Intent.ACTION_SENDTO, Uri.parse(SMS_URI_PREFIX + phoneNumber));
     }
 
     public static Intent getNewContactIntent() {
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
index 213bba1..158ed58 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -279,7 +279,7 @@
         }
 
         disableUiElements();
-        mPositionText.setText(getString(R.string.voicemail_playback_error));
+        mStateText.setText(getString(R.string.voicemail_playback_error));
     }
 
 
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 7270af7..9319b6e 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -417,9 +417,11 @@
             super(handler);
             mFetchResultHandler = handler;
 
-            mContext.getContentResolver().registerContentObserver(
-                    voicemailUri, false, this);
-            mFetchResultHandler.postDelayed(this, FETCH_CONTENT_TIMEOUT_MS);
+            if (mContext != null) {
+                mContext.getContentResolver().registerContentObserver(
+                        voicemailUri, false, this);
+                mFetchResultHandler.postDelayed(this, FETCH_CONTENT_TIMEOUT_MS);
+            }
         }
 
         /**