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