Merge "Show separate pressed effect for primary/secondary actions"
diff --git a/src/com/android/contacts/activities/DialpadActivity.java b/src/com/android/contacts/activities/DialpadActivity.java
index cfe17f3..1221068 100644
--- a/src/com/android/contacts/activities/DialpadActivity.java
+++ b/src/com/android/contacts/activities/DialpadActivity.java
@@ -64,7 +64,7 @@
     @Override
     protected void onNewIntent(Intent newIntent) {
         setIntent(newIntent);
-        mFragment.resolveIntent(newIntent);
+        mFragment.configureScreenFromIntent(newIntent);
     }
 
     public DialpadFragment getFragment() {
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 01212f5..2040f8d 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -481,12 +481,18 @@
         final String action = newIntent.getAction();
         if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
             setupFilterText(newIntent);
-        } else if (isDialIntent(newIntent)) {
-            setupDialUri(newIntent);
         }
         if (mInSearchUi || mSearchFragment.isVisible()) {
             exitSearchUi();
         }
+
+        if (mViewPager.getCurrentItem() == TAB_INDEX_DIALER) {
+            if (mDialpadFragment != null) {
+                mDialpadFragment.configureScreenFromIntent(newIntent);
+            } else {
+                Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
+            }
+        }
     }
 
     /** Returns true if the given intent contains a phone number to populate the dialer with */
@@ -535,33 +541,6 @@
         }
     }
 
-    /**
-     * Retrieves the uri stored in {@link #setupDialUri(Intent)}. This uri
-     * originally came from a dial intent received by this activity. The stored
-     * uri will then be cleared after after this method returns.
-     *
-     * @return The stored uri
-     */
-    public Uri getAndClearDialUri() {
-        Uri dialUri = mDialUri;
-        mDialUri = null;
-        return dialUri;
-    }
-
-    /**
-     * Stores the uri associated with a dial intent. This is so child activities can
-     * check if they are supposed to display new dial info.
-     *
-     * @param intent The intent received in {@link #onNewIntent(Intent)}
-     */
-    private void setupDialUri(Intent intent) {
-        // If the intent was relaunched from history, don't reapply the intent.
-        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
-            return;
-        }
-        mDialUri = intent.getData();
-    }
-
     @Override
     public void onBackPressed() {
         if (mInSearchUi) {
diff --git a/src/com/android/contacts/calllog/CallLogNotificationsService.java b/src/com/android/contacts/calllog/CallLogNotificationsService.java
index eda11d6..6dd24aa 100644
--- a/src/com/android/contacts/calllog/CallLogNotificationsService.java
+++ b/src/com/android/contacts/calllog/CallLogNotificationsService.java
@@ -18,25 +18,55 @@
 
 import android.app.IntentService;
 import android.content.Intent;
+import android.net.Uri;
 import android.util.Log;
 
 /**
  * Provides operations for managing notifications.
  * <p>
- * At the moment, it only handle {@link #ACTION_MARK_NEW_CALLS_AS_OLD}, which marks all the new
- * items in the call log as old; this is called when a notification is dismissed.
+ * It handles the following actions:
+ * <ul>
+ * <li>{@link #ACTION_MARK_NEW_CALLS_AS_OLD}: marks all the new items in the call log as old; this
+ * is called when a notification is dismissed.</li>
+ * <li>{@link #ACTION_UPDATE_NOTIFICATIONS}: updates the content of the new items notification; it
+ * may include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}, containing the URI of the new
+ * voicemail that has triggered this update (if any).</li>
+ * </ul>
  */
 public class CallLogNotificationsService extends IntentService {
     private static final String TAG = "CallLogNotificationsService";
 
-    // Action to mark all the new calls as old. Invoked when the notifications need to be cleared.
+    /**
+     * Action to mark all the new calls as old.
+     */
     public static final String ACTION_MARK_NEW_CALLS_AS_OLD =
-            "com.android.contacts.ACTION_MARK_NEW_CALLS_AS_OLD";
+            "com.android.contacts.calllog.MARK_NEW_CALLS_AS_OLD";
+
+    /**
+     * Action to update the notifications.
+     * <p>
+     * May include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}.
+     */
+    public static final String ACTION_UPDATE_NOTIFICATIONS =
+            "com.android.contacts.calllog.UPDATE_NOTIFICATIONS";
+
+    /**
+     * Extra to included with {@link #ACTION_UPDATE_NOTIFICATIONS} to identify the new voicemail
+     * that triggered an update.
+     * <p>
+     * It must be a {@link Uri}.
+     */
+    public static final String EXTRA_NEW_VOICEMAIL_URI = "NEW_VOICEMAIL_URI";
 
     private CallLogQueryHandler mCallLogQueryHandler;
 
     public CallLogNotificationsService() {
         super("CallLogNotificationsService");
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
         mCallLogQueryHandler = new CallLogQueryHandler(getContentResolver(), null /*listener*/);
     }
 
@@ -44,10 +74,11 @@
     protected void onHandleIntent(Intent intent) {
         if (ACTION_MARK_NEW_CALLS_AS_OLD.equals(intent.getAction())) {
             mCallLogQueryHandler.markNewCallsAsOld();
-            return;
+        } else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
+            Uri voicemailUri = (Uri) intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI);
+            DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri);
         } else {
             Log.d(TAG, "onHandleIntent: could not handle: " + intent);
         }
     }
