com.silentcircle.contacts.editor.RawContactEditorView.java Source code

Java tutorial

Introduction

Here is the source code for com.silentcircle.contacts.editor.RawContactEditorView.java

Source

/*
Copyright  2013-2014, Silent Circle, LLC.
All rights reserved.
    
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Any redistribution, use, or modification is done solely for personal 
  benefit and not for any commercial purpose or for monetary gain
* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Silent Circle nor the names of its contributors may 
  be used to endorse or promote products derived from this software 
  without specific prior written permission.
    
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
 * This  implementation is edited version of original Android sources.
 */

/*
 * Copyright (C) 2009 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.
 */

package com.silentcircle.contacts.editor;

import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.PopupMenu;

import com.actionbarsherlock.app.SherlockDialogFragment;
import com.silentcircle.contacts.GroupMetaDataLoader;
import com.silentcircle.contacts.model.RawContactDelta;
import com.silentcircle.contacts.model.RawContactModifier;
import com.silentcircle.contacts.model.account.AccountType;
import com.silentcircle.contacts.model.dataitem.DataKind;
import com.silentcircle.contacts.R;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.GroupMembership;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Organization;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Photo;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.StructuredName;
// import com.android.internal.util.Objects;

import java.util.ArrayList;

/**
 * Custom view that provides all the editor interaction for a specific
 * {link ScRawContact} represented through an {@link com.silentcircle.contacts.model.RawContactDelta}. Callers can
 * reuse this view and quickly rebuild its contents through
 * {link #setState(com.silentcircle.contacts.model.RawContactDelta, com.silentcircle.contacts.model.account.AccountType, ViewIdGenerator)}.
 * <p>
 * Internal updates are performed against {@link com.silentcircle.contacts.model.RawContactDelta.ValuesDelta} so that the
 * source {link RawContact} can be swapped out. Any state-based changes, such as
 * adding {link Data} rows or changing {@link com.silentcircle.contacts.model.account.AccountType.EditType}, are performed through
 * {@link com.silentcircle.contacts.model.RawContactModifier} to ensure that {@link com.silentcircle.contacts.model.account.AccountType} are enforced.
 */
public class RawContactEditorView extends BaseRawContactEditorView {

    private static final String TAG = "RawContactEditorView";

    private LayoutInflater mInflater;

    private StructuredNameEditorView mName;
    private PhoneticNameEditorView mPhoneticName;
    private GroupMembershipView mGroupMembershipView;

    private ViewGroup mFields;
    private Button mAddFieldButton;

    private long mRawContactId = -1;
    private boolean mAutoAddToDefaultGroup = true;
    private Cursor mGroupMetaData;
    private DataKind mGroupMembershipKind;
    private RawContactDelta mState;

    private ContactEditorFragment parent;

    private boolean mPhoneticNameAdded;

    public RawContactEditorView(Context context) {
        super(context);
    }

