Merge "Don't show welcome screen when talkback is on and on pre-L-MR1" into ub-contactsdialer-h-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6441042..6144e55 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.contacts"
-    android:versionCode="20000"
-    android:versionName="2.0.0">
+    android:versionCode="10605"
+    android:versionName="1.6.5">
 
     <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
 
@@ -357,13 +357,6 @@
             android:windowSoftInputMode="stateHidden|adjustResize">
 
             <intent-filter>
-                <action android:name="android.intent.action.EDIT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
-                <data android:mimeType="vnd.android.cursor.item/contact" />
-                <data android:mimeType="vnd.android.cursor.item/raw_contact" />
-            </intent-filter>
-            <intent-filter>
                 <action android:name="android.intent.action.INSERT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.cursor.dir/person" />
@@ -372,6 +365,22 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name=".activities.ContactEditorSpringBoardActivity"
+            android:noHistory="true"
+            android:theme="@style/TransparentThemeAppCompat">
+
+            <intent-filter>
+                <action android:name="android.intent.action.EDIT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.item/person" />
+                <data android:mimeType="vnd.android.cursor.item/contact" />
+                <data android:mimeType="vnd.android.cursor.item/raw_contact" />
+            </intent-filter>
+
+        </activity>
+
+
         <activity android:name=".common.test.FragmentTestActivity">
             <intent-filter>
                 <category android:name="android.intent.category.TEST" />
diff --git a/proguard.flags b/proguard.flags
index b7b9f18..80ffe0a 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -73,6 +73,7 @@
 -keep class com.android.contacts.common.util.DeviceLocalAccountTypeFactory { *; }
 -keep class com.android.contacts.common.util.DeviceLocalAccountTypeFactory$* { *; }
 -keep class com.android.contacts.common.util.NameConverter { *; }
+-keep class com.android.contacts.common.util.PermissionsUtil { *; }
 -keep class com.android.contacts.common.util.SearchUtil { *; }
 -keep class com.android.contacts.common.util.SearchUtil$* { *; }
 -keep class com.android.contacts.ContactsApplication { *; }
diff --git a/res/layout/raw_contact_list_item.xml b/res/layout/raw_contact_list_item.xml
new file mode 100644
index 0000000..ec5de0c
--- /dev/null
+++ b/res/layout/raw_contact_list_item.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="24dp"
+    android:paddingRight="24dp"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:background="?android:attr/selectableItemBackground"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/photo"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_marginEnd="16dp"
+        android:scaleType="fitCenter"/>
+
+    <LinearLayout
+        android:id="@+id/text_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/display_name"
+            android:textSize="16sp"
+            android:textColor="@color/quantum_black_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"/>
+
+        <LinearLayout
+            android:id="@+id/account_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical">
+
+            <ImageView
+                android:id="@+id/account_icon"
+                android:layout_width="14dp"
+                android:layout_height="14dp"
+                android:layout_gravity="center_vertical"
+                android:layout_marginEnd="4dp"
+                android:scaleType="fitCenter"/>
+
+            <TextView
+                android:id="@+id/account_name"
+                android:textSize="13sp"
+                android:textColor="@color/quantum_black_secondary_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"/>
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 081d579..98c522d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <resources>
+    <!-- 87% black -->
+    <color name="quantum_black_text">#dd000000</color>
+
     <!-- 54% black -->
     <color name="quantum_black_secondary_text">#89000000</color>
 
@@ -42,6 +45,9 @@
     <color name="primary_color_dark">#0277bd</color>
     <color name="primary_color">#0288d1</color>
 
+    <color name="group_primary_color_dark">#546E7A</color>
+    <color name="group_primary_color">#607D8B</color>
+
     <!-- Color of the selected tab underline -->
     <color name="contacts_accent_color">#FFFFFF</color>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 566bd75..454777e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -693,6 +693,12 @@
          at a pre-determined text size. [CHAR LIMIT=20] -->
     <string name="contact_editor_title_existing_contact">Edit contact</string>
 