-
 }
diff --git a/src/com/android/contacts/calllog/CallLogReceiver.java b/src/com/android/contacts/calllog/CallLogReceiver.java
index a3ff1f2..14bfa64 100644
--- a/src/com/android/contacts/calllog/CallLogReceiver.java
+++ b/src/com/android/contacts/calllog/CallLogReceiver.java
@@ -16,9 +16,7 @@
 
 package com.android.contacts.calllog;
 
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.provider.VoicemailContract;
@@ -33,29 +31,20 @@
 public class CallLogReceiver extends BroadcastReceiver {
     private static final String TAG = "CallLogReceiver";
 
-    private VoicemailNotifier mNotifier;
-
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (mNotifier == null) {
-            mNotifier = getNotifier(context);
-        }
         if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) {
-            mNotifier.notifyNewVoicemail(intent.getData());
+            Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
+            serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
+            serviceIntent.putExtra(
+                    CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, intent.getData());
+            context.startService(serviceIntent);
         } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
-            mNotifier.updateNotification();
+            Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
+            serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
+            context.startService(serviceIntent);
         } else {
-            Log.d(TAG, "onReceive: could not handle: " + intent);
+            Log.w(TAG, "onReceive: could not handle: " + intent);
         }
     }
-
-    private VoicemailNotifier getNotifier(Context context) {
-        NotificationManager notificationManager =
-                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        ContentResolver contentResolver = context.getContentResolver();
-        return new DefaultVoicemailNotifier(context, notificationManager,
-                DefaultVoicemailNotifier.createNewCallsQuery(contentResolver),
-                DefaultVoicemailNotifier.createNameLookupQuery(contentResolver),
-                DefaultVoicemailNotifier.createPhoneNumberHelper(context));
-    }
 }
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
index bb0b0f3..1cf67c1 100644
--- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -51,13 +51,30 @@
     /** The identifier of the notification of new voicemails. */
     private static final int NOTIFICATION_ID = 1;
 
+    /** The singleton instance of {@link DefaultVoicemailNotifier}. */
+    private static DefaultVoicemailNotifier sInstance;
+
     private final Context mContext;
     private final NotificationManager mNotificationManager;
     private final NewCallsQuery mNewCallsQuery;
     private final NameLookupQuery mNameLookupQuery;
     private final PhoneNumberHelper mPhoneNumberHelper;
 