    public RawContactEditorView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);

        View view = getPhotoEditor();
        if (view != null) {
            view.setEnabled(enabled);
        }

        if (mName != null) {
            mName.setEnabled(enabled);
        }

        if (mPhoneticName != null) {
            mPhoneticName.setEnabled(enabled);
        }

        if (mFields != null) {
            int count = mFields.getChildCount();
            for (int i = 0; i < count; i++) {
                mFields.getChildAt(i).setEnabled(enabled);
            }
        }

        if (mGroupMembershipView != null) {
            mGroupMembershipView.setEnabled(enabled);
        }

        mAddFieldButton.setEnabled(enabled);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        mName = (StructuredNameEditorView) findViewById(R.id.edit_name);
        mName.setDeletable(false);

        mPhoneticName = (PhoneticNameEditorView) findViewById(R.id.edit_phonetic_name);
        mPhoneticName.setDeletable(false);

        mFields = (ViewGroup) findViewById(R.id.sect_fields);

        mAddFieldButton = (Button) findViewById(R.id.button_add_field);
        mAddFieldButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showAddInformationPopupWindow();
            }
        });
    }

    /**
     * Set the internal state for this view, given a current
     * {@link RawContactDelta} state and the {@link com.silentcircle.contacts.model.account.AccountType} that
     * apply to that state.
     */
    @Override
    public void setState(RawContactDelta state, AccountType type, ViewIdGenerator vig, boolean isProfile) {

        mState = state;

        // Remove any existing sections
        mFields.removeAllViews();

        // Bail if invalid state or account type
        if (state == null || type == null)
            return;

        setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));

        // Make sure we have a StructuredName and Organization
        RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
        RawContactModifier.ensureKindExists(state, type, Organization.CONTENT_ITEM_TYPE);

        mRawContactId = state.getRawContactId();

        // Show photo editor when supported
        RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
        setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
        getPhotoEditor().setEnabled(isEnabled());
        mName.setEnabled(isEnabled());

        mPhoneticName.setEnabled(isEnabled());

        // Show and hide the appropriate views
        mFields.setVisibility(VISIBLE);
        mName.setVisibility(VISIBLE);
        mPhoneticName.setVisibility(VISIBLE);

        mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
        if (mGroupMembershipKind != null) {
            mGroupMembershipView = (GroupMembershipView) mInflater.inflate(R.layout.item_group_membership, mFields,
                    false);
            mGroupMembershipView.setParent(parent);
            mGroupMembershipView.setKind(mGroupMembershipKind);
            mGroupMembershipView.setEnabled(isEnabled());
        }

        // Create editor sections for each possible data kind
        for (DataKind kind : type.getSortedDataKinds()) {
            // Skip kind of not editable
            if (!kind.editable)
                continue;

            final String mimeType = kind.mimeType;
            if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
                // Handle special case editor for structured name
                final RawContactDelta.ValuesDelta primary = state.getPrimaryEntry(mimeType);

                mName.setValues(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME), primary, state,
                        false, vig);
                mPhoneticName.setValues(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), primary,
                        state, false, vig);
            } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
                // Handle special case editor for photos
                final RawContactDelta.ValuesDelta primary = state.getPrimaryEntry(mimeType);
                getPhotoEditor().setValues(kind, primary, state, false, vig);
            } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
                if (mGroupMembershipView != null) {
                    mGroupMembershipView.setState(state);
                }
            } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
                // Create the organization section
                final KindSectionView section = (KindSectionView) mInflater.inflate(R.layout.item_kind_section,
                        mFields, false);
                section.setTitleVisible(false);
                section.setEnabled(isEnabled());
                section.setState(kind, state, false, vig);

                // If there is organization info for the contact already, display it
                if (!section.isEmpty()) {
                    mFields.addView(section);
                } else {
                    // Otherwise provide the user with an "add organization" button that shows the
                    // EditText fields only when clicked
                    final View organizationView = mInflater.inflate(R.layout.organization_editor_view_switcher,
                            mFields, false);
                    final View addOrganizationButton = organizationView.findViewById(R.id.add_organization_button);
                    final ViewGroup organizationSectionViewContainer = (ViewGroup) organizationView
                            .findViewById(R.id.container);

                    organizationSectionViewContainer.addView(section);

                    // Setup the click listener for the "add organization" button
                    addOrganizationButton.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // Once the user expands the organization field, the user cannot
                            // collapse them again.
                            organizationSectionViewContainer.setVisibility(VISIBLE);
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                                EditorAnimator.getInstance().expandOrganization(addOrganizationButton,
                                        organizationSectionViewContainer);
                            } else {
                                addOrganizationButton.setVisibility(INVISIBLE);
                            }
                        }
                    });
                    mFields.addView(organizationView);
                }
            } else {
                // Otherwise use generic section-based editors
                if (kind.fieldList == null)
                    continue;
                final KindSectionView section = (KindSectionView) mInflater.inflate(R.layout.item_kind_section,
                        mFields, false);
                section.setEnabled(isEnabled());
                section.setState(kind, state, false, vig);
                mFields.addView(section);
            }
        }

        if (mGroupMembershipView != null) {
            mFields.addView(mGroupMembershipView);
        }

        updatePhoneticNameVisibility();

        addToDefaultGroupIfNeeded();

        final int sectionCount = getSectionViewsWithoutFields().size();
        mAddFieldButton.setVisibility(sectionCount > 0 ? VISIBLE : GONE);
        mAddFieldButton.setEnabled(isEnabled());
    }

    @Override
    public void setGroupMetaData(Cursor groupMetaData) {
        mGroupMetaData = groupMetaData;
        addToDefaultGroupIfNeeded();
        if (mGroupMembershipView != null) {
            mGroupMembershipView.setGroupMetaData(groupMetaData);
        }
    }

    /**
     * The parent of the RawContactEditorView
     * @param p the parent
     */
    public void setParent(ContactEditorFragment p) {
        parent = p;
    }

    public void setAutoAddToDefaultGroup(boolean flag) {
        this.mAutoAddToDefaultGroup = flag;
    }

    /**
     * If automatic addition to the default group was requested (see
     * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any
     * group and if it is not adds it to the default group (in case of Google
     * contacts that's "My Contacts").
     */
    private void addToDefaultGroupIfNeeded() {
        if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed() || mState == null) {
            return;
        }

        boolean hasGroupMembership = false;
        ArrayList<RawContactDelta.ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
        if (entries != null) {
            for (RawContactDelta.ValuesDelta values : entries) {
                Long id = values.getGroupRowId();
                if (id != null && id.longValue() != 0) {
                    hasGroupMembership = true;
                    break;
                }
            }
        }

        if (!hasGroupMembership) {
            long defaultGroupId = getDefaultGroupId();
            if (defaultGroupId != -1) {
                RawContactDelta.ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind);
                entry.setGroupRowId(defaultGroupId);
            }
        }
    }

    /**
     * Returns the default group (e.g. "My Contacts") for the current raw contact's
     * account.  Returns -1 if there is no such group.
     */
    private long getDefaultGroupId() {
        mGroupMetaData.moveToPosition(-1);
        while (mGroupMetaData.moveToNext()) {
            long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
            if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
                    && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
                return groupId;
            }
        }
        return -1;
    }

    public TextFieldsEditorView getNameEditor() {
        return mName;
    }

    public TextFieldsEditorView getPhoneticNameEditor() {
        return mPhoneticName;
    }

    private void updatePhoneticNameVisibility() {
        boolean showByDefault = getContext().getResources().getBoolean(R.bool.config_editor_include_phonetic_name);

        if (showByDefault || mPhoneticName.hasData() || mPhoneticNameAdded) {
            mPhoneticName.setVisibility(VISIBLE);
        } else {
            mPhoneticName.setVisibility(GONE);
        }
    }

    @Override
    public long getRawContactId() {
        return mRawContactId;
    }

    /**
     * Return a list of KindSectionViews that have no fields yet...
     * these are candidates to have fields added in
     * {@link #showAddInformationPopupWindow()}
     */
    private ArrayList<KindSectionView> getSectionViewsWithoutFields() {
        final ArrayList<KindSectionView> fields = new ArrayList<KindSectionView>(mFields.getChildCount());
        for (int i = 0; i < mFields.getChildCount(); i++) {
            View child = mFields.getChildAt(i);
            if (child instanceof KindSectionView) {
                final KindSectionView sectionView = (KindSectionView) child;
                // If the section is already visible (has 1 or more editors), then don't offer the
                // option to add this type of field in the popup menu
                if (sectionView.getEditorCount() > 0) {
                    continue;
                }
                DataKind kind = sectionView.getKind();
                // not a list and already exists? ignore
                if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
                    continue;
                }
                if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
                    continue;
                }
                if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(kind.mimeType)
                        && mPhoneticName.getVisibility() == VISIBLE) {
                    continue;
                }

                fields.add(sectionView);
            }
        }
        return fields;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void showAddInformationPopupWindow() {
        final ArrayList<KindSectionView> fields = getSectionViewsWithoutFields();

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            showAddInformationPopupWindow(fields);
        } else {
            final PopupMenu popupMenu = new PopupMenu(getContext(), mAddFieldButton);
            final Menu menu = popupMenu.getMenu();
            for (int i = 0; i < fields.size(); i++) {
                menu.add(Menu.NONE, i, Menu.NONE, fields.get(i).getTitle());
            }

            popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    final KindSectionView view = fields.get(item.getItemId());
                    if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(view.getKind().mimeType)) {
                        mPhoneticNameAdded = true;
                        updatePhoneticNameVisibility();
                    } else {
                        view.addItem();
                    }

                    // If this was the last section without an entry, we just added one, and therefore
                    // there's no reason to show the button.
                    if (fields.size() == 1) {
                        mAddFieldButton.setVisibility(GONE);
                    }

                    return true;
                }
            });
            popupMenu.show();
        }
    }

    private void showAddInformationPopupWindow(ArrayList<KindSectionView> fields) {
        if (parent == null)
            return;

        AddInfoDialog infoDialog = AddInfoDialog.newInstance(fields, this);
        FragmentManager fragmentManager = parent.getFragmentManager();
        infoDialog.show(fragmentManager, "SilentContactsAddInfo");
    }

    /*
     * The add info pop-up selection dialog
     */
    public static class AddInfoDialog extends SherlockDialogFragment {

        // Use static here - no simple way to hand over the fields array or RawContactEditorView to the dialog object
        // handle with care, not thread safe
        private static ArrayList<KindSectionView> fieldsStatic;
        private static RawContactEditorView editorView;

        private static String SELECTION = "selection";

        /**
         * Create a AddInfoDialog instance and set the arguments.
         * 
         * @param fields
         *            Array list that contains the selectable new fields
         * @return
         */
        public static AddInfoDialog newInstance(ArrayList<KindSectionView> fields, RawContactEditorView editView) {
            fieldsStatic = fields;
            editorView = editView;
            AddInfoDialog f = new AddInfoDialog();

            String texts[] = new String[fields.size()];
            for (int i = 0; i < fields.size(); i++)
                texts[i] = fields.get(i).getTitle();

            Bundle args = new Bundle();
            args.putStringArray(SELECTION, texts);
            f.setArguments(args);
            return f;
        }

        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setItems(getArguments().getStringArray(SELECTION), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    if (fieldsStatic == null || editorView == null)
                        return;
                    // The 'which' argument contains the index position
                    // of the selected item
                    final KindSectionView view = fieldsStatic.get(which);
                    if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(view.getKind().mimeType)) {
                        editorView.mPhoneticNameAdded = true;
                        editorView.updatePhoneticNameVisibility();
                    } else {
                        view.addItem();
                    }

                    // If this was the last section without an entry, we just added one, and therefore
                    // there's no reason to show the button.
                    if (fieldsStatic.size() == 1) {
                        editorView.mAddFieldButton.setVisibility(GONE);
                    }
                    editorView = null;
                    fieldsStatic = null;
                }
            });
            return builder.create();
        }
    }
}