Merge "Protect against class cast exception" into klp-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 55cdea4..3723fd7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -519,6 +519,9 @@
     <!-- Message displayed when there is no application available to handle the add contact menu option. [CHAR LIMIT=NONE] -->
     <string name="add_contact_not_available">Re-enable the People application to use this feature.</string>
 
+    <!-- Message displayed when there is no application available to handle voice search. [CHAR LIMIT=NONE] -->
+    <string name="voice_search_not_available">Voice search is not available.</string>
+
     <!-- Hint displayed in dialer search box when there is no query that is currently typed.
          [CHAR LIMIT=30] -->
     <string name="dialer_hint_find_contact">Type a name or phone number</string>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index a24940d..a8d9d6f 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ActivityNotFoundException;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -643,7 +644,13 @@
             mMainActionPushLayerView.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    startActivity(actionIntent);
+                    try {
+                        startActivity(actionIntent);
+                    } catch (ActivityNotFoundException e) {
+                        final Toast toast = Toast.makeText(CallDetailActivity.this,
+                                R.string.add_contact_not_available, Toast.LENGTH_SHORT);
+                        toast.show();
+                    }
                 }
             });
             mMainActionPushLayerView.setContentDescription(actionDescription);
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 04934c7..0c0fcda 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -27,6 +27,8 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
@@ -48,6 +50,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView.OnScrollListener;
@@ -76,6 +79,7 @@
 import com.android.internal.telephony.ITelephony;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The dialer tab's title is 'phone', a more common name (see strings.xml).
@@ -330,7 +334,7 @@
             hideDialpadFragment(false, true);
             mInCallDialpadUp = false;
         }
-
+        prepareVoiceSearchButton();
         mFirstLaunch = false;
         mDialerDatabaseHelper.startSmartDialUpdateThread();
     }
@@ -447,8 +451,13 @@
                 }
                 break;
             case R.id.voice_search_button:
-                final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-                startActivityForResult(voiceIntent, ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
+                try {
+                    startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+                            ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
+                } catch (ActivityNotFoundException e) {
+                    Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
+                            Toast.LENGTH_SHORT).show();
+                }
                 break;
             default: {
                 Log.wtf(TAG, "Unexpected onClick event from " + view);
@@ -507,8 +516,7 @@
         mSearchViewContainer = findViewById(R.id.search_view_container);
         mSearchViewCloseButton = findViewById(R.id.search_close_button);
         mSearchViewCloseButton.setOnClickListener(this);
-        mVoiceSearchButton = findViewById(R.id.voice_search_button);
-        mVoiceSearchButton.setOnClickListener(this);
+
         mSearchView = (EditText) findViewById(R.id.search_view);
         mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
 
@@ -524,6 +532,19 @@
         ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         mSearchView.setHint(ssb);
+
+        prepareVoiceSearchButton();
+    }
+
+    private void prepareVoiceSearchButton() {
+        mVoiceSearchButton = findViewById(R.id.voice_search_button);
+        final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        if (canIntentBeHandled(voiceIntent)) {
+            mVoiceSearchButton.setVisibility(View.VISIBLE);
+            mVoiceSearchButton.setOnClickListener(this);
+        } else {
+            mVoiceSearchButton.setVisibility(View.GONE);
+        }
     }
 
     final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@@ -887,12 +908,6 @@
         } else if (getInSearchUi()) {
             mSearchView.setText(null);
             mDialpadFragment.clearDialpad();
-        } else if (isTaskRoot()) {
-            // Instead of stopping, simply push this to the back of the stack.
-            // This is only done when running at the top of the stack;
-            // otherwise, we have been launched by someone else so need to
-            // allow the user to go back to the caller.
-            moveTaskToBack(false);
         } else {
             super.onBackPressed();
         }
@@ -955,4 +970,11 @@
         intent.putExtra(Intents.Insert.NAME, text);
         return intent;
     }
+
+    private boolean canIntentBeHandled(Intent intent) {
+        final PackageManager packageManager = getPackageManager();
+        final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        return resolveInfo != null && resolveInfo.size() > 0;
+    }
 }