+    <!-- Dialog title when the user is selecting a raw contact to edit.  [CHAR LIMIT=128] -->
+    <string name="contact_editor_pick_raw_contact_dialog_title">Choose linked contact</string>
+
+    <!-- Text next to a contact's name that lets the user know it is a read only contact.  [CHAR LIMIT=128] -->
+    <string name="contact_editor_pick_raw_contact_read_only"><xliff:g id="display_name">%s</xliff:g> (read only)</string>
+
     <!-- Button label to prompt the user to add an account (when there are 0 existing accounts on the device) [CHAR LIMIT=30] -->
     <string name="add_account">Add account</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e7b6582..d51392a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -498,4 +498,14 @@
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowElevation">0dp</item>
     </style>
+
+    <!-- Transparent/blank activity -->
+    <style name="TransparentThemeAppCompat" parent="@style/PeopleActivityTheme">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
 </resources>
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index b810eec..3fc3c9d 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -54,6 +54,7 @@
 import com.android.contacts.common.util.AccountFilterUtil;
 import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils;
 import com.android.contacts.common.util.ViewUtil;
 import com.android.contacts.editor.ContactEditorFragment;
 import com.android.contacts.editor.SelectAccountDialogFragment;
@@ -134,6 +135,7 @@
             // another fragment in navigation drawer, the current search/selection mode will be
             // overlaid by the action bar of the newly-created fragment.
             stopSearchAndSelection();
