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