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

Commit 89d34862 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 25180 into eclair

* changes:
  Prevent unstateful edits, empty trimming, INSERT edge cases.
parents 815bb3ba 7f86847e
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -115,6 +115,11 @@ public class ContactsSource {
        return mInflatedLevel >= inflateLevel;
    }

    /** @hide exposed for unit tests */
    public void setInflatedLevel(int inflateLevel) {
        mInflatedLevel = inflateLevel;
    }

    /**
     * Ensure that the constraint rules behind this {@link ContactsSource} have
     * been inflated. Because this may involve parsing meta-data from
+6 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.contacts.model;

import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditField;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -31,6 +33,8 @@ import android.os.Parcelable;
import android.provider.BaseColumns;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;

import java.util.ArrayList;
@@ -294,6 +298,7 @@ public class EntityDelta implements Parcelable {
            // Assert version is consistent while persisting changes
            final Long beforeId = mValues.getId();
            final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION);
            if (beforeId == null || beforeVersion == null) return;

            final ContentProviderOperation.Builder builder = ContentProviderOperation
                    .newAssertQuery(RawContacts.CONTENT_URI);
@@ -525,7 +530,7 @@ public class EntityDelta implements Parcelable {

        public boolean isUpdate() {
            // When "after" has some changes, action is "update"
            return beforeExists() && (mAfter.size() > 0);
            return beforeExists() && (mAfter != null && mAfter.size() > 0);
        }

        public boolean isInsert() {
+43 −7
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
@@ -338,13 +340,30 @@ public class EntityModifier {
        return child;
    }

    /**
     * Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
     * from the given {@link EntitySet}, assuming the given {@link Sources}
     * dictates the structure for various fields. This method ignores rows not
     * described by the {@link ContactsSource}.
     */
    public static void trimEmpty(EntitySet set, Sources sources) {
        for (EntityDelta state : set) {
            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
            final ContactsSource source = sources.getInflatedSource(accountType,
                    ContactsSource.LEVEL_MIMETYPES);
            trimEmpty(state, source);
        }
    }

    /**
     * Processing to trim any empty {@link ValuesDelta} rows from the given
     * {@link EntityDelta}, assuming the given {@link ContactsSource} dictates
     * the structure for various fields. This method ignores rows not described
     * by the {@link ContactsSource}.
     */
    public static void trimEmpty(ContactsSource source, EntityDelta state) {
    public static void trimEmpty(EntityDelta state, ContactsSource source) {
        boolean hasValues = false;

        // Walk through entries for each well-known kind
        for (DataKind kind : source.getSortedDataKinds()) {
            final String mimeType = kind.mimeType;
@@ -352,14 +371,27 @@ public class EntityModifier {
            if (entries == null) continue;

            for (ValuesDelta entry : entries) {
                // Test and remove this row if empty
                // Skip any values that haven't been touched
                final boolean touched = entry.isInsert() || entry.isUpdate();
                if (touched && EntityModifier.isEmpty(entry, kind)) {
                if (!touched) {
                    hasValues = true;
                    continue;
                }

                // Test and remove this row if empty
                if (EntityModifier.isEmpty(entry, kind)) {
                    // TODO: remove this verbose logging
                    Log.w(TAG, "Trimming: " + entry.toString());
                    entry.markDeleted();
                } else {
                    hasValues = true;
                }
            }
        }

        if (!hasValues) {
            // Trim overall entity if no children exist
            state.markDeleted();
        }
    }

@@ -454,7 +486,10 @@ public class EntityModifier {
     */
    public static void parseExtras(EntityDelta state, DataKind kind, Bundle extras,
            String typeExtra, String valueExtra, String valueColumn) {
        final String value = extras.getString(valueExtra);
        final CharSequence value = extras.getCharSequence(valueExtra);

        // Bail early if source doesn't handle this type
        if (kind == null) return;

        // Bail when can't insert type, or value missing
        final boolean canInsert = EntityModifier.canInsert(state, kind);
@@ -462,16 +497,17 @@ public class EntityModifier {
        if (!validValue || !canInsert) return;

        // Find exact type, or otherwise best type
        final int typeValue = extras.getInt(typeExtra, Integer.MIN_VALUE);
        final int typeValue = extras.getInt(typeExtra, BaseTypes.TYPE_CUSTOM);
        final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);

        // Create data row and fill with value
        final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
        child.put(valueColumn, value);
        child.put(valueColumn, value.toString());

        if (editType != null && editType.customColumn != null) {
            // Write down label when custom type picked
            child.put(editType.customColumn, extras.getString(typeExtra));
            final CharSequence customType = extras.getCharSequence(typeExtra);
            child.put(editType.customColumn, customType.toString());
        }
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -66,14 +66,20 @@ public class Sources {
    }

    /**
     * Internal constructor that only performs initial parsing. Obtain a
     * {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE}.
     * Internal constructor that only performs initial parsing.
     */
    private Sources(Context context) {
        mContext = context;
        loadAccounts();
    }

    /** @hide exposed for unit tests */
    public Sources(ContactsSource... sources) {
        for (ContactsSource source : sources) {
            mSources.put(source.accountType, source);
        }
    }

    /**
     * Blocking call to load all {@link AuthenticatorDescription} known by the
     * {@link AccountManager} on the system.
+63 −36
Original line number Diff line number Diff line
@@ -62,7 +62,6 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts.Data;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -90,19 +89,9 @@ public final class EditContactActivity extends Activity implements View.OnClickL
    /** The launch code when picking a photo and the raw data is returned */
    private static final int PHOTO_PICKED_WITH_DATA = 3021;

    private static final int TOKEN_ENTITY = 41;

    private static final String KEY_EDIT_STATE = "state";
    private static final String KEY_SELECTED_RAW_CONTACT = "selected";

//    private static final String KEY_SELECTED_TAB_ID = "tabId";
//    private static final String KEY_CONTACT_ID = "contactId";

//    private int mSelectedTab = -1;

//    private long mSelectedRawContactId = -1;
//    private long mContactId = -1;

    private String mQuerySelection;

    private ScrollingTabWidget mTabWidget;
@@ -188,6 +177,20 @@ public final class EditContactActivity extends Activity implements View.OnClickL

            target.mQuerySelection = selection;
            target.mState = EntitySet.fromQuery(resolver, selection, null, null);

            // Handle any incoming values that should be inserted
            final Bundle extras = intent.getExtras();
            final boolean hasExtras = extras.size() > 0;
            final boolean hasState = target.mState.size() > 0;
            if (hasExtras && hasState) {
                // Find source defining the first RawContact found
                final EntityDelta state = target.mState.get(0);
                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
                final ContactsSource source = sources.getInflatedSource(accountType,
                        ContactsSource.LEVEL_CONSTRAINTS);
                EntityModifier.parseExtras(context, source, state, extras);
            }

            return null;
        }

@@ -203,11 +206,11 @@ public final class EditContactActivity extends Activity implements View.OnClickL

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (hasValidState()) {
            // Store entities with modifications
            outState.putParcelable(KEY_EDIT_STATE, mState);
            outState.putLong(KEY_SELECTED_RAW_CONTACT, getSelectedRawContactId());
//        outState.putLong(KEY_SELECTED_TAB_ID, mSelectedRawContactId);
//        outState.putLong(KEY_CONTACT_ID, mContactId);
        }

        super.onSaveInstanceState(outState);
    }
@@ -217,16 +220,13 @@ public final class EditContactActivity extends Activity implements View.OnClickL
        // Read modifications from instance
        mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE);

//        mSelectedRawContactId = savedInstanceState.getLong(KEY_SELECTED_TAB_ID);
//        mContactId = savedInstanceState.getLong(KEY_CONTACT_ID);

        Log.d(TAG, "onrestoreinstancestate");

        bindTabs();
        bindHeader();

        if (hasValidState()) {
            final long selectedId = savedInstanceState.getLong(KEY_SELECTED_RAW_CONTACT);
            setSelectedRawContactId(selectedId);
        }

        // Restore selected tab and any focus
        super.onRestoreInstanceState(savedInstanceState);
@@ -248,6 +248,14 @@ public final class EditContactActivity extends Activity implements View.OnClickL
        mTabWidget.setCurrentTab(index);
    }

    /**
     * Check if our internal {@link #mState} is valid, usually checked before
     * performing user actions.
     */
    protected boolean hasValidState() {
        return mState != null && mState.size() > 0;
    }



    /**
@@ -256,6 +264,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * {@link RawContacts}.
     */
    protected void bindTabs() {
        if (!hasValidState()) return;

        final Sources sources = Sources.getInstance(this);
        int selectedTab = 0;

@@ -287,6 +297,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * primary {@link Data} change.
     */
    protected void bindHeader() {
        if (!hasValidState()) return;

        // TODO: rebuild header widget based on internal entities

        // TODO: fill header bar with newly parsed data for speed
@@ -304,8 +316,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL

    /** {@inheritDoc} */
    public void onTabSelectionChanged(int tabIndex, boolean clicked) {
        boolean validTab = mState != null && tabIndex >= 0 && tabIndex < mState.size();
        if (!validTab) return;
        if (!hasValidState()) return;
        if (tabIndex < 0 || tabIndex >= mState.size()) return;

        // Find entity and source for selected tab
        final EntityDelta entity = mState.get(tabIndex);
@@ -321,11 +333,13 @@ public final class EditContactActivity extends Activity implements View.OnClickL

    /** {@inheritDoc} */
    public void onDisplayNameLongClick(View view) {
        if (!hasValidState()) return;
        this.createNameDialog().show();
    }

    /** {@inheritDoc} */
    public void onPhotoLongClick(View view) {
        if (!hasValidState()) return;
        this.createPhotoDialog().show();
    }

@@ -347,12 +361,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL

    /** {@inheritDoc} */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                return doSaveAction();
        }
        return super.onKeyDown(keyCode, event);
    public void onBackPressed() {
        doSaveAction();
    }

    /** {@inheritDoc} */
@@ -456,11 +466,18 @@ public final class EditContactActivity extends Activity implements View.OnClickL
        /** {@inheritDoc} */
        @Override
        protected Integer doInBackground(EditContactActivity target, EntitySet... params) {
            final ContentResolver resolver = target.getContentResolver();
            final Context context = target;
            final ContentResolver resolver = context.getContentResolver();

            EntitySet state = params[0];

            // Trim any empty fields, and RawContacts, before persisting
            final Sources sources = Sources.getInstance(context);
            EntityModifier.trimEmpty(state, sources);

            // Attempt to persist changes
            int tries = 0;
            Integer result = RESULT_FAILURE;
            EntitySet state = params[0];
            while (tries < PERSIST_TRIES) {
                try {
                    // Build operations and try applying
@@ -515,8 +532,7 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * finishes the activity.
     */
    private boolean doSaveAction() {
        // Bail early if nothing to save
        if (mState == null || mState.size() == 0) return true;
        if (!hasValidState()) return false;

        // Pass back last-selected contact
        final int selectedTab = mTabWidget.getCurrentTab();
@@ -558,6 +574,7 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * {@link EntityDelta} under the currently edited {@link Contacts}.
     */
    private boolean doAddAction() {
        // Adding is okay when missing state
        new AddContactTask(this).execute();
        return true;
    }
@@ -567,6 +584,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * user confirmation before continuing.
     */
    private boolean doDeleteAction() {
        if (!hasValidState()) return false;

        this.createDeleteDialog().show();
        return true;
    }
@@ -575,6 +594,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * Pick a specific photo to be added under the currently selected tab.
     */
    private boolean doPickPhotoAction() {
        if (!hasValidState()) return false;

        try {
            // Launch picker to choose photo for selected contact
            final Intent intent = ContactsUtils.getPhotoPickIntent();
@@ -589,6 +610,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
     * Clear any existing photo under the currently selected tab.
     */
    public boolean doRemovePhotoAction() {
        if (!hasValidState()) return false;

        // Remove photo from selected contact
        mEditor.setPhotoBitmap(null);
        return true;
@@ -601,6 +624,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL

    /** {@inheritDoc} */
    public void onRequest(int request) {
        if (!hasValidState()) return;

        switch (request) {
            case EditorListener.REQUEST_PICK_PHOTO: {
                doPickPhotoAction();
@@ -704,7 +729,7 @@ public final class EditContactActivity extends Activity implements View.OnClickL
            final DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener() {
                public void onCancel(DialogInterface dialog) {
                    // If nothing remains, close activity
                    if (target.mState == null || target.mState.size() == 0) {
                    if (!target.hasValidState()) {
                        target.finish();
                    }
                }
@@ -739,6 +764,8 @@ public final class EditContactActivity extends Activity implements View.OnClickL
                delta.markDeleted();

                // TODO: trigger task to update tabs (doesnt need to be background)
                bindTabs();
                bindHeader();
            }
        });
        builder.setNegativeButton(android.R.string.cancel, null);
Loading