+            updateStatusBarBackground();
         }
 
         private void stopSearchAndSelection() {
@@ -170,7 +172,7 @@
             super.onDrawerStateChanged(newState);
             // Set transparent status bar when drawer starts to move.
             if (newState != DrawerLayout.STATE_IDLE) {
-                makeStatusBarTransparent();
+                updateStatusBarBackground();
             }
             if (mRunnable != null && newState == DrawerLayout.STATE_IDLE) {
                 mRunnable.run();
@@ -285,17 +287,23 @@
     protected void onResume() {
         super.onResume();
         if (mDrawer.isDrawerOpen(GravityCompat.START)) {
-            makeStatusBarTransparent();
+            updateStatusBarBackground();
         }
     }
 
-    private void makeStatusBarTransparent() {
-        // Avoid making status bar transparent when action bar's selection mode is on.
-        if (getWindow().getStatusBarColor() !=
-                ContextCompat.getColor(this, R.color.contextual_selection_bar_status_bar_color)
-                        && CompatUtils.isLollipopCompatible()) {
-            getWindow().setStatusBarColor(Color.TRANSPARENT);
+    public void updateStatusBarBackground() {
+        updateStatusBarBackground(/* color */ -1);
+    }
+
+    public void updateStatusBarBackground(int color) {
+        if (!CompatUtils.isLollipopCompatible()) return;
+        if (color == -1) {
+            mDrawer.setStatusBarBackgroundColor(MaterialColorMapUtils.getStatusBarColor(this));
+        } else {
+            mDrawer.setStatusBarBackgroundColor(color);
         }
+        mDrawer.invalidate();
+        getWindow().setStatusBarColor(Color.TRANSPARENT);
     }
 
     // Set up fragment manager to load groups and filters.
@@ -417,7 +425,7 @@
         return null;
     }
 
-    protected boolean isGroupView() {
+    public boolean isGroupView() {
         return mCurrentView == ContactsView.GROUP_VIEW;
     }
 
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 824d4ea..880aa63 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -43,9 +43,11 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
+import com.android.contacts.ContactsDrawerActivity;
 import com.android.contacts.R;
 import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
 import com.android.contacts.common.compat.CompatUtils;
+import com.android.contacts.common.util.MaterialColorMapUtils;
 import com.android.contacts.list.ContactsRequest;
 
 import java.util.ArrayList;
@@ -365,6 +367,8 @@
                 = (mSearchContainer.getParent() == null) == mSearchMode;
         final boolean isTabHeightChanging = isSearchModeChanging || isSelectionModeChanging;
 
+        // Update toolbar and status bar color.
+        mToolBarFrame.setBackgroundColor(MaterialColorMapUtils.getToolBarColor(mActivity));
         updateStatusBarColor(isSelectionModeChanging && !isSearchModeChanging);
 
         // When skipAnimation=true, it is possible that we will switch from search mode
@@ -494,12 +498,11 @@
                     mActivity, R.color.contextual_selection_bar_status_bar_color);
             runStatusBarAnimation(/* colorTo */ cabStatusBarColor);
         } else {
-            final int normalStatusBarColor = ContextCompat.getColor(
-                    mActivity, R.color.primary_color_dark);
             if (shouldAnimate) {
-                runStatusBarAnimation(/* colorTo */ normalStatusBarColor);
-            } else {
-                mActivity.getWindow().setStatusBarColor(normalStatusBarColor);
+                runStatusBarAnimation(/* colorTo */
+                        MaterialColorMapUtils.getStatusBarColor(mActivity));
+            } else if (mActivity instanceof ContactsDrawerActivity) {
+                ((ContactsDrawerActivity) mActivity).updateStatusBarBackground();
             }
         }
     }
diff --git a/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
new file mode 100644
index 0000000..f32ee5b
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
@@ -0,0 +1,182 @@
+package com.android.contacts.activities;
+
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.app.LoaderManager;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.widget.Toast;
+
+import com.android.contacts.AppCompatContactsActivity;
+import com.android.contacts.R;
+import com.android.contacts.common.activity.RequestPermissionsActivity;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.contacts.editor.ContactEditorFragment;
+import com.android.contacts.editor.EditorIntents;
+import com.android.contacts.editor.PickRawContactDialogFragment;
+import com.android.contacts.editor.PickRawContactLoader;
+
+/**
+ * Transparent springboard activity that hosts a dialog to select a raw contact to edit.
+ * This activity has noHistory set to true, and all intents coming out from it have
+ * {@code FLAG_ACTIVITY_FORWARD_RESULT} set.
+ */
+public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity  {
+    private static final String TAG = "EditorSpringBoard";
+    private static final String TAG_RAW_CONTACTS_DIALOG = "rawContactsDialog";
+    private static final int LOADER_RAW_CONTACTS = 1;
+
+    private Uri mUri;
+    private Cursor mCursor;
+    private MaterialPalette mMaterialPalette;
+
+    /**
+     * The contact data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Cursor> mRawContactLoaderListener =
+            new LoaderManager.LoaderCallbacks<Cursor>() {
+
+                @Override
+                public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+                    return new PickRawContactLoader(ContactEditorSpringBoardActivity.this, mUri);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+                    if (cursor == null) {
+                        Toast.makeText(ContactEditorSpringBoardActivity.this,
+                                R.string.editor_failed_to_load, Toast.LENGTH_SHORT).show();
+                        finish();
+                        return;
+                    }
+                    mCursor = cursor;
+                    if (mCursor.getCount() == 1) {
+                        loadEditor();
+                    } else {
+                        showDialog();
+                    }
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Cursor> loader) {
+                    mCursor = null;
+                }
+            };
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (RequestPermissionsActivity.startPermissionActivity(this)) {
+            return;
+        }
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+
+        if (!Intent.ACTION_EDIT.equals(action)) {
+            finish();
+            return;
+        }
+        // Just for shorter variable names.
+        final String primary = ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR;
+        final String secondary =
+                ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR;
+        if (intent.hasExtra(primary) && intent.hasExtra(secondary)) {
+            mMaterialPalette = new MaterialPalette(intent.getIntExtra(primary, -1),
+                    intent.getIntExtra(secondary, -1));
+        }
+
+        mUri = intent.getData();
+        final String authority = mUri.getAuthority();
+        final String type = getContentResolver().getType(mUri);
+        // Go straight to editor if we're passed a raw contact Uri.
+        if (ContactsContract.AUTHORITY.equals(authority) &&
+                RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+            final long rawContactId = ContentUris.parseId(mUri);
+            final Intent editorIntent = getIntentForRawContact(rawContactId);
+            ImplicitIntentsUtil.startActivityInApp(this, editorIntent);
+        } else {
+            getLoaderManager().initLoader(LOADER_RAW_CONTACTS, null, mRawContactLoaderListener);
+        }
+    }
+
+    /**
+     * Start the dialog to pick the raw contact to edit.
+     */
+    private void showDialog() {
+        final FragmentManager fm = getFragmentManager();
+        final PickRawContactDialogFragment oldFragment = (PickRawContactDialogFragment)
+                fm.findFragmentByTag(TAG_RAW_CONTACTS_DIALOG);
+        final FragmentTransaction ft = fm.beginTransaction();
+        if (oldFragment != null) {
+            ft.remove(oldFragment);
+        }
+        final PickRawContactDialogFragment newFragment =
+                PickRawContactDialogFragment.getInstance(mUri, mCursor, mMaterialPalette);
+        ft.add(newFragment, TAG_RAW_CONTACTS_DIALOG);
+        // commitAllowingStateLoss is safe in this activity because the fragment entirely depends
+        // on the result of the loader. Even if we lose the fragment because the activity was
+        // in the background, when it comes back onLoadFinished will be called again which will
+        // have all the state the picker needs. This situation should be very rare, since the load
+        // should be quick.
+        ft.commitAllowingStateLoss();
+    }
+
+    /**
+     * Starts the editor for the first (only) raw contact in the cursor.
+     */
+    private void loadEditor() {
+        final Intent intent;
+        if (isSingleWritableAccount()) {
+            mCursor.moveToFirst();
+            final long rawContactId = mCursor.getLong(PickRawContactLoader.RAW_CONTACT_ID);
+            intent = getIntentForRawContact(rawContactId);
+
+        } else {
+            // If it's a single read-only raw contact, we'll want to let the editor create
+            // the writable raw contact for it.
+            intent = EditorIntents.createEditContactIntent(this, mUri, mMaterialPalette, -1);
+            intent.setClass(this, ContactEditorActivity.class);
+        }
+        ImplicitIntentsUtil.startActivityInApp(this, intent);
+    }
+
+    /**
+     * @return true if there is only one raw contact in the contact and it is from a writable
+     * account.
+     */
+    private boolean isSingleWritableAccount() {
+        if (mCursor.getCount() != 1) {
+            return false;
+        }
+        mCursor.moveToFirst();
+        final String accountType = mCursor.getString(PickRawContactLoader.ACCOUNT_TYPE);
+        final String dataSet = mCursor.getString(PickRawContactLoader.DATA_SET);
+        final AccountType account = AccountTypeManager.getInstance(this)
+                .getAccountType(accountType, dataSet);
+        return account.areContactsWritable();
+    }
+
+    /**
+     * Returns an intent to load the editor for the given raw contact. Sets
+     * {@code FLAG_ACTIVITY_FORWARD_RESULT} in case the activity that started us expects a result.
+     * @param rawContactId Raw contact to edit
+     */
+    private Intent getIntentForRawContact(long rawContactId) {
+        final Intent intent = EditorIntents.createEditContactIntentForRawContact(
+                this, mUri, rawContactId, mMaterialPalette);
+        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+        return intent;
+    }
+}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index e4dc56d..b2a0a07 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -27,6 +27,8 @@
 import android.content.Intent;
 import android.content.SyncStatusObserver;
 import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -34,6 +36,7 @@
 import android.provider.ContactsContract.ProviderStatus;
 import android.support.design.widget.CoordinatorLayout;
 import android.support.design.widget.Snackbar;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.content.LocalBroadcastManager;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.SwipeRefreshLayout;
@@ -384,10 +387,7 @@
 
         setUpAllFragment(fragmentManager);
 
-        if (isGroupView()) {
-            mMembersFragment = (GroupMembersFragment)
-                    fragmentManager.findFragmentByTag(TAG_GROUP_VIEW);
-        }
+        mMembersFragment = (GroupMembersFragment) fragmentManager.findFragmentByTag(TAG_GROUP_VIEW);
 
         // Configure floating action button
         mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
@@ -692,11 +692,13 @@
     }
 
     private boolean isAllFragmentInSelectionMode() {
-        return mAllFragment.getActionBarAdapter().isSelectionMode();
+        return mAllFragment.getActionBarAdapter() != null
+                && mAllFragment.getActionBarAdapter().isSelectionMode();
     }
 
     private boolean isAllFragmentInSearchMode() {
-        return mAllFragment.getActionBarAdapter().isSearchMode();
+        return mAllFragment.getActionBarAdapter() != null
+                && mAllFragment.getActionBarAdapter().isSearchMode();
     }
 
     @Override
