ImportExportDialogFragment.java revision 0a614a1c8f7fd866521cfb1d488d9509e6e0e930
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.contacts.common.interactions; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.DialogFragment; 23import android.app.FragmentManager; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.Intent; 27import android.content.res.Resources; 28import android.database.Cursor; 29import android.net.Uri; 30import android.os.Bundle; 31import android.provider.ContactsContract.Contacts; 32import android.telephony.PhoneNumberUtils; 33import android.telephony.SubscriptionInfo; 34import android.telephony.SubscriptionManager; 35import android.telephony.TelephonyManager; 36import android.text.TextUtils; 37import android.util.Log; 38import android.view.LayoutInflater; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.ArrayAdapter; 42import android.widget.TextView; 43import android.widget.Toast; 44 45import com.android.contacts.common.R; 46import com.android.contacts.common.editor.SelectAccountDialogFragment; 47import com.android.contacts.common.model.AccountTypeManager; 48import com.android.contacts.common.model.account.AccountWithDataSet; 49import com.android.contacts.common.util.AccountSelectionUtil; 50import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; 51import com.android.contacts.common.util.ImplicitIntentsUtil; 52import com.android.contacts.common.vcard.ExportVCardActivity; 53import com.android.contacts.common.vcard.VCardCommonArguments; 54import com.android.contacts.commonbind.analytics.AnalyticsUtil; 55 56import java.util.List; 57 58/** 59 * An dialog invoked to import/export contacts. 60 */ 61public class ImportExportDialogFragment extends DialogFragment 62 implements SelectAccountDialogFragment.Listener { 63 public static final String TAG = "ImportExportDialogFragment"; 64 65 public static final int EXPORT_MODE_FAVORITES = 0; 66 public static final int EXPORT_MODE_ALL_CONTACTS = 1; 67 public static final int EXPORT_MODE_DEFAULT = -1; 68 69 private static final String KEY_RES_ID = "resourceId"; 70 private static final String KEY_SUBSCRIPTION_ID = "subscriptionId"; 71 private static final String ARG_CONTACTS_ARE_AVAILABLE = "CONTACTS_ARE_AVAILABLE"; 72 73 private static int mExportMode = EXPORT_MODE_DEFAULT; 74 75 private final String[] LOOKUP_PROJECTION = new String[] { 76 Contacts.LOOKUP_KEY 77 }; 78 79 private SubscriptionManager mSubscriptionManager; 80 81 /** Preferred way to show this dialog */ 82 public static void show(FragmentManager fragmentManager, boolean contactsAreAvailable, 83 Class callingActivity, int exportMode) { 84 final ImportExportDialogFragment fragment = new ImportExportDialogFragment(); 85 Bundle args = new Bundle(); 86 args.putBoolean(ARG_CONTACTS_ARE_AVAILABLE, contactsAreAvailable); 87 args.putString(VCardCommonArguments.ARG_CALLING_ACTIVITY, callingActivity.getName()); 88 fragment.setArguments(args); 89 fragment.show(fragmentManager, ImportExportDialogFragment.TAG); 90 mExportMode = exportMode; 91 } 92 93 @Override 94 public Context getContext() { 95 return getActivity(); 96 } 97 98 @Override 99 public void onAttach(Activity activity) { 100 super.onAttach(activity); 101 AnalyticsUtil.sendScreenView(this); 102 } 103 104 @Override 105 public Dialog onCreateDialog(Bundle savedInstanceState) { 106 // Wrap our context to inflate list items using the correct theme 107 final Resources res = getActivity().getResources(); 108 final LayoutInflater dialogInflater = (LayoutInflater)getActivity() 109 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 110 final boolean contactsAreAvailable = getArguments().getBoolean(ARG_CONTACTS_ARE_AVAILABLE); 111 final String callingActivity = getArguments().getString( 112 VCardCommonArguments.ARG_CALLING_ACTIVITY); 113 114 // Adapter that shows a list of string resources 115 final ArrayAdapter<AdapterEntry> adapter = new ArrayAdapter<AdapterEntry>(getActivity(), 116 R.layout.select_dialog_item) { 117 @Override 118 public View getView(int position, View convertView, ViewGroup parent) { 119 final TextView result = (TextView)(convertView != null ? convertView : 120 dialogInflater.inflate(R.layout.select_dialog_item, parent, false)); 121 122 result.setText(getItem(position).mLabel); 123 return result; 124 } 125 }; 126 127 final TelephonyManager manager = 128 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); 129 130 mSubscriptionManager = SubscriptionManager.from(getActivity()); 131 132 if (res.getBoolean(R.bool.config_allow_import_from_vcf_file)) { 133 adapter.add(new AdapterEntry(getString(R.string.import_from_vcf_file), 134 R.string.import_from_vcf_file)); 135 } 136 if (manager != null && res.getBoolean(R.bool.config_allow_sim_import)) { 137 List<SubscriptionInfo> subInfoRecords = null; 138 try { 139 subInfoRecords = mSubscriptionManager.getActiveSubscriptionInfoList(); 140 } catch (SecurityException e) { 141 Log.w(TAG, "SecurityException thrown, lack permission for" 142 + " getActiveSubscriptionInfoList", e); 143 } 144 if (subInfoRecords != null) { 145 if (subInfoRecords.size() == 1) { 146 adapter.add(new AdapterEntry(getString(R.string.import_from_sim), 147 R.string.import_from_sim, subInfoRecords.get(0).getSubscriptionId())); 148 } else { 149 for (SubscriptionInfo record : subInfoRecords) { 150 adapter.add(new AdapterEntry(getSubDescription(record), 151 R.string.import_from_sim, record.getSubscriptionId())); 152 } 153 } 154 } 155 } 156 if (res.getBoolean(R.bool.config_allow_export)) { 157 if (contactsAreAvailable) { 158 adapter.add(new AdapterEntry(getString(R.string.export_to_vcf_file), 159 R.string.export_to_vcf_file)); 160 } 161 } 162 if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) { 163 if (contactsAreAvailable) { 164 adapter.add(new AdapterEntry(getString(R.string.share_visible_contacts), 165 R.string.share_visible_contacts)); 166 } 167 } 168 169 final DialogInterface.OnClickListener clickListener = 170 new DialogInterface.OnClickListener() { 171 @Override 172 public void onClick(DialogInterface dialog, int which) { 173 boolean dismissDialog; 174 final int resId = adapter.getItem(which).mChoiceResourceId; 175 switch (resId) { 176 case R.string.import_from_sim: 177 case R.string.import_from_vcf_file: { 178 dismissDialog = handleImportRequest(resId, 179 adapter.getItem(which).mSubscriptionId); 180 break; 181 } 182 case R.string.export_to_vcf_file: { 183 dismissDialog = true; 184 Intent exportIntent = new Intent(getActivity(), ExportVCardActivity.class); 185 exportIntent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY, 186 callingActivity); 187 getActivity().startActivity(exportIntent); 188 break; 189 } 190 case R.string.share_visible_contacts: { 191 dismissDialog = true; 192 doShareVisibleContacts(); 193 break; 194 } 195 default: { 196 dismissDialog = true; 197 Log.e(TAG, "Unexpected resource: " 198 + getActivity().getResources().getResourceEntryName(resId)); 199 } 200 } 201 if (dismissDialog) { 202 dialog.dismiss(); 203 } 204 } 205 }; 206 return new AlertDialog.Builder(getActivity()) 207 .setTitle(contactsAreAvailable 208 ? R.string.dialog_import_export 209 : R.string.dialog_import) 210 .setSingleChoiceItems(adapter, -1, clickListener) 211 .create(); 212 } 213 214 private void doShareVisibleContacts() { 215 try { 216 // TODO move the query into a loader and do this in a background thread 217 final Cursor cursor; 218 if (mExportMode == EXPORT_MODE_FAVORITES) { 219 cursor = getActivity().getContentResolver().query(Contacts.CONTENT_STREQUENT_URI, 220 LOOKUP_PROJECTION, null, null, 221 Contacts.DISPLAY_NAME + " COLLATE NOCASE ASC"); 222 } else { // EXPORT_MODE_ALL_CONTACTS 223 cursor = getActivity().getContentResolver().query(Contacts.CONTENT_URI, 224 LOOKUP_PROJECTION, Contacts.IN_VISIBLE_GROUP + "!=0", null, null); 225 } 226 if (cursor != null) { 227 try { 228 if (!cursor.moveToFirst()) { 229 Toast.makeText(getActivity(), R.string.no_contact_to_share, 230 Toast.LENGTH_SHORT).show(); 231 return; 232 } 233 234 StringBuilder uriListBuilder = new StringBuilder(); 235 int index = 0; 236 do { 237 if (index != 0) 238 uriListBuilder.append(':'); 239 uriListBuilder.append(cursor.getString(0)); 240 index++; 241 } while (cursor.moveToNext()); 242 Uri uri = Uri.withAppendedPath( 243 Contacts.CONTENT_MULTI_VCARD_URI, 244 Uri.encode(uriListBuilder.toString())); 245 246 final Intent intent = new Intent(Intent.ACTION_SEND); 247 intent.setType(Contacts.CONTENT_VCARD_TYPE); 248 intent.putExtra(Intent.EXTRA_STREAM, uri); 249 ImplicitIntentsUtil.startActivityOutsideApp(getActivity(), intent); 250 } finally { 251 cursor.close(); 252 } 253 } 254 } catch (Exception e) { 255 Log.e(TAG, "Sharing visible contacts failed", e); 256 Toast.makeText(getContext(), R.string.share_visible_contacts_failure, 257 Toast.LENGTH_SHORT).show(); 258 } 259 } 260 261 /** 262 * Handle "import from SIM" and "import from SD". 263 * 264 * @return {@code true} if the dialog show be closed. {@code false} otherwise. 265 */ 266 private boolean handleImportRequest(int resId, int subscriptionId) { 267 // There are three possibilities: 268 // - more than one accounts -> ask the user 269 // - just one account -> use the account without asking the user 270 // - no account -> use phone-local storage without asking the user 271 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity()); 272 final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true); 273 final int size = accountList.size(); 274 if (size > 1) { 275 // Send over to the account selector 276 final Bundle args = new Bundle(); 277 args.putInt(KEY_RES_ID, resId); 278 args.putInt(KEY_SUBSCRIPTION_ID, subscriptionId); 279 SelectAccountDialogFragment.show( 280 getFragmentManager(), this, 281 R.string.dialog_new_contact_account, 282 AccountListFilter.ACCOUNTS_CONTACT_WRITABLE, args); 283 284 // In this case, because this DialogFragment is used as a target fragment to 285 // SelectAccountDialogFragment, we can't close it yet. We close the dialog when 286 // we get a callback from it. 287 return false; 288 } 289 290 AccountSelectionUtil.doImport(getActivity(), resId, 291 (size == 1 ? accountList.get(0) : null), subscriptionId); 292 return true; // Close the dialog. 293 } 294 295 /** 296 * Called when an account is selected on {@link SelectAccountDialogFragment}. 297 */ 298 @Override 299 public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) { 300 AccountSelectionUtil.doImport(getActivity(), extraArgs.getInt(KEY_RES_ID), 301 account, extraArgs.getInt(KEY_SUBSCRIPTION_ID)); 302 303 // At this point the dialog is still showing (which is why we can use getActivity() above) 304 // So close it. 305 dismiss(); 306 } 307 308 @Override 309 public void onAccountSelectorCancelled() { 310 // See onAccountChosen() -- at this point the dialog is still showing. Close it. 311 dismiss(); 312 } 313 314 private CharSequence getSubDescription(SubscriptionInfo record) { 315 CharSequence name = record.getDisplayName(); 316 if (TextUtils.isEmpty(record.getNumber())) { 317 // Don't include the phone number in the description, since we don't know the number. 318 return getString(R.string.import_from_sim_summary_no_number, name); 319 } 320 return TextUtils.expandTemplate( 321 getString(R.string.import_from_sim_summary), 322 name, 323 PhoneNumberUtils.createTtsSpannable(record.getNumber())); 324 } 325 326 private static class AdapterEntry { 327 public final CharSequence mLabel; 328 public final int mChoiceResourceId; 329 public final int mSubscriptionId; 330 331 public AdapterEntry(CharSequence label, int resId, int subId) { 332 mLabel = label; 333 mChoiceResourceId = resId; 334 mSubscriptionId = subId; 335 } 336 337 public AdapterEntry(String label, int resId) { 338 // Store a nonsense value for mSubscriptionId. If this constructor is used, 339 // the mSubscriptionId value should not be read later. 340 this(label, resId, /* subId = */ -1); 341 } 342 } 343} 344