Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b9065ddb authored by Gary Mai's avatar Gary Mai
Browse files

Implement linked contact menu option

Remove unlink option from QuickContact.
Link now appears when the contact has only one raw contact.
Otherwise a new "View linked contacts" menu option appears.
This dialog shows all linked contacts (including read-only) and
has buttons for adding another contact or unlinking all of them.
Show progress dialogs for each of these actions.

Test: Manually verified:
  * View linked contacts only appears when there are more than one
    raw contacts
  * Link appears otherwise
  * Pressing add goes to the contact picker and correctly joins the
    selected contact
  * Pressing unlink opens the confirmation dialog
  * Canceling or dismissing the confirmation closes everything
  * Unlinking from the confirmation does the unlink
  * Confirmed the above with rotating on each dialog and during the
    progress dialogs

Bug: 32707898
Change-Id: I39435a07fefce4276e34ba302001ff3dab352516
parent 0aad0ed8
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -366,7 +366,6 @@

         <activity
            android:name=".activities.ContactEditorSpringBoardActivity"
            android:noHistory="true"
            android:theme="@style/TransparentThemeAppCompat">

             <intent-filter>
+4 −4
Original line number Diff line number Diff line
@@ -25,14 +25,14 @@
        android:id="@+id/menu_edit"
        android:showAsAction="always" />

    <item
        android:id="@+id/menu_split"
        android:title="@string/menu_splitAggregate" />

    <item
        android:id="@+id/menu_join"
        android:title="@string/menu_joinAggregate" />

    <item
        android:id="@+id/menu_linked_contacts"
        android:title="@string/menu_linkedContacts" />

    <item
        android:id="@+id/menu_delete"
        android:title="@string/menu_deleteContact" />
+9 −1
Original line number Diff line number Diff line
@@ -147,6 +147,14 @@
    <!-- Positive button text from the confirmation dialog for joining contacts when there are unsaved changes. [CHAR LIMIT = 60] -->
    <string name="joinConfirmation_positive_button">Save and Link</string>

    <!-- The text to show on on a ProgressDialog indicating we're currently linking
         contacts [CHAR LIMIT=20]-->
    <string name="contacts_linking_progress_bar">Linking</string>

    <!-- The text to show on on a ProgressDialog indicating we're currently unlinking
     contacts [CHAR LIMIT=20]-->
    <string name="contacts_unlinking_progress_bar">Unlinking</string>

    <!-- Menu item that links an aggregate with another aggregate -->
    <string name="menu_joinAggregate">Link</string>

@@ -746,7 +754,7 @@
    <!-- Button label to prompt the user to add another account (when there are already existing accounts on the device) [CHAR LIMIT=30] -->
    <string name="add_new_account">Add new account</string>

    <!-- Menu item shown only when the special debug mode is enabled, which is used to send all contacts database files via email.  [CHAR LIMI=NONE] -->
    <!-- Menu item shown only when the special debug mode is enabled, which is used to send all contacts database files via email.  [CHAR LIMIT=NONE] -->
    <string name="menu_export_database">Export database files</string>

    <!-- Content description for the button that adds a new contact
+39 −11
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.contacts;

import static android.Manifest.permission.WRITE_CONTACTS;

import android.app.Activity;
import android.app.IntentService;
import android.content.ContentProviderOperation;
@@ -45,8 +47,6 @@ import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.os.ResultReceiver;
import android.telephony.SubscriptionInfo;
@@ -72,6 +72,7 @@ import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.compat.PinnedPositionsCompat;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contactsbind.FeedbackHelper;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

@@ -81,8 +82,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import static android.Manifest.permission.WRITE_CONTACTS;

/**
 * A service responsible for saving changes to the content provider.
 */