@@ -758,10 +760,11 @@
     }
 
     private void switchToOrUpdateGroupView(String action) {
-        final boolean shouldUpdate = mMembersFragment != null;
-        switchView(ContactsView.GROUP_VIEW);
-        if (shouldUpdate) {
+        // If group fragment is active and visible, we simply update it.
+        if (mMembersFragment != null && !mMembersFragment.isInactive()) {
             mMembersFragment.updateExistingGroupFragment(mGroupUri, action);
+        } else {
+            switchView(ContactsView.GROUP_VIEW);
         }
     }
 
@@ -782,14 +785,21 @@
             transaction.replace(
                     R.id.contacts_list_container, mMembersFragment, TAG_GROUP_VIEW);
         } else if (isDuplicatesView()) {
-            final Fragment duplicatesFragment = ObjectFactory.getDuplicatesFragment();
-            final Fragment duplicatesUtilFragment = ObjectFactory.getDuplicatesUtilFragment();
-            if (duplicatesFragment != null && duplicatesUtilFragment != null) {
+            Fragment duplicatesFragment = fragmentManager.findFragmentByTag(TAG_DUPLICATES);
+            Fragment duplicatesUtilFragment =
+                    fragmentManager.findFragmentByTag(TAG_DUPLICATES_UTIL);
+            if (duplicatesFragment == null || duplicatesUtilFragment == null) {
+                duplicatesFragment = ObjectFactory.getDuplicatesFragment();
+                duplicatesUtilFragment = ObjectFactory.getDuplicatesUtilFragment();
                 duplicatesUtilFragment.setTargetFragment(duplicatesFragment, /* requestCode */ 0);
-                transaction.replace(
-                        R.id.contacts_list_container, duplicatesFragment, TAG_DUPLICATES);
-                transaction.add(duplicatesUtilFragment, TAG_DUPLICATES_UTIL);
             }
+            transaction.replace(
+                    R.id.contacts_list_container, duplicatesFragment, TAG_DUPLICATES);
+            if (!duplicatesUtilFragment.isAdded()) {
+                transaction.add(duplicatesUtilFragment, TAG_DUPLICATES_UTIL);
+                resetToolBarStatusBarColor();
+            }
+            resetToolBarStatusBarColor();
         }
         transaction.addToBackStack(TAG_SECOND_LEVEL);
         transaction.commit();
@@ -806,6 +816,7 @@
         mShouldSwitchToAllContacts = false;
         mCurrentView = ContactsView.ALL_CONTACTS;
         showFabWithAnimation(/* showFab */ true);
+        mAllFragment.scrollToTop();
 
         super.switchToAllContacts();
     }
