Create new SIM contacts import screen.
This is needed since SIM import is going be surfaced more prominently via an
assistant.
Test
Automated: Ran GoogleContactsTests
Manual: verify that contacts are imported when launched from settings
Bug 31781331
Change-Id: I55ea078056038d348d5b6f05f91f5159e1ee39d3
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index d4da588..1ed36b5 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -16,7 +16,6 @@
package com.android.contacts;
-import static android.Manifest.permission.WRITE_CONTACTS;
import android.app.Activity;
import android.app.IntentService;
import android.content.ContentProviderOperation;
@@ -54,16 +53,17 @@
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.database.ContactUpdateUtils;
+import com.android.contacts.common.database.SimContactDao;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.CPOWrapper;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.RawContactDeltaList;
import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.SimContact;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.compat.PinnedPositionsCompat;
import com.android.contacts.util.ContactPhotoUtils;
-
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -72,6 +72,8 @@
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.
*/
@@ -86,6 +88,7 @@
public static final String EXTRA_ACCOUNT_NAME = "accountName";
public static final String EXTRA_ACCOUNT_TYPE = "accountType";
public static final String EXTRA_DATA_SET = "dataSet";
+ public static final String EXTRA_ACCOUNT = "account";
public static final String EXTRA_CONTENT_VALUES = "contentValues";
public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
public static final String EXTRA_RESULT_RECEIVER = "resultReceiver";
@@ -136,12 +139,23 @@
public static final String EXTRA_UNDO_ACTION = "undoAction";
public static final String EXTRA_UNDO_DATA = "undoData";
- public static final String BROADCAST_ACTION_GROUP_DELETED = "groupDeleted";
+ public static final String ACTION_IMPORT_FROM_SIM = "importFromSim";
+ public static final String EXTRA_SIM_CONTACTS = "simContacts";
+
+ public static final String BROADCAST_GROUP_DELETED = "groupDeleted";
+ public static final String BROADCAST_SIM_IMPORT_COMPLETE = "simImportComplete";
+
+ public static final String EXTRA_RESULT_CODE = "resultCode";
+ public static final String EXTRA_RESULT_COUNT = "count";
+ public static final String EXTRA_OPERATION_REQUESTED_AT_TIME = "requestedTime";
public static final int CP2_ERROR = 0;
public static final int CONTACTS_LINKED = 1;
public static final int CONTACTS_SPLIT = 2;
public static final int BAD_ARGUMENTS = 3;
+ public static final int RESULT_UNKNOWN = 0;
+ public static final int RESULT_SUCCESS = 1;
+ public static final int RESULT_FAILURE = 2;
private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
Data.MIMETYPE,
@@ -176,6 +190,7 @@
private Handler mMainHandler;
private GroupsDao mGroupsDao;
+ private SimContactDao mSimContactDao;
public ContactSaveService() {
super(TAG);
@@ -187,6 +202,7 @@
public void onCreate() {
super.onCreate();
mGroupsDao = new GroupsDaoImpl(this);
+ mSimContactDao = new SimContactDao(this);
}
public static void registerListener(Listener listener) {
@@ -305,6 +321,8 @@
setRingtone(intent);
} else if (ACTION_UNDO.equals(action)) {
undo(intent);
+ } else if (ACTION_IMPORT_FROM_SIM.equals(action)) {
+ importFromSim(intent);
}
}
@@ -812,7 +830,7 @@
}
final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
- final Intent callbackIntent = new Intent(BROADCAST_ACTION_GROUP_DELETED);
+ final Intent callbackIntent = new Intent(BROADCAST_GROUP_DELETED);
final Bundle undoData = mGroupsDao.captureDeletionUndoData(groupUri);
callbackIntent.putExtra(EXTRA_UNDO_ACTION, ACTION_DELETE_GROUP);
callbackIntent.putExtra(EXTRA_UNDO_DATA, undoData);
@@ -1628,6 +1646,36 @@
operations.add(builder.build());
}
+ public static Intent createImportFromSimIntent(Context context,
+ ArrayList<SimContact> contacts, AccountWithDataSet targetAccount) {
+ return new Intent(context, ContactSaveService.class)
+ .setAction(ACTION_IMPORT_FROM_SIM)
+ .putExtra(EXTRA_SIM_CONTACTS, contacts)
+ .putExtra(EXTRA_ACCOUNT, targetAccount);
+ }
+
+ private void importFromSim(Intent intent) {
+ final Intent result = new Intent(BROADCAST_SIM_IMPORT_COMPLETE)
+ .putExtra(EXTRA_OPERATION_REQUESTED_AT_TIME, System.currentTimeMillis());
+ try {
+ final AccountWithDataSet targetAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ final ArrayList<SimContact> contacts =
+ intent.getParcelableArrayListExtra(EXTRA_SIM_CONTACTS);
+ mSimContactDao.importContacts(contacts, targetAccount);
+ // notify success
+ LocalBroadcastManager.getInstance(this).sendBroadcast(result
+ .putExtra(EXTRA_RESULT_COUNT, contacts.size())
+ .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS));
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "importFromSim completed successfully");
+ }
+ } catch (RemoteException|OperationApplicationException e) {
+ Log.e(TAG, "Failed to import contacts from SIM card", e);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(result
+ .putExtra(EXTRA_RESULT_CODE, RESULT_FAILURE));
+ }
+ }
+
/**
* Shows a toast on the UI thread.
*/
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
new file mode 100644
index 0000000..a9dcf97
--- /dev/null
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -0,0 +1,323 @@
+/*
+ * 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.
+ */
+package com.android.contacts;
+
+import android.app.DialogFragment;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.widget.ContentLoadingProgressBar;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.compat.CompatUtils;
+import com.android.contacts.common.database.SimContactDao;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.editor.AccountHeaderPresenter;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Dialog that presents a list of contacts from a SIM card that can be imported into a selected
+ * account
+ */
+public class SimImportFragment extends DialogFragment
+ implements LoaderManager.LoaderCallbacks<ArrayList<SimContact>>,
+ MultiSelectEntryContactListAdapter.SelectedContactsListener {
+
+ private static final String KEY_ACCOUNT = "account";
+ private static final String ARG_SUBSCRIPTION_ID = "subscriptionId";
+ public static final int NO_SUBSCRIPTION_ID = -1;
+
+ private ContactsPreferences mPreferences;
+ private AccountTypeManager mAccountTypeManager;
+ private SimContactAdapter mAdapter;
+ private AccountHeaderPresenter mAccountHeaderPresenter;
+ private ContentLoadingProgressBar mLoadingIndicator;
+ private Toolbar mToolbar;
+ private ListView mListView;
+ private View mImportButton;
+
+ private int mSubscriptionId;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(STYLE_NO_TITLE, R.style.PeopleThemeAppCompat_FullScreenDialog);
+ mPreferences = new ContactsPreferences(getContext());
+ mAccountTypeManager = AccountTypeManager.getInstance(getActivity());
+ mAdapter = new SimContactAdapter(getActivity());
+
+ mAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getActivity()));
+ mAdapter.setDisplayCheckBoxes(true);
+ mAdapter.setHasHeader(0, false);
+
+ final Bundle args = getArguments();
+ mSubscriptionId = args == null ? NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID,
+ NO_SUBSCRIPTION_ID);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.fragment_sim_import, container, false);
+
+ mAccountHeaderPresenter = new AccountHeaderPresenter(
+ view.findViewById(R.id.account_header_container));
+ if (savedInstanceState != null) {
+ AccountWithDataSet account = savedInstanceState.getParcelable(KEY_ACCOUNT);
+ mAccountHeaderPresenter.setCurrentAccount(account);
+ } else {
+ final AccountWithDataSet currentDefaultAccount = AccountWithDataSet
+ .getDefaultOrBestFallback(mPreferences, mAccountTypeManager);
+ mAccountHeaderPresenter.setCurrentAccount(currentDefaultAccount);
+ }
+
+ mListView = (ListView) view.findViewById(R.id.list);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mAdapter.toggleSelectionOfContactId(id);
+ }
+ });
+ mImportButton = view.findViewById(R.id.import_button);
+ mImportButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ importCurrentSelections();
+ // Do we wait for import to finish?
+ dismiss();
+ }
+ });
+ mImportButton.setEnabled(mAdapter.getSelectedContactIds().size() > 0);
+
+ mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
+ mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+
+ mLoadingIndicator = (ContentLoadingProgressBar) view.findViewById(R.id.loading_progress);
+ mAdapter.setSelectedContactsListener(this);
+
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mAdapter.isEmpty() && getLoaderManager().getLoader(0).isStarted()) {
+ mLoadingIndicator.show();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_ACCOUNT, mAccountHeaderPresenter.getCurrentAccount());
+ }
+
+ @Override
+ public SimContactLoader onCreateLoader(int id, Bundle args) {
+ return new SimContactLoader(getContext(), mSubscriptionId);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ArrayList<SimContact>> loader,
+ ArrayList<SimContact> data) {
+ mListView.setEmptyView(getView().findViewById(R.id.empty_message));
+ mAdapter.setContacts(data);
+ // we default to selecting all contacts.
+ mAdapter.selectAll();
+ mLoadingIndicator.hide();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<ArrayList<SimContact>> loader) {
+ }
+
+ private void importCurrentSelections() {
+ ContactSaveService.startService(getContext(), ContactSaveService
+ .createImportFromSimIntent(getContext(), mAdapter.getSelectedContacts(),
+ mAccountHeaderPresenter.getCurrentAccount()));
+ }
+
+ @Override
+ public void onSelectedContactsChanged() {
+ updateSelectedCount();
+ }
+
+ @Override
+ public void onSelectedContactsChangedViaCheckBox() {
+ updateSelectedCount();
+ }
+
+ private void updateSelectedCount() {
+ final int selectedCount = mAdapter.getSelectedContactIds().size();
+ if (selectedCount == 0) {
+ mToolbar.setTitle(R.string.sim_import_title_none_selected);
+ } else {
+ mToolbar.setTitle(getString(R.string.sim_import_title_some_selected_fmt,
+ selectedCount));
+ }
+ if (mImportButton != null) {
+ mImportButton.setEnabled(selectedCount > 0);
+ }
+ }
+
+ public Context getContext() {
+ if (CompatUtils.isMarshmallowCompatible()) {
+ return super.getContext();
+ }
+ return getActivity();
+ }
+
+ /**
+ * Creates a fragment that will display contacts stored on the default SIM card
+ */
+ public static SimImportFragment newInstance() {
+ return new SimImportFragment();
+ }
+
+ /**
+ * Creates a fragment that will display the contacts stored on the SIM card that has the
+ * provided subscriptionId
+ */
+ public static SimImportFragment newInstance(int subscriptionId) {
+ final SimImportFragment fragment = new SimImportFragment();
+ final Bundle args = new Bundle();
+ args.putInt(ARG_SUBSCRIPTION_ID, subscriptionId);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ private static class SimContactAdapter extends ContactListAdapter {
+ private ArrayList<SimContact> mContacts;
+
+ public SimContactAdapter(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ super.bindView(itemView, partition, cursor, position);
+ ContactListItemView contactView = (ContactListItemView) itemView;
+ bindNameAndViewId(contactView, cursor);
+ bindPhoto(contactView, partition, cursor);
+ }
+
+ public void setContacts(ArrayList<SimContact> contacts) {
+ mContacts = contacts;
+ changeCursor(SimContact.convertToContactsCursor(mContacts,
+ ContactQuery.CONTACT_PROJECTION_PRIMARY));
+ }
+
+ public ArrayList<SimContact> getSelectedContacts() {
+ if (mContacts == null) return null;
+
+ final Set<Long> selectedIds = getSelectedContactIds();
+ final ArrayList<SimContact> selected = new ArrayList<>();
+ for (SimContact contact : mContacts) {
+ if (selectedIds.contains(contact.getId())) {
+ selected.add(contact);
+ }
+ }
+ return selected;
+ }
+
+ public void selectAll() {
+ if (mContacts == null) return;
+
+ final TreeSet<Long> selected = new TreeSet<>();
+ for (SimContact contact : mContacts) {
+ selected.add(contact.getId());
+ }
+ setSelectedContactIds(selected);
+ }
+ }
+
+ public static class SimContactLoader extends AsyncTaskLoader<ArrayList<SimContact>> {
+ private SimContactDao mDao;
+ private final int mSubscriptionId;
+ private ArrayList<SimContact> mData;
+
+ public SimContactLoader(Context context, int subscriptionId) {
+ super(context);
+ mDao = new SimContactDao(context);
+ mSubscriptionId = subscriptionId;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (mData != null) {
+ deliverResult(mData);
+ } else {
+ forceLoad();
+ }
+ }
+
+ @Override
+ public void deliverResult(ArrayList<SimContact> data) {
+ mData = data;
+ super.deliverResult(data);
+ }
+
+ @Override
+ public ArrayList<SimContact> loadInBackground() {
+ if (mSubscriptionId != NO_SUBSCRIPTION_ID) {
+ return mDao.loadSimContacts(mSubscriptionId);
+ } else {
+ return mDao.loadSimContacts();
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ mData = null;
+ }
+ }
+}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b2a0a07..935c66f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -20,15 +20,13 @@
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
-import android.content.ContentResolver;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
-import android.content.SyncStatusObserver;
import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.content.SyncStatusObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -40,7 +38,6 @@
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.SwipeRefreshLayout;
-import android.text.TextUtils;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -65,8 +62,6 @@
import com.android.contacts.common.logging.Logger;
import com.android.contacts.common.logging.ScreenEvent.ScreenType;
import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountDisplayInfo;
-import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.AccountFilterUtil;
import com.android.contacts.common.util.Constants;
@@ -469,7 +464,7 @@
mSaveServiceListener = new SaveServiceListener();
LocalBroadcastManager.getInstance(this).registerReceiver(mSaveServiceListener,
- new IntentFilter(ContactSaveService.BROADCAST_ACTION_GROUP_DELETED));
+ new IntentFilter(ContactSaveService.BROADCAST_GROUP_DELETED));
}
@Override
@@ -731,7 +726,7 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
- case ContactSaveService.BROADCAST_ACTION_GROUP_DELETED:
+ case ContactSaveService.BROADCAST_GROUP_DELETED:
onGroupDeleted(intent);
break;
}
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
new file mode 100644
index 0000000..f9fb4e8
--- /dev/null
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+package com.android.contacts.common.database;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides data access methods for loading contacts from a SIM card and and migrating these
+ * SIM contacts to a CP2 account.
+ */
+public class SimContactDao {
+ @VisibleForTesting
+ public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn");
+
+ public static String _ID = BaseColumns._ID;
+ public static String NAME = "name";
+ public static String NUMBER = "number";
+ public static String EMAILS = "emails";
+
+ private ContentResolver mResolver;
+
+ public SimContactDao(Context context) {
+ this(context.getContentResolver());
+ }
+
+ private SimContactDao(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
+ return loadFrom(ICC_CONTENT_URI.buildUpon()
+ .appendPath("subId")
+ .appendPath(String.valueOf(subscriptionId))
+ .build());
+ }
+
+ public ArrayList<SimContact> loadSimContacts() {
+ return loadFrom(ICC_CONTENT_URI);
+ }
+
+ private ArrayList<SimContact> loadFrom(Uri uri) {
+ final Cursor cursor = mResolver.query(uri, null, null, null, null);
+
+ try {
+ return loadFromCursor(cursor);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private ArrayList<SimContact> loadFromCursor(Cursor cursor) {
+ final int colId = cursor.getColumnIndex(_ID);
+ final int colName = cursor.getColumnIndex(NAME);
+ final int colNumber = cursor.getColumnIndex(NUMBER);
+ final int colEmails = cursor.getColumnIndex(EMAILS);
+
+ final ArrayList<SimContact> result = new ArrayList<>();
+
+ while (cursor.moveToNext()) {
+ final long id = cursor.getLong(colId);
+ final String name = cursor.getString(colName);
+ final String number = cursor.getString(colNumber);
+ final String emails = cursor.getString(colEmails);
+
+ final SimContact contact = new SimContact(id, name, number, parseEmails(emails));
+ result.add(contact);
+ }
+ return result;
+ }
+
+ public ContentProviderResult[] importContacts(List<SimContact> contacts,
+ AccountWithDataSet targetAccount)
+ throws RemoteException, OperationApplicationException {
+ final ArrayList<ContentProviderOperation> ops = createImportOperations(contacts, targetAccount);
+ return mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+ }
+
+ private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
+ AccountWithDataSet targetAccount) {
+ final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (SimContact contact : contacts) {
+ contact.appendCreateContactOperations(ops, targetAccount);
+ }
+ return ops;
+ }
+
+ private String[] parseEmails(String emails) {
+ return emails != null ? emails.split(",") : null;
+ }
+}
diff --git a/src/com/android/contacts/common/interactions/ImportDialogFragment.java b/src/com/android/contacts/common/interactions/ImportDialogFragment.java
index 0c0ce73..991a9c2 100644
--- a/src/com/android/contacts/common/interactions/ImportDialogFragment.java
+++ b/src/com/android/contacts/common/interactions/ImportDialogFragment.java
@@ -37,6 +37,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.android.contacts.SimImportFragment;
import com.android.contacts.common.R;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
@@ -151,7 +152,9 @@
public void onClick(DialogInterface dialog, int which) {
boolean dismissDialog;
final int resId = adapter.getItem(which).mChoiceResourceId;
- if (resId == R.string.import_from_sim || resId == R.string.import_from_vcf_file) {
+ if (resId == R.string.import_from_sim) {
+ dismissDialog = handleSimImportRequest(adapter.getItem(which).mSubscriptionId);
+ } else if (resId == R.string.import_from_vcf_file) {
dismissDialog = handleImportRequest(resId,
adapter.getItem(which).mSubscriptionId);
} else {
@@ -172,8 +175,13 @@
.create();
}
+ private boolean handleSimImportRequest(int subscriptionId) {
+ SimImportFragment.newInstance(subscriptionId).show(getFragmentManager(), "SimImport");
+ return true;
+ }
+
/**
- * Handle "import from SIM" and "import from SD".
+ * Handle "import from SD".
*
* @return {@code true} if the dialog show be closed. {@code false} otherwise.
*/
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index 677aa46..6294f47 100644
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -41,7 +41,7 @@
public abstract class ContactListAdapter extends MultiSelectEntryContactListAdapter {
public static class ContactQuery {
- private static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
+ public static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
Contacts.CONTACT_PRESENCE, // 2
diff --git a/src/com/android/contacts/common/model/SimContact.java b/src/com/android/contacts/common/model/SimContact.java
new file mode 100644
index 0000000..a13ef67
--- /dev/null
+++ b/src/com/android/contacts/common/model/SimContact.java
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+package com.android.contacts.common.model;
+
+import android.content.ContentProviderOperation;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Holds data for contacts loaded from the SIM card.
+ */
+public class SimContact implements Parcelable {
+ private final long mId;
+ private final String mName;
+ private final String mPhone;
+ private final String[] mEmails;
+
+ public SimContact(long id, String name, String phone, String[] emails) {
+ this.mId = id;
+ this.mName = name;
+ this.mPhone = phone;
+ this.mEmails = emails;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getPhone() {
+ return mPhone;
+ }
+
+ public String[] getEmails() {
+ return mEmails;
+ }
+
+ public void appendCreateContactOperations(List<ContentProviderOperation> ops,
+ AccountWithDataSet targetAccount) {
+ // nothing to save.
+ if (mName == null && mPhone == null && mEmails == null) return;
+
+ final int rawContactOpIndex = ops.size();
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withYieldAllowed(true)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, targetAccount.name)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, targetAccount.type)
+ .withValue(ContactsContract.RawContacts.DATA_SET, targetAccount.dataSet)
+ .build());
+ if (mName != null) {
+ ops.add(createInsertOp(rawContactOpIndex, StructuredName.CONTENT_ITEM_TYPE,
+ StructuredName.DISPLAY_NAME, mName));
+ }
+ if (mPhone != null) {
+ ops.add(createInsertOp(rawContactOpIndex, Phone.CONTENT_ITEM_TYPE,
+ Phone.NUMBER, mPhone));
+ }
+ if (mEmails != null) {
+ for (String email : mEmails) {
+ ops.add(createInsertOp(rawContactOpIndex, Email.CONTENT_ITEM_TYPE,
+ Email.ADDRESS, email));
+ }
+ }
+ }
+
+ private ContentProviderOperation createInsertOp(int rawContactOpIndex, String mimeType,
+ String column, String value) {
+ return ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
+ .withValue(ContactsContract.Data.MIMETYPE, mimeType)
+ .withValue(column, value)
+ .build();
+ }
+
+ public void appendAsContactRow(MatrixCursor cursor) {
+ cursor.newRow().add(ContactsContract.Contacts._ID, mId)
+ .add(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, mName)
+ .add(ContactsContract.Contacts.LOOKUP_KEY, getLookupKey());
+ }
+
+ /**
+ * Generate a "fake" lookup key. This is needed because
+ * {@link com.android.contacts.common.ContactPhotoManager} will only generate a letter avatar
+ * if the contact has a lookup key.
+ */
+ private String getLookupKey() {
+ if (mName != null) {
+ return "sim-n-" + Uri.encode(mName);
+ } else if (mPhone != null) {
+ return "sim-p-" + Uri.encode(mPhone);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SimContact{" +
+ "mId=" + mId +
+ ", mName='" + mName + '\'' +
+ ", mPhone='" + mPhone + '\'' +
+ ", mEmails=" + Arrays.toString(mEmails) +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final SimContact that = (SimContact) o;
+
+ if (mId != that.mId) return false;
+ if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
+ if (mPhone != null ? !mPhone.equals(that.mPhone) : that.mPhone != null) return false;
+ return Arrays.equals(mEmails, that.mEmails);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (mId ^ (mId >>> 32));
+ result = 31 * result + (mName != null ? mName.hashCode() : 0);
+ result = 31 * result + (mPhone != null ? mPhone.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(mEmails);
+ return result;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mId);
+ dest.writeString(mName);
+ dest.writeString(mPhone);
+ dest.writeStringArray(mEmails);
+ }
+
+ /**
+ * Convert a collection of SIM contacts to a Cursor matching a query from
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_URI} with the provided projection.
+ *
+ * This allows a collection of SIM contacts to be displayed using the existing adapters for
+ * contacts.
+ */
+ public static final MatrixCursor convertToContactsCursor(Collection<SimContact> contacts,
+ String[] projection) {
+ final MatrixCursor result = new MatrixCursor(projection);
+ for (SimContact contact : contacts) {
+ contact.appendAsContactRow(result);
+ }
+ return result;
+ }
+
+ public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
+ @Override
+ public SimContact createFromParcel(Parcel source) {
+ long id = source.readLong();
+ String name = source.readString();
+ String phone = source.readString();
+ String[] emails = source.createStringArray();
+ return new SimContact(id, name, phone, emails);
+ }
+
+ @Override
+ public SimContact[] newArray(int size) {
+ return new SimContact[size];
+ }
+ };
+}
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 187f71c..37f6652 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -20,13 +20,15 @@
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.preference.ContactsPreferences;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@@ -237,4 +239,25 @@
return ret;
}
+
+ public static AccountWithDataSet getDefaultOrBestFallback(ContactsPreferences preferences,
+ AccountTypeManager accountTypeManager) {
+ if (preferences.isDefaultAccountSet()) {
+ return preferences.getDefaultAccount();
+ }
+ List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(/* writableOnly */ true);
+
+ if (accounts.isEmpty()) {
+ return AccountWithDataSet.getNullAccount();
+ }
+
+ // Return the first google account
+ for (AccountWithDataSet account : accounts) {
+ if (GoogleAccountType.ACCOUNT_TYPE.equals(account) && account.dataSet == null) {
+ return account;
+ }
+ }
+ // Arbitrarily return the first writable account
+ return accounts.get(0);
+ }
}
diff --git a/src/com/android/contacts/common/preference/ContactsPreferenceActivity.java b/src/com/android/contacts/common/preference/ContactsPreferenceActivity.java
index f544c7b..030575d 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferenceActivity.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferenceActivity.java
@@ -16,14 +16,22 @@
package com.android.contacts.common.preference;
-import android.app.ActionBar;
+import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.provider.ContactsContract.ProviderStatus;
-import android.provider.ContactsContract.QuickContact;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
+import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.contacts.common.R;
import com.android.contacts.common.list.ProviderStatusWatcher;
@@ -44,13 +52,19 @@
private ProviderStatusWatcher mProviderStatusWatcher;
+ private AppCompatDelegate mCompatDelegate;
+
public static final String EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ mCompatDelegate = AppCompatDelegate.create(this, null);
- final ActionBar actionBar = getActionBar();
+ super.onCreate(savedInstanceState);
+ mCompatDelegate.onCreate(savedInstanceState);
+
+
+ final ActionBar actionBar = mCompatDelegate.getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
}
@@ -80,6 +94,71 @@
}
}
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mCompatDelegate.onPostCreate(savedInstanceState);
+ }
+
+ public void setSupportActionBar(Toolbar toolbar) {
+ mCompatDelegate.setSupportActionBar(toolbar);
+ }
+
+ @NonNull
+ @Override
+ public MenuInflater getMenuInflater() {
+ return mCompatDelegate.getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutRes) {
+ mCompatDelegate.setContentView(layoutRes);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ mCompatDelegate.setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ mCompatDelegate.setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ mCompatDelegate.addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ mCompatDelegate.onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ mCompatDelegate.setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mCompatDelegate.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCompatDelegate.onDestroy();
+ }
+
+ @Override
+ public void invalidateOptionsMenu() {
+ mCompatDelegate.invalidateOptionsMenu();
+ }
+
protected void showAboutFragment() {
getFragmentManager().beginTransaction()
.replace(android.R.id.content, AboutPreferenceFragment.newInstance(), TAG_ABOUT)
@@ -107,8 +186,8 @@
}
}
- private void setActivityTitle(int res) {
- final ActionBar actionBar = getActionBar();
+ private void setActivityTitle(@StringRes int res) {
+ final ActionBar actionBar = mCompatDelegate.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(res);
}
diff --git a/src/com/android/contacts/common/preference/ContactsPreferences.java b/src/com/android/contacts/common/preference/ContactsPreferences.java
index f4187eb..1ae2b9d 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferences.java
@@ -206,6 +206,10 @@
mPreferences.edit().putString(mDefaultAccountKey, accountWithDataSet.stringify()).commit();
}
+ public boolean isDefaultAccountSet() {
+ return mDefaultAccount != null || mPreferences.contains(mDefaultAccountKey);
+ }
+
/**
* @return false if there is only one writable account or no requirement to return true is met.
* true if the contact editor should show the "accounts changed" notification, that is:
diff --git a/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
index 89208a8..b3b2832 100644
--- a/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
+++ b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
@@ -18,10 +18,12 @@
import android.app.Activity;
import android.app.LoaderManager;
+import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
@@ -32,15 +34,22 @@
import android.provider.BlockedNumberContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Profile;
+import android.support.design.widget.Snackbar;
+import android.support.v4.content.LocalBroadcastManager;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.R;
import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.R;
import com.android.contacts.common.compat.TelecomManagerUtil;
import com.android.contacts.common.compat.TelephonyManagerCompat;
-import com.android.contacts.common.interactions.ImportDialogFragment;
import com.android.contacts.common.interactions.ExportDialogFragment;
+import com.android.contacts.common.interactions.ImportDialogFragment;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.logging.ScreenEvent.ScreenType;
@@ -122,6 +131,9 @@
private ProfileListener mListener;
+ private ViewGroup mRootView;
+ private SaveServiceResultListener mSaveServiceListener;
+
private final LoaderManager.LoaderCallbacks<Cursor> mProfileLoaderListener =
new LoaderManager.LoaderCallbacks<Cursor>() {
@@ -165,6 +177,25 @@
}
@Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Wrap the preference view in a FrameLayout so we can show a snackbar
+ mRootView = new FrameLayout(getActivity());
+ final View list = super.onCreateView(inflater, mRootView, savedInstanceState);
+ mRootView.addView(list);
+ return mRootView;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mSaveServiceListener = new SaveServiceResultListener();
+ LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
+ mSaveServiceListener,
+ new IntentFilter(ContactSaveService.BROADCAST_SIM_IMPORT_COMPLETE));
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -212,6 +243,13 @@
getLoaderManager().restartLoader(LOADER_PROFILE, null, mProfileLoaderListener);
}
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mSaveServiceListener);
+ mRootView = null;
+ }
+
public void updateMyInfoPreference(boolean hasProfile, String displayName, long contactId) {
final CharSequence summary = hasProfile ? displayName : getString(R.string.set_up_profile);
mMyInfoPreference.setSummary(summary);
@@ -361,5 +399,30 @@
}
}
}
+
+ private class SaveServiceResultListener extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final long now = System.currentTimeMillis();
+ final long opStart = intent.getLongExtra(
+ ContactSaveService.EXTRA_OPERATION_REQUESTED_AT_TIME, now);
+
+ // If it's been over 30 seconds the user is likely in a different context so suppress
+ // the toast message.
+ if (now - opStart > 30*1000) return;
+
+ final int code = intent.getIntExtra(ContactSaveService.EXTRA_RESULT_CODE,
+ ContactSaveService.RESULT_UNKNOWN);
+ final int count = intent.getIntExtra(ContactSaveService.EXTRA_RESULT_COUNT, -1);
+ if (code == ContactSaveService.RESULT_SUCCESS && count > 0) {
+ Snackbar.make(mRootView, getResources().getQuantityString(
+ R.plurals.sim_import_success_toast_fmt, count, count),
+ Snackbar.LENGTH_LONG).show();
+ } else if (code == ContactSaveService.RESULT_FAILURE) {
+ Snackbar.make(mRootView, R.string.sim_import_failed_toast,
+ Snackbar.LENGTH_LONG).show();
+ }
+ }
+ }
}
diff --git a/src/com/android/contacts/editor/AccountHeaderPresenter.java b/src/com/android/contacts/editor/AccountHeaderPresenter.java
new file mode 100644
index 0000000..de2b014
--- /dev/null
+++ b/src/com/android/contacts/editor/AccountHeaderPresenter.java
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListPopupWindow;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.AccountsListAdapter;
+import com.android.contacts.util.UiClosables;
+
+import java.util.List;
+
+/**
+ * Controls the display of an account selector or header.
+ *
+ * TODO: This was mostly copied from {@link RawContactEditorView}. The code in that class
+ * should probably be modified to use this instead of leaving it duplicated.
+ */
+public class AccountHeaderPresenter {
+
+ public interface Observer {
+ void onChange(AccountHeaderPresenter sender);
+
+ public static final Observer NONE = new Observer() {
+ @Override
+ public void onChange(AccountHeaderPresenter sender) {
+ }
+ };
+ }
+
+ private final Context mContext;
+ private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+
+ private AccountWithDataSet mCurrentAccount;
+
+ // Account header
+ private final View mAccountHeaderContainer;
+ private TextView mAccountHeaderType;
+ private TextView mAccountHeaderName;
+ private ImageView mAccountHeaderIcon;
+ private ImageView mAccountHeaderExpanderIcon;
+
+ // This would be different if the account was readonly
+ @StringRes
+ private int mSelectorTitle = R.string.editor_account_selector_title;
+
+ private Observer mObserver = Observer.NONE;
+
+ public AccountHeaderPresenter(View container) {
+ mContext = container.getContext();
+ mAccountHeaderContainer = container;
+ mAccountHeaderType = (TextView) container.findViewById(R.id.account_type);
+ mAccountHeaderName = (TextView) container.findViewById(R.id.account_name);
+ mAccountHeaderIcon = (ImageView) container.findViewById(R.id.account_type_icon);
+ mAccountHeaderExpanderIcon = (ImageView) container.findViewById(R.id.account_expander_icon);
+
+ mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(mContext);
+ }
+
+ public void setObserver(Observer observer) {
+ mObserver = observer;
+ }
+
+ public void setCurrentAccount(@NonNull AccountWithDataSet account) {
+ if (mCurrentAccount != null && mCurrentAccount.equals(account)) {
+ return;
+ }
+ mCurrentAccount = account;
+ if (mObserver != null) {
+ mObserver.onChange(this);
+ }
+ updateDisplayedAccount();
+ }
+
+ public AccountWithDataSet getCurrentAccount() {
+ return mCurrentAccount;
+ }
+
+ private void updateDisplayedAccount() {
+ mAccountHeaderContainer.setVisibility(View.GONE);
+ if (mCurrentAccount == null) return;
+
+ final AccountDisplayInfo account =
+ mAccountDisplayInfoFactory.getAccountDisplayInfo(mCurrentAccount);
+
+ final String accountLabel = getAccountLabel(account);
+
+ // Either the account header or selector should be shown, not both.
+ final List<AccountWithDataSet> accounts =
+ AccountTypeManager.getInstance(mContext).getAccounts(true);
+
+ if (accounts.size() > 1) {
+ addAccountSelector(accountLabel);
+ } else {
+ addAccountHeader(accountLabel);
+ }
+ }
+
+ private void addAccountHeader(String accountLabel) {
+ mAccountHeaderContainer.setVisibility(View.VISIBLE);
+
+ // Set the account name
+ mAccountHeaderName.setVisibility(View.VISIBLE);
+ mAccountHeaderName.setText(accountLabel);
+
+ // Set the account type
+ final String selectorTitle = mContext.getResources().getString(mSelectorTitle);
+ mAccountHeaderType.setText(selectorTitle);
+
+ // Set the icon
+ final AccountDisplayInfo displayInfo = mAccountDisplayInfoFactory
+ .getAccountDisplayInfo(mCurrentAccount);
+ mAccountHeaderIcon.setImageDrawable(displayInfo.getIcon());
+
+ // Set the content description
+ mAccountHeaderContainer.setContentDescription(
+ EditorUiUtils.getAccountInfoContentDescription(accountLabel,
+ selectorTitle));
+ }
+
+ private void addAccountSelector(CharSequence nameLabel) {
+ final View.OnClickListener onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final ListPopupWindow popup = new ListPopupWindow(mContext, null);
+ final AccountsListAdapter adapter =
+ new AccountsListAdapter(mContext,
+ AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
+ mCurrentAccount);
+ popup.setWidth(mAccountHeaderContainer.getWidth());
+ popup.setAnchorView(mAccountHeaderContainer);
+ popup.setAdapter(adapter);
+ popup.setModal(true);
+ popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+ popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ UiClosables.closeQuietly(popup);
+ final AccountWithDataSet newAccount = adapter.getItem(position);
+ setCurrentAccount(newAccount);
+ }
+ });
+ popup.show();
+ }
+ };
+ setUpAccountSelector(nameLabel.toString(), onClickListener);
+ }
+
+ private void setUpAccountSelector(String nameLabel, View.OnClickListener listener) {
+ addAccountHeader(nameLabel);
+ // Add handlers for choosing another account to save to.
+ mAccountHeaderExpanderIcon.setVisibility(View.VISIBLE);
+ mAccountHeaderContainer.setOnClickListener(listener);
+ }
+
+ private String getAccountLabel(AccountDisplayInfo account) {
+ // TODO: if used from editor this would need to be different if editing the user's profile.
+ return account.getNameLabel().toString();
+ }
+}