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