@@ -814,6 +825,14 @@
         getFragmentManager().popBackStackImmediate(
                 TAG_SECOND_LEVEL, FragmentManager.POP_BACK_STACK_INCLUSIVE);
         mMembersFragment = null;
+        resetToolBarStatusBarColor();
+    }
+
+    // Reset toolbar and status bar color to Contacts theme color.
+    private void resetToolBarStatusBarColor() {
+        findViewById(R.id.toolbar_frame).setBackgroundColor(
+                ContextCompat.getColor(this, R.color.primary_color));
+        updateStatusBarBackground(ContextCompat.getColor(this, R.color.primary_color_dark));
     }
 
     @Override
diff --git a/src/com/android/contacts/common/util/MaterialColorMapUtils.java b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
index a8fbf42..50d8f0f 100644
--- a/src/com/android/contacts/common/util/MaterialColorMapUtils.java
+++ b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
@@ -16,13 +16,16 @@
 
 package com.android.contacts.common.util;
 
-import com.android.contacts.common.R;
+import com.android.contacts.ContactsDrawerActivity;
+import com.android.contacts.R;
 
+import android.app.Activity;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
+import android.support.v4.content.ContextCompat;
 
 public class MaterialColorMapUtils {
     private final TypedArray sPrimaryColors;
@@ -177,4 +180,26 @@
 
         return H;
     }
+
+    /**
+     * Returns status bar color for group view and non-group views.
+     */
+    public static int getStatusBarColor(Activity activity) {
+        final boolean isGroupView = activity instanceof ContactsDrawerActivity
+                && ((ContactsDrawerActivity) activity).isGroupView();
+        return isGroupView
+                ? ContextCompat.getColor(activity, R.color.group_primary_color_dark)
+                : ContextCompat.getColor(activity, R.color.primary_color_dark);
+    }
+
+    /**
+     * Returns toolbar color for group view and non-group views.
+     */
+    public static int getToolBarColor(Activity activity) {
+        final boolean isGroupView = activity instanceof ContactsDrawerActivity
+                && ((ContactsDrawerActivity) activity).isGroupView();
+        return isGroupView
+                ? ContextCompat.getColor(activity, R.color.group_primary_color)
+                : ContextCompat.getColor(activity, R.color.primary_color);
+    }
 }
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 80e1b82..0542436 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -203,6 +203,13 @@
     public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
 
     /**
+     * Intent key to pass the ID of the raw contact id that should be displayed in the full editor
+     * by itself.
+     */
+    public static final String INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE =
+            "raw_contact_id_to_display_alone";
+
+    /**
      * Intent extra to specify a {@link ContactEditor.SaveMode}.
      */
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
@@ -1478,6 +1485,8 @@
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
             }
