Loading res/values/strings.xml +1 −10 Original line number Diff line number Diff line Loading @@ -1376,16 +1376,7 @@ <string name="import_from_sim">Import from SIM card</string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary">Import from SIM <xliff:g id="sim_name">^1</xliff:g>\n<xliff:g id="sim_number">^2</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts, without a phone number --> <string name="import_from_sim_summary_no_number">Import from SIM <xliff:g id="sim_name">%1$s</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary_by_carrier">Import from <xliff:g id="carrier_name">^1</xliff:g> SIM\n<xliff:g id="sim_number">^2</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary_by_carrier_no_number">Import from <xliff:g id="carrier_name">%1$s</xliff:g> SIM</string> <string name="import_from_sim_summary_fmt">Import from SIM <xliff:g id="sim_name">%1$s</xliff:g></string> <!-- Action string for selecting a .vcf file to import contacts from [CHAR LIMIT=30] --> <string name="import_from_vcf_file" product="default">Import from .vcf file</string> Loading src/com/android/contacts/ContactSaveService.java +11 −5 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ 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.text.TextUtils; Loading Loading @@ -150,6 +152,7 @@ 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 EXTRA_CALLBACK_DATA = "extraCallbackData"; public static final String EXTRA_RESULT_CODE = "resultCode"; public static final String EXTRA_RESULT_COUNT = "count"; Loading Loading @@ -208,7 +211,7 @@ public class ContactSaveService extends IntentService { public void onCreate() { super.onCreate(); mGroupsDao = new GroupsDaoImpl(this); mSimContactDao = new SimContactDao(this); mSimContactDao = SimContactDao.create(this); } public static void registerListener(Listener listener) { Loading Loading @@ -1685,12 +1688,14 @@ public class ContactSaveService extends IntentService { operations.add(builder.build()); } public static Intent createImportFromSimIntent(Context context, ArrayList<SimContact> contacts, AccountWithDataSet targetAccount) { public static Intent createImportFromSimIntent(@NonNull Context context, @NonNull ArrayList<SimContact> contacts, @NonNull AccountWithDataSet targetAccount, @Nullable Bundle callbackData) { return new Intent(context, ContactSaveService.class) .setAction(ACTION_IMPORT_FROM_SIM) .putExtra(EXTRA_SIM_CONTACTS, contacts) .putExtra(EXTRA_ACCOUNT, targetAccount); .putExtra(EXTRA_ACCOUNT, targetAccount) .putExtra(EXTRA_CALLBACK_DATA, callbackData); } private void importFromSim(Intent intent) { Loading @@ -1704,7 +1709,8 @@ public class ContactSaveService extends IntentService { // notify success LocalBroadcastManager.getInstance(this).sendBroadcast(result .putExtra(EXTRA_RESULT_COUNT, contacts.size()) .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS)); .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS) .putExtra(EXTRA_CALLBACK_DATA, intent.getBundleExtra(EXTRA_CALLBACK_DATA))); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "importFromSim completed successfully"); } Loading src/com/android/contacts/SimImportFragment.java +10 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ 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.SimCard; import com.android.contacts.common.model.SimContact; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.preference.ContactsPreferences; Loading @@ -59,7 +60,8 @@ public class SimImportFragment extends DialogFragment private static final String KEY_SELECTED_IDS = "selectedIds"; private static final String ARG_SUBSCRIPTION_ID = "subscriptionId"; public static final int NO_SUBSCRIPTION_ID = -1; public static final String CALLBACK_KEY_SUBSCRIPTION_ID = "simSubscriptionId"; private ContactsPreferences mPreferences; private AccountTypeManager mAccountTypeManager; Loading Loading @@ -88,8 +90,8 @@ public class SimImportFragment extends DialogFragment mAdapter.setHasHeader(0, false); final Bundle args = getArguments(); mSubscriptionId = args == null ? NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, NO_SUBSCRIPTION_ID); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); if (savedInstanceState == null) return; mSelectedContacts = savedInstanceState.getLongArray(KEY_SELECTED_IDS); Loading Loading @@ -198,9 +200,11 @@ public class SimImportFragment extends DialogFragment } private void importCurrentSelections() { final Bundle callbackData = new Bundle(); callbackData.putInt(CALLBACK_KEY_SUBSCRIPTION_ID, mSubscriptionId); ContactSaveService.startService(getContext(), ContactSaveService .createImportFromSimIntent(getContext(), mAdapter.getSelectedContacts(), mAccountHeaderPresenter.getCurrentAccount())); mAccountHeaderPresenter.getCurrentAccount(), callbackData)); } @Override Loading Loading @@ -321,7 +325,7 @@ public class SimImportFragment extends DialogFragment public SimContactLoader(Context context, int subscriptionId) { super(context); mDao = new SimContactDao(context); mDao = SimContactDao.create(context); mSubscriptionId = subscriptionId; } Loading @@ -342,7 +346,7 @@ public class SimImportFragment extends DialogFragment @Override public ArrayList<SimContact> loadInBackground() { if (mSubscriptionId != NO_SUBSCRIPTION_ID) { if (mSubscriptionId != SimCard.NO_SUBSCRIPTION_ID) { return mDao.loadSimContacts(mSubscriptionId); } else { return mDao.loadSimContacts(); Loading src/com/android/contacts/common/database/SimContactDao.java +131 −56 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.contacts.common.database; import android.annotation.TargetApi; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; Loading @@ -28,25 +29,25 @@ import android.os.Build; import android.os.RemoteException; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.contacts.common.Experiments; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.SimCard; 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.util.SharedPreferenceUtil; import com.android.contactsbind.experiments.Flags; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; /** * Provides data access methods for loading contacts from a SIM card and and migrating these Loading @@ -55,6 +56,10 @@ import java.util.Set; public class SimContactDao { private static final String TAG = "SimContactDao"; // Set to true for manual testing on an emulator or phone without a SIM card // DO NOT SUBMIT if set to true private static final boolean USE_FAKE_INSTANCE = false; @VisibleForTesting public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn"); Loading @@ -76,65 +81,50 @@ public class SimContactDao { public void warmupSimQueryIfNeeded() { // Not needed if we don't have an Assistant section if (!Flags.getInstance().getBoolean(Experiments.ASSISTANT) || !shouldLoad()) return; !canReadSimContacts()) return; new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (SimCard card : getSimCards()) { // We don't actually have to do any caching ourselves. Some other layer must do // caching of the data (OS or framework) because subsequent queries are very fast. final Cursor cursor = mResolver.query(ICC_CONTENT_URI, null, null, null, null); if (cursor != null) { cursor.close(); // caching of the data (OS or framework) because subsequent queries are very // fast. card.loadContacts(SimContactDao.this); } return null; } }.execute(); } public boolean shouldLoad() { final Set<String> simIds = hasTelephony() && hasPermissions() ? getSimCardIds() : Collections.<String>emptySet(); public boolean canReadSimContacts() { return hasTelephony() && hasPermissions() && mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "shouldLoad: hasTelephony? " + hasTelephony() + " hasPermissions? " + hasPermissions() + " SIM absent? " + (getSimState() != TelephonyManager.SIM_STATE_ABSENT) + " SIM ids=" + simIds + " imported=" + SharedPreferenceUtil.getImportedSims(mContext)); public List<SimCard> getSimCards() { if (!canReadSimContacts()) { return Collections.emptyList(); } return hasTelephony() && hasPermissions() && getSimState() != TelephonyManager.SIM_STATE_ABSENT && !Sets.difference(simIds, SharedPreferenceUtil.getImportedSims(mContext)) .isEmpty(); final List<SimCard> sims = CompatUtils.isMSIMCompatible() ? getSimCardsFromSubscriptions() : Collections.singletonList(SimCard.create(mTelephonyManager)); return SharedPreferenceUtil.restoreSimStates(mContext, sims); } public Set<String> getSimCardIds() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); @NonNull @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) private List<SimCard> getSimCardsFromSubscriptions() { final SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); final List<SubscriptionInfo> subscriptions = subscriptionManager .getActiveSubscriptionInfoList(); if (subscriptions == null) { return Collections.emptySet(); } final ArraySet<String> result = new ArraySet<>( subscriptionManager.getActiveSubscriptionInfoCount()); for (SubscriptionInfo info : subscriptions) { result.add(info.getIccId()); final ArrayList<SimCard> result = new ArrayList<>(); for (SubscriptionInfo subscriptionInfo : subscriptions) { result.add(SimCard.create(subscriptionInfo)); } return result; } return Collections.singleton(getSimSerialNumber()); } public int getSimState() { return mTelephonyManager.getSimState(); } public String getSimSerialNumber() { return mTelephonyManager.getSimSerialNumber(); } public ArrayList<SimContact> loadSimContacts(int subscriptionId) { return loadFrom(ICC_CONTENT_URI.buildUpon() Loading @@ -147,6 +137,10 @@ public class SimContactDao { return loadFrom(ICC_CONTENT_URI); } public Context getContext() { return mContext; } private ArrayList<SimContact> loadFrom(Uri uri) { final Cursor cursor = mResolver.query(uri, null, null, null, null); Loading Loading @@ -185,6 +179,31 @@ public class SimContactDao { return mResolver.applyBatch(ContactsContract.AUTHORITY, ops); } public void persistSimState(SimCard sim) { SharedPreferenceUtil.persistSimStates(mContext, Collections.singletonList(sim)); } public void persistSimStates(List<SimCard> simCards) { SharedPreferenceUtil.persistSimStates(mContext, simCards); } public SimCard getFirstSimCard() { return getSimBySubscriptionId(SimCard.NO_SUBSCRIPTION_ID); } public SimCard getSimBySubscriptionId(int subscriptionId) { final List<SimCard> sims = getSimCards(); if (subscriptionId == SimCard.NO_SUBSCRIPTION_ID && !sims.isEmpty()) { return sims.get(0); } for (SimCard sim : getSimCards()) { if (sim.getSubscriptionId() == subscriptionId) { return sim; } } return null; } private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts, AccountWithDataSet targetAccount) { final ArrayList<ContentProviderOperation> ops = new ArrayList<>(); Loading @@ -198,15 +217,6 @@ public class SimContactDao { return emails != null ? emails.split(",") : null; } public void persistImportSuccess() { // TODO: either need to have an assistant card per SIM card or show contacts from all // SIMs in the import view. final Set<String> simIds = getSimCardIds(); for (String id : simIds) { SharedPreferenceUtil.addImportedSim(mContext, id); } } private boolean hasTelephony() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); } Loading @@ -215,4 +225,69 @@ public class SimContactDao { return PermissionsUtil.hasContactsPermissions(mContext) && PermissionsUtil.hasPhonePermissions(mContext); } public static SimContactDao create(Context context) { if (USE_FAKE_INSTANCE) { return new DebugImpl(context) .addSimCard(new SimCard("fake-sim-id1", 1, "Fake Carrier", "Card 1", "15095550101", "us").withContacts( new SimContact(1, "Sim One", "15095550111", null), new SimContact(2, "Sim Two", "15095550112", null), new SimContact(3, "Sim Three", "15095550113", null), new SimContact(4, "Sim Four", "15095550114", null), new SimContact(5, "411 & more", "411", null) )) .addSimCard(new SimCard("fake-sim-id2", 2, "Carrier Two", "Card 2", "15095550102", "us").withContacts( new SimContact(1, "John Sim", "15095550121", null), new SimContact(2, "Bob Sim", "15095550122", null), new SimContact(3, "Mary Sim", "15095550123", null), new SimContact(4, "Alice Sim", "15095550124", null) )); } return new SimContactDao(context); } // TODO remove this class and the USE_FAKE_INSTANCE flag once this code is not under // active development or anytime after 3/1/2017 private static class DebugImpl extends SimContactDao { private List<SimCard> mSimCards = new ArrayList<>(); private SparseArray<SimCard> mCardsBySubscription = new SparseArray<>(); public DebugImpl(Context context) { super(context); } public DebugImpl addSimCard(SimCard sim) { mSimCards.add(sim); mCardsBySubscription.put(sim.getSubscriptionId(), sim); return this; } @Override public void warmupSimQueryIfNeeded() { } @Override public List<SimCard> getSimCards() { return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards); } @Override public ArrayList<SimContact> loadSimContacts() { return new ArrayList<>(mSimCards.get(0).getContacts()); } @Override public ArrayList<SimContact> loadSimContacts(int subscriptionId) { return new ArrayList<>(mCardsBySubscription.get(subscriptionId).getContacts()); } @Override public boolean canReadSimContacts() { return true; } } } src/com/android/contacts/common/interactions/ImportDialogFragment.java +36 −100 Original line number Diff line number Diff line Loading @@ -24,14 +24,10 @@ import android.app.FragmentManager; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract.Contacts; import android.support.annotation.RequiresApi; import android.support.v4.util.ArraySet; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; Loading @@ -44,15 +40,15 @@ 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; import com.android.contacts.common.database.SimContactDao; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.SimCard; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.AccountSelectionUtil; import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.editor.SelectAccountDialogFragment; import java.util.List; import java.util.Locale; import java.util.Set; /** * An dialog invoked to import/export contacts. Loading @@ -67,6 +63,7 @@ public class ImportDialogFragment extends DialogFragment public static final String EXTRA_SIM_ONLY = "extraSimOnly"; private boolean mSimOnly = false; private SimContactDao mSimDao; private final String[] LOOKUP_PROJECTION = new String[] { Contacts.LOOKUP_KEY Loading Loading @@ -97,6 +94,7 @@ public class ImportDialogFragment extends DialogFragment final Bundle args = getArguments(); mSimOnly = args != null && args.getBoolean(EXTRA_SIM_ONLY, false); mSimDao = SimContactDao.create(getContext()); } @Override Loading @@ -112,7 +110,6 @@ public class ImportDialogFragment extends DialogFragment @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Wrap our context to inflate list items using the correct theme final Resources res = getActivity().getResources(); final LayoutInflater dialogInflater = (LayoutInflater)getActivity() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); Loading @@ -129,48 +126,7 @@ public class ImportDialogFragment extends DialogFragment } }; final TelephonyManager manager = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); if (res.getBoolean(R.bool.config_allow_import_from_vcf_file) && !mSimOnly) { adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), R.string.import_from_vcf_file)); } if (CompatUtils.isMSIMCompatible()) { mSubscriptionManager = SubscriptionManager.from(getActivity()); if (manager != null && res.getBoolean(R.bool.config_allow_sim_import)) { List<SubscriptionInfo> subInfoRecords = null; try { subInfoRecords = mSubscriptionManager.getActiveSubscriptionInfoList(); } catch (SecurityException e) { Log.w(TAG, "SecurityException thrown, lack permission for" + " getActiveSubscriptionInfoList", e); } if (subInfoRecords != null) { if (subInfoRecords.size() == 1) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, subInfoRecords.get(0).getSubscriptionId())); } else if (hasUniqueNonNullCarrierNames(subInfoRecords)) { for (SubscriptionInfo record : subInfoRecords) { adapter.add(new AdapterEntry(getSubDescriptionForCarrier(record), R.string.import_from_sim, record.getSubscriptionId())); } } else { for (SubscriptionInfo record : subInfoRecords) { adapter.add(new AdapterEntry(getSubDescription(record), R.string.import_from_sim, record.getSubscriptionId())); } } } } } else { if (manager != null && manager.hasIccCard() && res.getBoolean(R.bool.config_allow_sim_import)) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, -1)); } } addItems(adapter); final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { Loading Loading @@ -201,6 +157,25 @@ public class ImportDialogFragment extends DialogFragment .create(); } private void addItems(ArrayAdapter<AdapterEntry> adapter) { final Resources res = getActivity().getResources(); if (res.getBoolean(R.bool.config_allow_import_from_vcf_file) && !mSimOnly) { adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), R.string.import_from_vcf_file)); } final List<SimCard> sims = mSimDao.getSimCards(); if (sims.size() == 1) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, SimCard.NO_SUBSCRIPTION_ID)); return; } for (SimCard sim : sims) { adapter.add(new AdapterEntry(getSimDescription(sim), R.string.import_from_sim, sim.getSubscriptionId())); } } private boolean handleSimImportRequest(int subscriptionId) { SimImportFragment.newInstance(subscriptionId).show(getFragmentManager(), "SimImport"); return true; Loading Loading @@ -260,62 +235,23 @@ public class ImportDialogFragment extends DialogFragment dismiss(); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) private boolean hasUniqueNonNullCarrierNames(List<SubscriptionInfo> subscriptions) { final Set<CharSequence> names = new ArraySet<>(); for (SubscriptionInfo subscription : subscriptions) { final CharSequence name = subscription.getCarrierName(); if (name == null) { return false; } if (!names.add(name)) { return false; private CharSequence getSimDescription(SimCard sim) { final CharSequence name = sim.getDisplayName(); CharSequence number = sim.getFormattedPhone(); // If formatting fails use the raw phone number if (number == null) { number = sim.getPhone(); } if (number != null) { number = PhoneNumberUtilsCompat.createTtsSpannable(number); } return true; } private CharSequence getSubDescription(SubscriptionInfo record) { final CharSequence name = record.getDisplayName(); final CharSequence number = getFormattedNumber(record); if (TextUtils.isEmpty(number)) { return getString(R.string.import_from_sim_summary_no_number, name); return getString(R.string.import_from_sim_summary_fmt, name); } return TextUtils.expandTemplate(getString(R.string.import_from_sim_summary), name, number); return new SpannableStringBuilder(getString(R.string.import_from_sim_summary_fmt, name)) .append('\n').append(number); } private CharSequence getSubDescriptionForCarrier(SubscriptionInfo record) { final CharSequence carrierName = record.getCarrierName(); final CharSequence number = getFormattedNumber(record); if (TextUtils.isEmpty(number)) { return getString(R.string.import_from_sim_summary_by_carrier_no_number, carrierName); } return TextUtils.expandTemplate( getString(R.string.import_from_sim_summary_by_carrier), carrierName, number); } private CharSequence getFormattedNumber(SubscriptionInfo subscription) { final String rawNumber = subscription.getNumber(); if (rawNumber == null) { return null; } final String country = subscription.getCountryIso(); final String number; if (country != null) { number = PhoneNumberUtilsCompat.formatNumber(rawNumber, null, country.toUpperCase(Locale.US)); } else { number = rawNumber; } return PhoneNumberUtilsCompat.createTtsSpannable(number); } private static class AdapterEntry { public final CharSequence mLabel; public final int mChoiceResourceId; Loading Loading
res/values/strings.xml +1 −10 Original line number Diff line number Diff line Loading @@ -1376,16 +1376,7 @@ <string name="import_from_sim">Import from SIM card</string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary">Import from SIM <xliff:g id="sim_name">^1</xliff:g>\n<xliff:g id="sim_number">^2</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts, without a phone number --> <string name="import_from_sim_summary_no_number">Import from SIM <xliff:g id="sim_name">%1$s</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary_by_carrier">Import from <xliff:g id="carrier_name">^1</xliff:g> SIM\n<xliff:g id="sim_number">^2</xliff:g></string> <!-- Action string for selecting a SIM subscription for importing contacts --> <string name="import_from_sim_summary_by_carrier_no_number">Import from <xliff:g id="carrier_name">%1$s</xliff:g> SIM</string> <string name="import_from_sim_summary_fmt">Import from SIM <xliff:g id="sim_name">%1$s</xliff:g></string> <!-- Action string for selecting a .vcf file to import contacts from [CHAR LIMIT=30] --> <string name="import_from_vcf_file" product="default">Import from .vcf file</string> Loading
src/com/android/contacts/ContactSaveService.java +11 −5 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ 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.text.TextUtils; Loading Loading @@ -150,6 +152,7 @@ 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 EXTRA_CALLBACK_DATA = "extraCallbackData"; public static final String EXTRA_RESULT_CODE = "resultCode"; public static final String EXTRA_RESULT_COUNT = "count"; Loading Loading @@ -208,7 +211,7 @@ public class ContactSaveService extends IntentService { public void onCreate() { super.onCreate(); mGroupsDao = new GroupsDaoImpl(this); mSimContactDao = new SimContactDao(this); mSimContactDao = SimContactDao.create(this); } public static void registerListener(Listener listener) { Loading Loading @@ -1685,12 +1688,14 @@ public class ContactSaveService extends IntentService { operations.add(builder.build()); } public static Intent createImportFromSimIntent(Context context, ArrayList<SimContact> contacts, AccountWithDataSet targetAccount) { public static Intent createImportFromSimIntent(@NonNull Context context, @NonNull ArrayList<SimContact> contacts, @NonNull AccountWithDataSet targetAccount, @Nullable Bundle callbackData) { return new Intent(context, ContactSaveService.class) .setAction(ACTION_IMPORT_FROM_SIM) .putExtra(EXTRA_SIM_CONTACTS, contacts) .putExtra(EXTRA_ACCOUNT, targetAccount); .putExtra(EXTRA_ACCOUNT, targetAccount) .putExtra(EXTRA_CALLBACK_DATA, callbackData); } private void importFromSim(Intent intent) { Loading @@ -1704,7 +1709,8 @@ public class ContactSaveService extends IntentService { // notify success LocalBroadcastManager.getInstance(this).sendBroadcast(result .putExtra(EXTRA_RESULT_COUNT, contacts.size()) .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS)); .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS) .putExtra(EXTRA_CALLBACK_DATA, intent.getBundleExtra(EXTRA_CALLBACK_DATA))); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "importFromSim completed successfully"); } Loading
src/com/android/contacts/SimImportFragment.java +10 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ 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.SimCard; import com.android.contacts.common.model.SimContact; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.preference.ContactsPreferences; Loading @@ -59,7 +60,8 @@ public class SimImportFragment extends DialogFragment private static final String KEY_SELECTED_IDS = "selectedIds"; private static final String ARG_SUBSCRIPTION_ID = "subscriptionId"; public static final int NO_SUBSCRIPTION_ID = -1; public static final String CALLBACK_KEY_SUBSCRIPTION_ID = "simSubscriptionId"; private ContactsPreferences mPreferences; private AccountTypeManager mAccountTypeManager; Loading Loading @@ -88,8 +90,8 @@ public class SimImportFragment extends DialogFragment mAdapter.setHasHeader(0, false); final Bundle args = getArguments(); mSubscriptionId = args == null ? NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, NO_SUBSCRIPTION_ID); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); if (savedInstanceState == null) return; mSelectedContacts = savedInstanceState.getLongArray(KEY_SELECTED_IDS); Loading Loading @@ -198,9 +200,11 @@ public class SimImportFragment extends DialogFragment } private void importCurrentSelections() { final Bundle callbackData = new Bundle(); callbackData.putInt(CALLBACK_KEY_SUBSCRIPTION_ID, mSubscriptionId); ContactSaveService.startService(getContext(), ContactSaveService .createImportFromSimIntent(getContext(), mAdapter.getSelectedContacts(), mAccountHeaderPresenter.getCurrentAccount())); mAccountHeaderPresenter.getCurrentAccount(), callbackData)); } @Override Loading Loading @@ -321,7 +325,7 @@ public class SimImportFragment extends DialogFragment public SimContactLoader(Context context, int subscriptionId) { super(context); mDao = new SimContactDao(context); mDao = SimContactDao.create(context); mSubscriptionId = subscriptionId; } Loading @@ -342,7 +346,7 @@ public class SimImportFragment extends DialogFragment @Override public ArrayList<SimContact> loadInBackground() { if (mSubscriptionId != NO_SUBSCRIPTION_ID) { if (mSubscriptionId != SimCard.NO_SUBSCRIPTION_ID) { return mDao.loadSimContacts(mSubscriptionId); } else { return mDao.loadSimContacts(); Loading
src/com/android/contacts/common/database/SimContactDao.java +131 −56 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.contacts.common.database; import android.annotation.TargetApi; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; Loading @@ -28,25 +29,25 @@ import android.os.Build; import android.os.RemoteException; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.contacts.common.Experiments; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.SimCard; 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.util.SharedPreferenceUtil; import com.android.contactsbind.experiments.Flags; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; /** * Provides data access methods for loading contacts from a SIM card and and migrating these Loading @@ -55,6 +56,10 @@ import java.util.Set; public class SimContactDao { private static final String TAG = "SimContactDao"; // Set to true for manual testing on an emulator or phone without a SIM card // DO NOT SUBMIT if set to true private static final boolean USE_FAKE_INSTANCE = false; @VisibleForTesting public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn"); Loading @@ -76,65 +81,50 @@ public class SimContactDao { public void warmupSimQueryIfNeeded() { // Not needed if we don't have an Assistant section if (!Flags.getInstance().getBoolean(Experiments.ASSISTANT) || !shouldLoad()) return; !canReadSimContacts()) return; new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (SimCard card : getSimCards()) { // We don't actually have to do any caching ourselves. Some other layer must do // caching of the data (OS or framework) because subsequent queries are very fast. final Cursor cursor = mResolver.query(ICC_CONTENT_URI, null, null, null, null); if (cursor != null) { cursor.close(); // caching of the data (OS or framework) because subsequent queries are very // fast. card.loadContacts(SimContactDao.this); } return null; } }.execute(); } public boolean shouldLoad() { final Set<String> simIds = hasTelephony() && hasPermissions() ? getSimCardIds() : Collections.<String>emptySet(); public boolean canReadSimContacts() { return hasTelephony() && hasPermissions() && mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "shouldLoad: hasTelephony? " + hasTelephony() + " hasPermissions? " + hasPermissions() + " SIM absent? " + (getSimState() != TelephonyManager.SIM_STATE_ABSENT) + " SIM ids=" + simIds + " imported=" + SharedPreferenceUtil.getImportedSims(mContext)); public List<SimCard> getSimCards() { if (!canReadSimContacts()) { return Collections.emptyList(); } return hasTelephony() && hasPermissions() && getSimState() != TelephonyManager.SIM_STATE_ABSENT && !Sets.difference(simIds, SharedPreferenceUtil.getImportedSims(mContext)) .isEmpty(); final List<SimCard> sims = CompatUtils.isMSIMCompatible() ? getSimCardsFromSubscriptions() : Collections.singletonList(SimCard.create(mTelephonyManager)); return SharedPreferenceUtil.restoreSimStates(mContext, sims); } public Set<String> getSimCardIds() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); @NonNull @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) private List<SimCard> getSimCardsFromSubscriptions() { final SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); final List<SubscriptionInfo> subscriptions = subscriptionManager .getActiveSubscriptionInfoList(); if (subscriptions == null) { return Collections.emptySet(); } final ArraySet<String> result = new ArraySet<>( subscriptionManager.getActiveSubscriptionInfoCount()); for (SubscriptionInfo info : subscriptions) { result.add(info.getIccId()); final ArrayList<SimCard> result = new ArrayList<>(); for (SubscriptionInfo subscriptionInfo : subscriptions) { result.add(SimCard.create(subscriptionInfo)); } return result; } return Collections.singleton(getSimSerialNumber()); } public int getSimState() { return mTelephonyManager.getSimState(); } public String getSimSerialNumber() { return mTelephonyManager.getSimSerialNumber(); } public ArrayList<SimContact> loadSimContacts(int subscriptionId) { return loadFrom(ICC_CONTENT_URI.buildUpon() Loading @@ -147,6 +137,10 @@ public class SimContactDao { return loadFrom(ICC_CONTENT_URI); } public Context getContext() { return mContext; } private ArrayList<SimContact> loadFrom(Uri uri) { final Cursor cursor = mResolver.query(uri, null, null, null, null); Loading Loading @@ -185,6 +179,31 @@ public class SimContactDao { return mResolver.applyBatch(ContactsContract.AUTHORITY, ops); } public void persistSimState(SimCard sim) { SharedPreferenceUtil.persistSimStates(mContext, Collections.singletonList(sim)); } public void persistSimStates(List<SimCard> simCards) { SharedPreferenceUtil.persistSimStates(mContext, simCards); } public SimCard getFirstSimCard() { return getSimBySubscriptionId(SimCard.NO_SUBSCRIPTION_ID); } public SimCard getSimBySubscriptionId(int subscriptionId) { final List<SimCard> sims = getSimCards(); if (subscriptionId == SimCard.NO_SUBSCRIPTION_ID && !sims.isEmpty()) { return sims.get(0); } for (SimCard sim : getSimCards()) { if (sim.getSubscriptionId() == subscriptionId) { return sim; } } return null; } private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts, AccountWithDataSet targetAccount) { final ArrayList<ContentProviderOperation> ops = new ArrayList<>(); Loading @@ -198,15 +217,6 @@ public class SimContactDao { return emails != null ? emails.split(",") : null; } public void persistImportSuccess() { // TODO: either need to have an assistant card per SIM card or show contacts from all // SIMs in the import view. final Set<String> simIds = getSimCardIds(); for (String id : simIds) { SharedPreferenceUtil.addImportedSim(mContext, id); } } private boolean hasTelephony() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); } Loading @@ -215,4 +225,69 @@ public class SimContactDao { return PermissionsUtil.hasContactsPermissions(mContext) && PermissionsUtil.hasPhonePermissions(mContext); } public static SimContactDao create(Context context) { if (USE_FAKE_INSTANCE) { return new DebugImpl(context) .addSimCard(new SimCard("fake-sim-id1", 1, "Fake Carrier", "Card 1", "15095550101", "us").withContacts( new SimContact(1, "Sim One", "15095550111", null), new SimContact(2, "Sim Two", "15095550112", null), new SimContact(3, "Sim Three", "15095550113", null), new SimContact(4, "Sim Four", "15095550114", null), new SimContact(5, "411 & more", "411", null) )) .addSimCard(new SimCard("fake-sim-id2", 2, "Carrier Two", "Card 2", "15095550102", "us").withContacts( new SimContact(1, "John Sim", "15095550121", null), new SimContact(2, "Bob Sim", "15095550122", null), new SimContact(3, "Mary Sim", "15095550123", null), new SimContact(4, "Alice Sim", "15095550124", null) )); } return new SimContactDao(context); } // TODO remove this class and the USE_FAKE_INSTANCE flag once this code is not under // active development or anytime after 3/1/2017 private static class DebugImpl extends SimContactDao { private List<SimCard> mSimCards = new ArrayList<>(); private SparseArray<SimCard> mCardsBySubscription = new SparseArray<>(); public DebugImpl(Context context) { super(context); } public DebugImpl addSimCard(SimCard sim) { mSimCards.add(sim); mCardsBySubscription.put(sim.getSubscriptionId(), sim); return this; } @Override public void warmupSimQueryIfNeeded() { } @Override public List<SimCard> getSimCards() { return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards); } @Override public ArrayList<SimContact> loadSimContacts() { return new ArrayList<>(mSimCards.get(0).getContacts()); } @Override public ArrayList<SimContact> loadSimContacts(int subscriptionId) { return new ArrayList<>(mCardsBySubscription.get(subscriptionId).getContacts()); } @Override public boolean canReadSimContacts() { return true; } } }
src/com/android/contacts/common/interactions/ImportDialogFragment.java +36 −100 Original line number Diff line number Diff line Loading @@ -24,14 +24,10 @@ import android.app.FragmentManager; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract.Contacts; import android.support.annotation.RequiresApi; import android.support.v4.util.ArraySet; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; Loading @@ -44,15 +40,15 @@ 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; import com.android.contacts.common.database.SimContactDao; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.SimCard; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.AccountSelectionUtil; import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.editor.SelectAccountDialogFragment; import java.util.List; import java.util.Locale; import java.util.Set; /** * An dialog invoked to import/export contacts. Loading @@ -67,6 +63,7 @@ public class ImportDialogFragment extends DialogFragment public static final String EXTRA_SIM_ONLY = "extraSimOnly"; private boolean mSimOnly = false; private SimContactDao mSimDao; private final String[] LOOKUP_PROJECTION = new String[] { Contacts.LOOKUP_KEY Loading Loading @@ -97,6 +94,7 @@ public class ImportDialogFragment extends DialogFragment final Bundle args = getArguments(); mSimOnly = args != null && args.getBoolean(EXTRA_SIM_ONLY, false); mSimDao = SimContactDao.create(getContext()); } @Override Loading @@ -112,7 +110,6 @@ public class ImportDialogFragment extends DialogFragment @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Wrap our context to inflate list items using the correct theme final Resources res = getActivity().getResources(); final LayoutInflater dialogInflater = (LayoutInflater)getActivity() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); Loading @@ -129,48 +126,7 @@ public class ImportDialogFragment extends DialogFragment } }; final TelephonyManager manager = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); if (res.getBoolean(R.bool.config_allow_import_from_vcf_file) && !mSimOnly) { adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), R.string.import_from_vcf_file)); } if (CompatUtils.isMSIMCompatible()) { mSubscriptionManager = SubscriptionManager.from(getActivity()); if (manager != null && res.getBoolean(R.bool.config_allow_sim_import)) { List<SubscriptionInfo> subInfoRecords = null; try { subInfoRecords = mSubscriptionManager.getActiveSubscriptionInfoList(); } catch (SecurityException e) { Log.w(TAG, "SecurityException thrown, lack permission for" + " getActiveSubscriptionInfoList", e); } if (subInfoRecords != null) { if (subInfoRecords.size() == 1) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, subInfoRecords.get(0).getSubscriptionId())); } else if (hasUniqueNonNullCarrierNames(subInfoRecords)) { for (SubscriptionInfo record : subInfoRecords) { adapter.add(new AdapterEntry(getSubDescriptionForCarrier(record), R.string.import_from_sim, record.getSubscriptionId())); } } else { for (SubscriptionInfo record : subInfoRecords) { adapter.add(new AdapterEntry(getSubDescription(record), R.string.import_from_sim, record.getSubscriptionId())); } } } } } else { if (manager != null && manager.hasIccCard() && res.getBoolean(R.bool.config_allow_sim_import)) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, -1)); } } addItems(adapter); final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { Loading Loading @@ -201,6 +157,25 @@ public class ImportDialogFragment extends DialogFragment .create(); } private void addItems(ArrayAdapter<AdapterEntry> adapter) { final Resources res = getActivity().getResources(); if (res.getBoolean(R.bool.config_allow_import_from_vcf_file) && !mSimOnly) { adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), R.string.import_from_vcf_file)); } final List<SimCard> sims = mSimDao.getSimCards(); if (sims.size() == 1) { adapter.add(new AdapterEntry(getString(R.string.import_from_sim), R.string.import_from_sim, SimCard.NO_SUBSCRIPTION_ID)); return; } for (SimCard sim : sims) { adapter.add(new AdapterEntry(getSimDescription(sim), R.string.import_from_sim, sim.getSubscriptionId())); } } private boolean handleSimImportRequest(int subscriptionId) { SimImportFragment.newInstance(subscriptionId).show(getFragmentManager(), "SimImport"); return true; Loading Loading @@ -260,62 +235,23 @@ public class ImportDialogFragment extends DialogFragment dismiss(); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) private boolean hasUniqueNonNullCarrierNames(List<SubscriptionInfo> subscriptions) { final Set<CharSequence> names = new ArraySet<>(); for (SubscriptionInfo subscription : subscriptions) { final CharSequence name = subscription.getCarrierName(); if (name == null) { return false; } if (!names.add(name)) { return false; private CharSequence getSimDescription(SimCard sim) { final CharSequence name = sim.getDisplayName(); CharSequence number = sim.getFormattedPhone(); // If formatting fails use the raw phone number if (number == null) { number = sim.getPhone(); } if (number != null) { number = PhoneNumberUtilsCompat.createTtsSpannable(number); } return true; } private CharSequence getSubDescription(SubscriptionInfo record) { final CharSequence name = record.getDisplayName(); final CharSequence number = getFormattedNumber(record); if (TextUtils.isEmpty(number)) { return getString(R.string.import_from_sim_summary_no_number, name); return getString(R.string.import_from_sim_summary_fmt, name); } return TextUtils.expandTemplate(getString(R.string.import_from_sim_summary), name, number); return new SpannableStringBuilder(getString(R.string.import_from_sim_summary_fmt, name)) .append('\n').append(number); } private CharSequence getSubDescriptionForCarrier(SubscriptionInfo record) { final CharSequence carrierName = record.getCarrierName(); final CharSequence number = getFormattedNumber(record); if (TextUtils.isEmpty(number)) { return getString(R.string.import_from_sim_summary_by_carrier_no_number, carrierName); } return TextUtils.expandTemplate( getString(R.string.import_from_sim_summary_by_carrier), carrierName, number); } private CharSequence getFormattedNumber(SubscriptionInfo subscription) { final String rawNumber = subscription.getNumber(); if (rawNumber == null) { return null; } final String country = subscription.getCountryIso(); final String number; if (country != null) { number = PhoneNumberUtilsCompat.formatNumber(rawNumber, null, country.toUpperCase(Locale.US)); } else { number = rawNumber; } return PhoneNumberUtilsCompat.createTtsSpannable(number); } private static class AdapterEntry { public final CharSequence mLabel; public final int mChoiceResourceId; Loading