@@ -133,6 +132,7 @@ public class ContactSaveService extends IntentService {
    public static final String EXTRA_DATA_ID = "dataId";

    public static final String ACTION_SPLIT_CONTACT = "splitContact";
    public static final String EXTRA_HARD_SPLIT = "extraHardSplit";

    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
    public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts";
@@ -159,6 +159,8 @@ public class ContactSaveService extends IntentService {

    public static final String BROADCAST_GROUP_DELETED = "groupDeleted";
    public static final String BROADCAST_SIM_IMPORT_COMPLETE = "simImportComplete";
    public static final String BROADCAST_LINK_COMPLETE = "linkComplete";
    public static final String BROADCAST_UNLINK_COMPLETE = "unlinkComplete";

    public static final String BROADCAST_SERVICE_STATE_CHANGED = "serviceStateChanged";

@@ -1244,7 +1246,7 @@ public class ContactSaveService extends IntentService {

    /**
     * Creates an intent that can be sent to this service to split a contact into it's constituent
     * pieces. This will set the raw contact ids to TYPE_AUTOMATIC for AggregationExceptions so
     * pieces. This will set the raw contact ids to {@link AggregationExceptions#TYPE_AUTOMATIC} so
     * they may be re-merged by the auto-aggregator.
     */
    public static Intent createSplitContactIntent(Context context, long[][] rawContactIds,
@@ -1256,10 +1258,24 @@ public class ContactSaveService extends IntentService {
        return serviceIntent;
    }

    /**
     * Creates an intent that can be sent to this service to split a contact into it's constituent
     * pieces. This will explicitly set the raw contact ids to
     * {@link AggregationExceptions#TYPE_KEEP_SEPARATE}.
     */
    public static Intent createHardSplitContactIntent(Context context, long[][] rawContactIds) {
        final Intent serviceIntent = new Intent(context, ContactSaveService.class);
        serviceIntent.setAction(ContactSaveService.ACTION_SPLIT_CONTACT);
        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACT_IDS, rawContactIds);
        serviceIntent.putExtra(ContactSaveService.EXTRA_HARD_SPLIT, true);
        return serviceIntent;
    }

    private void splitContact(Intent intent) {
        final long rawContactIds[][] = (long[][]) intent
                .getSerializableExtra(EXTRA_RAW_CONTACT_IDS);
        final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
        final boolean hardSplit = intent.getBooleanExtra(EXTRA_HARD_SPLIT, false);
        if (rawContactIds == null) {
            Log.e(TAG, "Invalid argument for splitContact request");
            if (receiver != null) {
@@ -1273,7 +1289,8 @@ public class ContactSaveService extends IntentService {
        for (int i = 0; i < rawContactIds.length; i++) {
            for (int j = 0; j < rawContactIds.length; j++) {
                if (i != j) {
                    if (!buildSplitTwoContacts(operations, rawContactIds[i], rawContactIds[j])) {
                    if (!buildSplitTwoContacts(operations, rawContactIds[i], rawContactIds[j],
                            hardSplit)) {
                        if (receiver != null) {
                            receiver.send(CP2_ERROR, new Bundle());
                            return;
@@ -1288,6 +1305,8 @@ public class ContactSaveService extends IntentService {
            }
            return;
        }
        LocalBroadcastManager.getInstance(this)
                .sendBroadcast(new Intent(BROADCAST_UNLINK_COMPLETE));
        if (receiver != null) {
            receiver.send(CONTACTS_SPLIT, new Bundle());
        } else {
@@ -1301,7 +1320,7 @@ public class ContactSaveService extends IntentService {
     * @return false if an error occurred, true otherwise.
     */
    private boolean buildSplitTwoContacts(ArrayList<ContentProviderOperation> operations,
            long[] rawContactIds1, long[] rawContactIds2) {
            long[] rawContactIds1, long[] rawContactIds2, boolean hardSplit) {
        if (rawContactIds1 == null || rawContactIds2 == null) {
            Log.e(TAG, "Invalid arguments for splitContact request");
            return false;
@@ -1312,7 +1331,7 @@ public class ContactSaveService extends IntentService {
        final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE;
        for (int i = 0; i < rawContactIds1.length; i++) {
            for (int j = 0; j < rawContactIds2.length; j++) {
                buildSplitContactDiff(operations, rawContactIds1[i], rawContactIds2[j]);
                buildSplitContactDiff(operations, rawContactIds1[i], rawContactIds2[j], hardSplit);
                // Before we get to 500 we need to flush the operations list
                if (operations.size() > 0 && operations.size() % batchSize == 0) {
                    if (!applyOperations(resolver, operations)) {
@@ -1453,6 +1472,8 @@ public class ContactSaveService extends IntentService {
                    showToast(R.string.contactsJoinedNamedMessage, name);
                }
            }
            LocalBroadcastManager.getInstance(this)
                    .sendBroadcast(new Intent(BROADCAST_LINK_COMPLETE));
        } else {
            if (receiver != null) {
                receiver.send(CP2_ERROR, new Bundle());
@@ -1591,6 +1612,8 @@ public class ContactSaveService extends IntentService {
            Uri uri = RawContacts.getContactLookupUri(resolver,
                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
            callbackIntent.setData(uri);
            LocalBroadcastManager.getInstance(this)
                    .sendBroadcast(new Intent(BROADCAST_LINK_COMPLETE));
        }
        deliverCallback(callbackIntent);
    }
@@ -1710,13 +1733,18 @@ public class ContactSaveService extends IntentService {
    }

    /**
     * Construct a {@link AggregationExceptions#TYPE_AUTOMATIC} ContentProviderOperation.
     * Construct a {@link AggregationExceptions#TYPE_AUTOMATIC} or a
     * {@link AggregationExceptions#TYPE_KEEP_SEPARATE} ContentProviderOperation if a hard split is
     * requested.
     */
    private void buildSplitContactDiff(ArrayList<ContentProviderOperation> operations,
            long rawContactId1, long rawContactId2) {
            long rawContactId1, long rawContactId2, boolean hardSplit) {
        final Builder builder =
                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_AUTOMATIC);
        builder.withValue(AggregationExceptions.TYPE,
                hardSplit
                        ? AggregationExceptions.TYPE_KEEP_SEPARATE
                        : AggregationExceptions.TYPE_AUTOMATIC);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
        operations.add(builder.build());
+72 −23
Original line number Diff line number Diff line
package com.android.contacts.activities;

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.LoaderManager;
@@ -13,6 +14,7 @@ import android.provider.ContactsContract.RawContacts;
import android.widget.Toast;

import com.android.contacts.AppCompatContactsActivity;
import com.android.contacts.ContactSaveService;
import com.android.contacts.R;
import com.android.contacts.common.activity.RequestPermissionsActivity;
import com.android.contacts.common.logging.EditorEvent;
@@ -25,15 +27,18 @@ import com.android.contacts.editor.EditorIntents;
import com.android.contacts.editor.PickRawContactDialogFragment;
import com.android.contacts.editor.PickRawContactLoader;
import com.android.contacts.editor.PickRawContactLoader.RawContactsMetadata;
import com.android.contacts.editor.SplitContactConfirmationDialogFragment;
import com.android.contacts.quickcontact.QuickContactActivity;
import com.android.contactsbind.FeedbackHelper;

/**
 * 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.
 * All intents coming out from this activity have {@code FLAG_ACTIVITY_FORWARD_RESULT} set.
 */
public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity implements
        PickRawContactDialogFragment.PickRawContactListener {
        PickRawContactDialogFragment.PickRawContactListener,
        SplitContactConfirmationDialogFragment.Listener {

    private static final String TAG = "EditorSpringBoard";
    private static final String TAG_RAW_CONTACTS_DIALOG = "rawContactsDialog";
    private static final int LOADER_RAW_CONTACTS = 1;
@@ -44,6 +49,7 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
    private RawContactsMetadata mResult;
    private MaterialPalette mMaterialPalette;
    private boolean mHasWritableAccount;
    private boolean mShowReadOnly;
    private int mWritableAccountPosition;

    /**
@@ -65,13 +71,7 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
                        return;
                    }
                    mResult = result;
                    maybeTrimReadOnly();
                    setHasWritableAccount();
                    if (mResult.rawContacts.size() > 1 && mHasWritableAccount) {
                        showDialog();
                    } else {
                        loadEditor();
                    }
                    onLoad();
                }

                @Override
@@ -103,6 +103,7 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
            mMaterialPalette = new MaterialPalette(intent.getIntExtra(primary, -1),
                    intent.getIntExtra(secondary, -1));
        }
        mShowReadOnly = intent.getBooleanExtra(EXTRA_SHOW_READ_ONLY, false);

        mUri = intent.getData();
        final String authority = mUri.getAuthority();
@@ -129,16 +130,28 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
        startEditorAndForwardExtras(getIntentForRawContact(rawContactId));
    }

    /**
     * Once the load is finished, decide whether to show the dialog or load the editor directly.
     */
    private void onLoad() {
        maybeTrimReadOnly();
        setHasWritableAccount();
        if (mShowReadOnly || (mResult.rawContacts.size() > 1 && mHasWritableAccount)) {
            showDialog();
        } else {
            loadEditor();
        }
    }

    /**
     * If not configured to show read only raw contact, trim them from the result.
     */
    private void maybeTrimReadOnly() {
        final boolean showReadOnly = getIntent().getBooleanExtra(EXTRA_SHOW_READ_ONLY, false);
        mResult.showReadOnly = showReadOnly;

        if (showReadOnly) {
        mResult.showReadOnly = mShowReadOnly;
        if (mShowReadOnly) {
            return;
        }

        mResult.trimReadOnly(AccountTypeManager.getInstance(this));
    }

@@ -147,19 +160,18 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
     */
    private void showDialog() {
        final FragmentManager fm = getFragmentManager();
        final PickRawContactDialogFragment oldFragment = (PickRawContactDialogFragment)
                fm.findFragmentByTag(TAG_RAW_CONTACTS_DIALOG);
        if (oldFragment != null && oldFragment.getDialog() != null
                && oldFragment.getDialog().isShowing()) {
        final SplitContactConfirmationDialogFragment split =
                (SplitContactConfirmationDialogFragment) fm
                        .findFragmentByTag(SplitContactConfirmationDialogFragment.TAG);
        // If we were showing the split confirmation before show it again.
        if (split != null && split.isAdded()) {
            fm.beginTransaction().show(split).commitAllowingStateLoss();
            return;
        }
        final FragmentTransaction ft = fm.beginTransaction();
        if (oldFragment != null) {
            ft.remove(oldFragment);
        }
        final PickRawContactDialogFragment newFragment = PickRawContactDialogFragment.getInstance(
        final PickRawContactDialogFragment pick = PickRawContactDialogFragment.getInstance(
                 mResult);
        ft.add(newFragment, TAG_RAW_CONTACTS_DIALOG);
        ft.add(pick, 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
@@ -185,6 +197,7 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
            intent.setClass(this, ContactEditorActivity.class);
        }
        startEditorAndForwardExtras(intent);
        finish();
    }

    /**
@@ -225,4 +238,40 @@ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity
        setResult(RESULT_CANCELED, null);
        finish();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Ignore failed requests
        if (resultCode != Activity.RESULT_OK) {
            finish();
        }
        if (data != null) {
            final Intent intent = ContactSaveService.createJoinContactsIntent(
                    this, mResult.contactId, ContentUris.parseId(data.getData()),
                    QuickContactActivity.class, Intent.ACTION_VIEW);
            startService(intent);
            finish();
        }
    }

    @Override
    public void onSplitContactConfirmed(boolean hasPendingChanges) {
        final long[][] rawContactIds = getRawContactIds();
        final Intent intent = ContactSaveService.createHardSplitContactIntent(this, rawContactIds);
        startService(intent);
        finish();
    }

    @Override
    public void onSplitContactCanceled() {
        finish();
    }

    private long[][] getRawContactIds() {
        final long[][] result = new long[mResult.rawContacts.size()][1];
        for (int i = 0; i < mResult.rawContacts.size(); i++) {
            result[i][0] = mResult.rawContacts.get(i).id;
        }
        return result;
    }
}
Loading