+            mRawContactIdToDisplayAlone = mIntentExtras
+                    .getLong(INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE);
         }
     }
 
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index c4f48e6..c903b84 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 
 import com.android.contacts.activities.ContactEditorActivity;
+import com.android.contacts.activities.ContactEditorSpringBoardActivity;
 import com.android.contacts.common.model.RawContactDeltaList;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 
@@ -38,19 +39,32 @@
     }
 
     /**
-     * Returns an Intent to start the {@link ContactEditorActivity} for an
+     * Returns an Intent to start the {@link ContactEditorSpringBoardActivity} for an
      * existing contact.
      */
-    public static Intent createEditContactIntent(Context context, Uri contactLookupUri,
+    public static Intent createEditContactIntent(Context context, Uri uri,
             MaterialPalette materialPalette, long photoId) {
-        final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri, context,
-                ContactEditorActivity.class);
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
+                ContactEditorSpringBoardActivity.class);
         putMaterialPalette(intent, materialPalette);
         putPhotoId(intent, photoId);
         return intent;
     }
 
     /**
+     * Returns an Intent to start the {@link ContactEditorActivity} for the given raw contact.
+     */
+    public static Intent createEditContactIntentForRawContact(Context context,
+            Uri uri, long rawContactId, MaterialPalette materialPalette) {
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
+                ContactEditorActivity.class);
+        intent.putExtra(ContactEditorFragment.INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE,
+                rawContactId);
+        putMaterialPalette(intent, materialPalette);
+        return intent;
+    }
+
+    /**
      * Returns an Intent to start the {@link ContactEditorActivity} for a new contact with
      * the field values specified by rawContactDeltaList pre-populate in the form.
      */
@@ -71,9 +85,9 @@
      * Returns an Intent to edit a different contact in the editor with whatever
      * values were already entered on the current editor.
      */