-    public DefaultVoicemailNotifier(Context context,
+    /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */
+    public static synchronized DefaultVoicemailNotifier getInstance(Context context) {
+        if (sInstance == null) {
+            NotificationManager notificationManager =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            ContentResolver contentResolver = context.getContentResolver();
+            sInstance = new DefaultVoicemailNotifier(context, notificationManager,
+                    createNewCallsQuery(contentResolver),
+                    createNameLookupQuery(contentResolver),
+                    createPhoneNumberHelper(context));
+        }
+        return sInstance;
+    }
+
+    private DefaultVoicemailNotifier(Context context,
             NotificationManager notificationManager, NewCallsQuery newCallsQuery,
             NameLookupQuery nameLookupQuery, PhoneNumberHelper phoneNumberHelper) {
         mContext = context;
@@ -67,20 +84,9 @@
         mPhoneNumberHelper = phoneNumberHelper;
     }
 
-    @Override
-    public void notifyNewVoicemail(Uri newVoicemailUri) {
-        Log.d(TAG, "notifyNewVoicemail: " + newVoicemailUri);
-        updateNotification(newVoicemailUri);
-    }
-
-    @Override
-    public void updateNotification() {
-        Log.d(TAG, "updateNotification");
-        updateNotification(null);
-    }
-
     /** Updates the notification and notifies of the call with the given URI. */
-    private void updateNotification(Uri newCallUri) {
+    @Override
+    public void updateNotification(Uri newCallUri) {
         // Lookup the list of new voicemails to include in the notification.
         // TODO: Move this into a service, to avoid holding the receiver up.
         final NewCall[] newCalls = mNewCallsQuery.query();
diff --git a/src/com/android/contacts/calllog/VoicemailNotifier.java b/src/com/android/contacts/calllog/VoicemailNotifier.java
index 1ac949d..8d45486 100644
--- a/src/com/android/contacts/calllog/VoicemailNotifier.java
+++ b/src/com/android/contacts/calllog/VoicemailNotifier.java
@@ -23,17 +23,15 @@
  */
 public interface VoicemailNotifier {
     /**
-     * Notifies the user of a new voicemail.
+     * Updates the notification and clears it if there are no new voicemails.
+     * <p>
+     * If the given URI corresponds to a new voicemail, also notifies about it.
+     * <p>
+     * It is not safe to call this method from the main thread.
      *
-     * @param newVoicemailUri URI of the new voicemail record just inserted
+     * @param newCallUri URI of the new call, may be null
      */
-    public void notifyNewVoicemail(Uri newVoicemailUri);
-
-    /**
-     * Updates the notification and clears it if there are no new voicemails. Called when the phone
-     * just rebooted to put back notifications for anything the user has not acknowledged.
-     */
-    public void updateNotification();
+    public void updateNotification(Uri newCallUri);
 
     /** Clears the new voicemail notification. */
     public void clearNotification();
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index add6b80..a5db5ce 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -143,9 +143,6 @@
     static final String EXTRA_SEND_EMPTY_FLASH
             = "com.android.phone.extra.SEND_EMPTY_FLASH";
 
-    /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
-    private boolean mIsAddCallMode;
-
     private String mCurrentCountryIso;
 
     /**
@@ -313,7 +310,7 @@
         mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
         mDialpadChooser.setOnItemClickListener(this);
 
-        resolveIntent(getActivity().getIntent());
+        configureScreenFromIntent(getActivity().getIntent());
 
         return fragmentView;
     }
@@ -323,38 +320,18 @@
     }
 
     /**
-     * Handles the intent that launched us.
-     *
-     * We can be launched either with ACTION_DIAL or ACTION_VIEW (which
-     * may include a phone number to pre-load), or ACTION_MAIN (which just
-     * brings up a blank dialpad).
-     *
-     * @return true IFF the current intent has the DialtactsActivity.EXTRA_IGNORE_STATE
-     *    extra set to true, which indicates (to our container) that we should ignore
-     *    any possible saved state, and instead reset our state based on the parent's
-     *    intent.
+     * @return true when {@link #mDigits} is actually filled by the Intent.
      */
-    public boolean resolveIntent(Intent intent) {
-        boolean ignoreState = false;
-
-        // by default we are not adding a call.
-        mIsAddCallMode = false;
-
-        // By default we don't show the "dialpad chooser" UI.
-        boolean needToShowDialpadChooser = false;
-
-        // Resolve the intent
+    private boolean fillDigitsIfNecessary(Intent intent) {
         final String action = intent.getAction();
         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
-            // see if we are "adding a call" from the InCallScreen; false by default.
-            mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
-
             Uri uri = intent.getData();
             if (uri != null) {
                 if ("tel".equals(uri.getScheme())) {
                     // Put the requested number into the input area
                     String data = uri.getSchemeSpecificPart();
                     setFormattedDigits(data, null);
+                    return true;
                 } else {
                     String type = intent.getType();
                     if (People.CONTENT_ITEM_TYPE.equals(type)
@@ -364,22 +341,42 @@
                                 new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
                                 null, null, null);
                         if (c != null) {
-                            if (c.moveToFirst()) {
-                                // Put the number into the input area
-                                setFormattedDigits(c.getString(0), c.getString(1));
+                            try {
+                                if (c.moveToFirst()) {
+                                    // Put the number into the input area
+                                    setFormattedDigits(c.getString(0), c.getString(1));
+                                    return true;
+                                }
+                            } finally {
+                                c.close();
                             }
-                            c.close();
                         }
                     }
                 }
-            } else {
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @see #showDialpadChooser(boolean)
+     */
+    private static boolean needToShowDialpadChooser(Intent intent, boolean isAddCallMode) {
+        final String action = intent.getAction();
+
+        boolean needToShowDialpadChooser = false;
+
+        if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
+            Uri uri = intent.getData();
+            if (uri == null) {
                 // ACTION_DIAL or ACTION_VIEW with no data.
                 // This behaves basically like ACTION_MAIN: If there's
                 // already an active call, bring up an intermediate UI to
                 // make the user confirm what they really want to do.
                 // Be sure *not* to show the dialpad chooser if this is an
                 // explicit "Add call" action, though.
-                if (!mIsAddCallMode && phoneIsInUse()) {
+                if (!isAddCallMode && phoneIsInUse()) {
                     needToShowDialpadChooser = true;
                 }
             }
@@ -399,11 +396,34 @@
             }
         }
 
-        // Bring up the "dialpad chooser" IFF we need to make the user
-        // confirm which dialpad they really want.
-        showDialpadChooser(needToShowDialpadChooser);
+        return needToShowDialpadChooser;
+    }
 
-        return ignoreState;
+    private static boolean isAddCallMode(Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
+            // see if we are "adding a call" from the InCallScreen; false by default.
+            return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires
+     * the screen to enter "Add Call" mode, this method will show correct UI for the mode.
+     */
+    public void configureScreenFromIntent(Intent intent) {
+        boolean needToShowDialpadChooser = false;
+
+        final boolean isAddCallMode = isAddCallMode(intent);
+        if (!isAddCallMode) {
+            final boolean digitsFilled = fillDigitsIfNecessary(intent);
+            if (!digitsFilled) {
+                needToShowDialpadChooser = needToShowDialpadChooser(intent, isAddCallMode);
+            }
+        }
+        showDialpadChooser(needToShowDialpadChooser);
     }
 
     private void setFormattedDigits(String data, String normalizedNumber) {
@@ -476,13 +496,10 @@
         }
 
         Activity parent = getActivity();
-        // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
-        // digits in the dialer field.
         if (parent instanceof DialtactsActivity) {
-            Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
-            if (dialUri != null) {
-                resolveIntent(parent.getIntent());
-            }
+            // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
+            // digits in the dialer field.
+            fillDigitsIfNecessary(parent.getIntent());
         }
 
         // While we're in the foreground, listen for phone state changes,
@@ -904,8 +921,8 @@
             // ListView.  We do this only once.
             if (mDialpadChooserAdapter == null) {
                 mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
-                mDialpadChooser.setAdapter(mDialpadChooserAdapter);
             }
+            mDialpadChooser.setAdapter(mDialpadChooserAdapter);
         } else {
             // Log.i(TAG, "Displaying normal Dialer UI.");
             mDigits.setVisibility(View.VISIBLE);