diff --git a/src/com/android/dialer/calllog/CallLogNotificationsService.java b/src/com/android/dialer/calllog/CallLogNotificationsService.java
index 3270963..ccd9335 100644
--- a/src/com/android/dialer/calllog/CallLogNotificationsService.java
+++ b/src/com/android/dialer/calllog/CallLogNotificationsService.java
@@ -70,6 +70,10 @@
 
     @Override
     protected void onHandleIntent(Intent intent) {
+        if (intent == null) {
+            Log.d(TAG, "onHandleIntent: could not handle null intent");
+            return;
+        }
         if (ACTION_MARK_NEW_VOICEMAILS_AS_OLD.equals(intent.getAction())) {
             mCallLogQueryHandler.markNewVoicemailsAsOld();
         } else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
diff --git a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java b/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
index 9913c20..3eacfec 100644
--- a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
+++ b/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java
@@ -50,7 +50,7 @@
      * mock-out this, it is not a static method.
      */
     public boolean isVoicemailNumber(CharSequence number) {
-        return PhoneNumberUtils.isVoiceMailNumber(number.toString());
+        return number!= null && PhoneNumberUtils.isVoiceMailNumber(number.toString());
     }
 
     /**
@@ -58,7 +58,7 @@
      * static method.
      */
     public boolean isSipNumber(CharSequence number) {
-        return PhoneNumberUtils.isUriNumber(number.toString());
+        return number != null && PhoneNumberUtils.isUriNumber(number.toString());
     }
 
     public static boolean isUnknownNumberThatCanBeLookedUp(CharSequence number, int presentation) {
@@ -77,13 +77,13 @@
         if (new PhoneNumberUtilsWrapper().isVoicemailNumber(number)) {
             return false;
         }
-        if (isLegacyUnknownNumbers(number.toString())) {
+        if (isLegacyUnknownNumbers(number)) {
             return false;
         }
         return true;
     }
 
     public static boolean isLegacyUnknownNumbers(CharSequence number) {
-        return LEGACY_UNKNOWN_NUMBERS.contains(number.toString());
+        return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString());
     }
 }
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index b9e4b9a..852b7c1 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -628,10 +628,26 @@
 
             updatedContactCursor.moveToPosition(-1);
             while (updatedContactCursor.moveToNext()) {
-                insert.bindLong(1, updatedContactCursor.getLong(PhoneQuery.PHONE_ID));
-                insert.bindString(2, updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER));
-                insert.bindLong(3, updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID));
-                insert.bindString(4, updatedContactCursor.getString(PhoneQuery.PHONE_LOOKUP_KEY));
+                insert.clearBindings();
+
+                // Handle string columns which can possibly be null first. In the case of certain
+                // null columns (due to malformed rows possibly inserted by third-party apps
+                // or sync adapters), skip the phone number row.
+                final String number = updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER);
+                if (TextUtils.isEmpty(number)) {
+                    continue;
+                } else {
+                    insert.bindString(2, number);
+                }
+
+                final String lookupKey = updatedContactCursor.getString(
+                        PhoneQuery.PHONE_LOOKUP_KEY);
+                if (TextUtils.isEmpty(lookupKey)) {
+                    continue;
+                } else {
+                    insert.bindString(4, lookupKey);
+                }
+
                 final String displayName = updatedContactCursor.getString(
                         PhoneQuery.PHONE_DISPLAY_NAME);
                 if (displayName == null) {
@@ -639,6 +655,9 @@
                 } else {
                     insert.bindString(5, displayName);
                 }
+
+                insert.bindLong(1, updatedContactCursor.getLong(PhoneQuery.PHONE_ID));
+                insert.bindLong(3, updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID));
                 insert.bindLong(6, updatedContactCursor.getLong(PhoneQuery.PHONE_PHOTO_ID));
                 insert.bindLong(7, updatedContactCursor.getLong(PhoneQuery.PHONE_LAST_TIME_USED));
                 insert.bindLong(8, updatedContactCursor.getInt(PhoneQuery.PHONE_TIMES_USED));
@@ -648,8 +667,6 @@
                 insert.bindLong(12, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_PRIMARY));
                 insert.bindLong(13, currentMillis);
                 insert.executeInsert();
-                insert.clearBindings();
-
                 final String contactPhoneNumber =
                         updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER);
                 final ArrayList<String> numberPrefixes =
diff --git a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
index 30d01d2..c7e4c5f 100644
--- a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
+++ b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
@@ -41,16 +41,16 @@
             if (TextUtils.isEmpty(number)) {
                 return;
             }
-            final long id = getContactIdFromPhoneNumber(context, number);
-            if (id != NO_CONTACT_FOUND) {
-                final Thread thread = new Thread() {
-                    @Override
-                    public void run() {
+            final Thread thread = new Thread() {
+                @Override
+                public void run() {
+                    final long id = getContactIdFromPhoneNumber(context, number);
+                    if (id != NO_CONTACT_FOUND) {
                         undemoteContactWithId(context, id);
                     }
-                };
-                thread.start();
-            }
+                }
+            };
+            thread.start();
         }
     }
 
@@ -68,6 +68,9 @@
                 Uri.encode(number));
         final Cursor cursor = context.getContentResolver().query(contactUri, new String[] {
                 PhoneLookup._ID}, null, null, null);
+        if (cursor == null) {
+            return NO_CONTACT_FOUND;
+        }
         try {
             if (cursor.moveToFirst()) {
                 final long id = cursor.getLong(0);
diff --git a/src/com/android/dialer/list/PhoneFavoriteFragment.java b/src/com/android/dialer/list/PhoneFavoriteFragment.java
index 9e38394..81d9bfd 100644
--- a/src/com/android/dialer/list/PhoneFavoriteFragment.java
+++ b/src/com/android/dialer/list/PhoneFavoriteFragment.java
@@ -453,8 +453,11 @@
     }
 
     private void saveHorizontalOffsets(ContactTileRow row, ArrayList<ContactEntry> list) {
-        for (int i = 0; i < list.size(); i++) {
+        for (int i = 0; i < list.size() && i < row.getChildCount(); i++) {
             final View child = row.getChildAt(i);
+            if (child == null) {
+                continue;
+            }
             final ContactEntry entry = list.get(i);
             final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id);
             if (DEBUG) {
diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
index e28fd73..cd4fb00 100644
--- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
@@ -88,6 +88,7 @@
     private long mIdToKeepInPlace = -1;
 
     private boolean mAwaitingRemove = false;
+    private boolean mDelayCursorUpdates = false;
 
     private ContactPhotoManager mPhotoManager;
     protected int mNumFrequents;
@@ -178,6 +179,7 @@
      * @param inDragging Boolean variable indicating whether there is a drag in process.
      */
     public void setInDragging(boolean inDragging) {
+        mDelayCursorUpdates = inDragging;
         mInDragging = inDragging;
     }
 
@@ -224,7 +226,7 @@
      * Else use {@link ContactTileLoaderFactory}
      */
     public void setContactCursor(Cursor cursor) {
-        if (cursor != null && !cursor.isClosed()) {
+        if (!mDelayCursorUpdates && cursor != null && !cursor.isClosed()) {
             mNumStarred = getNumStarredContacts(cursor);
             if (mAwaitingRemove) {
                 mDataSetChangedListener.cacheOffsetsForDatasetChange();