-    public static Intent createEditOtherContactIntent(Context context, Uri contactLookupUri,
+    public static Intent createEditOtherContactIntent(Context context, Uri uri,
             ArrayList<ContentValues> contentValues) {
-        final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri, context,
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
                 ContactEditorActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
diff --git a/src/com/android/contacts/editor/PickRawContactDialogFragment.java b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
new file mode 100644
index 0000000..20e8f35
--- /dev/null
+++ b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
@@ -0,0 +1,162 @@
+package com.android.contacts.editor;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+
+/**
+ * Dialog containing the raw contacts that make up a contact. On selection the editor is loaded
+ * for the chosen raw contact.
+ */
+public class PickRawContactDialogFragment extends DialogFragment {
+    /**
+     * Used to list the account info for the given raw contacts list.
+     */
+    private static final class RawContactAccountListAdapter extends CursorAdapter {
+        private final LayoutInflater mInflater;
+        private final Context mContext;
+
+        public RawContactAccountListAdapter(Context context, Cursor cursor) {
+            super(context, cursor, 0);
+            mContext = context;
+            mInflater = LayoutInflater.from(context);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            final long rawContactId = cursor.getLong(PickRawContactLoader.RAW_CONTACT_ID);
+            final String accountName = cursor.getString(PickRawContactLoader.ACCOUNT_NAME);
+            final String accountType = cursor.getString(PickRawContactLoader.ACCOUNT_TYPE);
+            final String dataSet = cursor.getString(PickRawContactLoader.DATA_SET);
+            final AccountType account = AccountTypeManager.getInstance(mContext)
+                    .getAccountType(accountType, dataSet);
+
+            final ContactsPreferences prefs = new ContactsPreferences(mContext);
+            final int displayNameColumn =
+                    prefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY
+                            ? PickRawContactLoader.DISPLAY_NAME_PRIMARY
+                            : PickRawContactLoader.DISPLAY_NAME_ALTERNATIVE;
+            String displayName = cursor.getString(displayNameColumn);
+
+            final TextView nameView = (TextView) view.findViewById(
+                    R.id.display_name);
+            final TextView accountTextView = (TextView) view.findViewById(
+                    R.id.account_name);
+            final ImageView accountIconView = (ImageView) view.findViewById(
+                    R.id.account_icon);
+
+            if (!account.areContactsWritable()) {
+                displayName = mContext
+                        .getString(R.string.contact_editor_pick_raw_contact_read_only, displayName);
+                view.setAlpha(.38f);
+            } else {
+                view.setAlpha(1f);
+            }
+
+            nameView.setText(displayName);
+            accountTextView.setText(accountName);
+            accountIconView.setImageDrawable(account.getDisplayIcon(mContext));
+
+            final ContactPhotoManager.DefaultImageRequest
+                    request = new ContactPhotoManager.DefaultImageRequest(
+                    displayName, String.valueOf(rawContactId), /* isCircular = */ true);
+            final ImageView photoView = (ImageView) view.findViewById(
+                    R.id.photo);
+            ContactPhotoManager.getInstance(mContext).loadDirectoryPhoto(photoView,
+                    ContactPhotoManager.getDefaultAvatarUriForContact(request),
+                    /* darkTheme = */ false,
+                    /* isCircular = */ true,
+                    request);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            return mInflater.inflate(R.layout.raw_contact_list_item, parent, false);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            getCursor().moveToPosition(position);
+            return getCursor().getLong(PickRawContactLoader.RAW_CONTACT_ID);
+        }
+    }
+
+    // Cursor holding all raw contact rows for the given Contact.
+    private Cursor mCursor;
+    // Uri for the whole Contact.
+    private Uri mUri;
+    private MaterialPalette mMaterialPalette;
+
+    public static PickRawContactDialogFragment getInstance(Uri uri, Cursor cursor,
+            MaterialPalette materialPalette) {
+        final PickRawContactDialogFragment fragment = new PickRawContactDialogFragment();
+        fragment.setUri(uri);
+        fragment.setCursor(cursor);
+        fragment.setMaterialPalette(materialPalette);
+        return fragment;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        final CursorAdapter adapter = new RawContactAccountListAdapter(getContext(), mCursor);
+        builder.setTitle(R.string.contact_editor_pick_raw_contact_dialog_title);
+        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final long rawContactId = adapter.getItemId(which);
+                final Intent intent = EditorIntents.createEditContactIntentForRawContact(
+                        getActivity(), mUri, rawContactId, mMaterialPalette);
+                intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+                ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
+            }
+        });
+        builder.setCancelable(true);
+        return builder.create();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        mCursor = null;
+        finishActivity();
+    }
+
+    private void setUri(Uri uri) {
+        mUri = uri;
+    }
+
+    private void setCursor(Cursor cursor) {
+        mCursor = cursor;
+    }
+
+    private void setMaterialPalette(MaterialPalette materialPalette) {
+        mMaterialPalette = materialPalette;
+    }
+
+    private void finishActivity() {
+        if (getActivity() != null && !getActivity().isFinishing()) {
+            getActivity().finish();
+        }
+    }
+}
diff --git a/src/com/android/contacts/editor/PickRawContactLoader.java b/src/com/android/contacts/editor/PickRawContactLoader.java
new file mode 100644
index 0000000..62be517
--- /dev/null
+++ b/src/com/android/contacts/editor/PickRawContactLoader.java
@@ -0,0 +1,78 @@
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+
+/**
+ * Loader for the pick a raw contact to edit activity. Loads all raw contact metadata for the
+ * given Contact {@link Uri}.
+ */
+public class PickRawContactLoader extends CursorLoader {
+    private Uri mContactUri;
+
+    public static final String[] COLUMNS = new String[] {
+            RawContacts.ACCOUNT_NAME,
+            RawContacts.ACCOUNT_TYPE,
+            RawContacts.DATA_SET,
+            RawContacts._ID,
+            RawContacts.DISPLAY_NAME_PRIMARY,
+            RawContacts.DISPLAY_NAME_ALTERNATIVE
+    };
+
+    public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
+
+    public static final int ACCOUNT_NAME = 0;
+    public static final int ACCOUNT_TYPE = 1;
+    public static final int DATA_SET = 2;
+    public static final int RAW_CONTACT_ID = 3;
+    public static final int DISPLAY_NAME_PRIMARY = 4;
+    public static final int DISPLAY_NAME_ALTERNATIVE = 5;
+
+    public PickRawContactLoader(Context context, Uri contactUri) {
+        super(context, ensureIsContactUri(contactUri), COLUMNS, SELECTION, null, RawContacts._ID);
+        mContactUri = contactUri;
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // Get the id of the contact we're looking at.
+        final Cursor cursor = getContext().getContentResolver()
+                .query(mContactUri, new String[] { Contacts._ID }, null,
+                null, null);
+
+        if (cursor == null) {
+            return null;
+        }
+
+        if (cursor.getCount() < 1) {
+            cursor.close();
+            return null;
+        }
+
+        cursor.moveToFirst();
+        final long contactId = cursor.getLong(0);
+        cursor.close();
+        // Update selection arguments and uri.
+        setSelectionArgs(new String[]{ Long.toString(contactId) });
+        setUri(RawContacts.CONTENT_URI);
+        return super.loadInBackground();
+    }
+
+    /**
+     * Ensures that this is a valid contact URI. If invalid, then an exception is
+     * thrown. Otherwise, the original URI is returned.
+     */
+    private static Uri ensureIsContactUri(final Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri must not be null");
+        }
+        if (!uri.toString().startsWith(Contacts.CONTENT_URI.toString())) {
+            throw new IllegalArgumentException("Invalid contact Uri: " + uri);
+        }
+        return uri;
+    }
+}
diff --git a/src/com/android/contacts/group/GroupMembersFragment.java b/src/com/android/contacts/group/GroupMembersFragment.java
index 809cf20..30f339c 100644
--- a/src/com/android/contacts/group/GroupMembersFragment.java
+++ b/src/com/android/contacts/group/GroupMembersFragment.java
@@ -24,8 +24,9 @@
 import android.database.CursorWrapper;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.provider.ContactsContract.Contacts;
-import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -78,7 +79,7 @@
     private static final String ARG_GROUP_URI = "groupUri";
 
     private static final int LOADER_GROUP_METADATA = 0;
-
+    private static final int MSG_FAIL_TO_LOAD = 1;
     private static final int RESULT_GROUP_ADD_MEMBER = 100;
 
     /** Filters out duplicate contacts. */
@@ -194,9 +195,7 @@
                 Log.e(TAG, "Failed to load group metadata for " + mGroupUri);
                 Toast.makeText(getContext(), R.string.groupLoadErrorToast, Toast.LENGTH_SHORT)
                         .show();
-                // TODO: we probably shouldn't finish mActivity.
-                mActivity.setResult(AppCompatActivity.RESULT_CANCELED);
-                mActivity.finish();
+                mHandler.sendEmptyMessage(MSG_FAIL_TO_LOAD);
                 return;
             }
             mGroupMetaData = new GroupMetaData(getActivity(), cursor);
@@ -219,6 +218,15 @@
 
     private Set<String> mGroupMemberContactIds = new HashSet();
 
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if(msg.what == MSG_FAIL_TO_LOAD) {
+                mActivity.onBackPressed();
+            }
+        }
+    };
+
     public static GroupMembersFragment newInstance(Uri groupUri) {
         final Bundle args = new Bundle();
         args.putParcelable(ARG_GROUP_URI, groupUri);
@@ -646,6 +654,13 @@
         return mGroupMetaData != null && mGroupMetaData.groupId == groupId;
     }
 
+    /**
+     * Return true if the fragment is not yet added, being removed, or detached.
+     */
+    public boolean isInactive() {
+        return !isAdded() || isRemoving() || isDetached();
+    }
+
     @Override
     public void onDestroy() {
         if (mActionBarAdapter != null) {
diff --git a/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java b/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
index a196ffa..8364b7b 100644
--- a/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
+++ b/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
@@ -3,6 +3,7 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -75,6 +76,12 @@
 
         device.wait(Until.hasObject(By.textEndsWith("make and manage phone calls?")), TIMEOUT);
 
+        final PackageManager packageManager = mTargetContext.getPackageManager();
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            device.waitForIdle();
+            return;
+        }
+
         final UiObject2 grantPhonePermissionButton = device.findObject(By.text("ALLOW"));
 
         grantPhonePermissionButton.clickAndWait(Until.newWindow(), TIMEOUT);