1/*
2 * Copyright (C) 2011 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.vcard;
18
19import com.android.contacts.R;
20import com.android.contacts.model.AccountTypeManager;
21import com.android.contacts.model.AccountWithDataSet;
22import com.android.vcard.VCardEntry;
23import com.android.vcard.VCardEntryCounter;
24import com.android.vcard.VCardParser;
25import com.android.vcard.VCardParser_V21;
26import com.android.vcard.VCardParser_V30;
27import com.android.vcard.VCardSourceDetector;
28import com.android.vcard.exception.VCardException;
29import com.android.vcard.exception.VCardNestedException;
30import com.android.vcard.exception.VCardVersionException;
31
32import android.app.Activity;
33import android.content.ComponentName;
34import android.content.Context;
35import android.content.Intent;
36import android.content.ServiceConnection;
37import android.net.Uri;
38import android.nfc.NdefMessage;
39import android.nfc.NdefRecord;
40import android.nfc.NfcAdapter;
41import android.os.AsyncTask;
42import android.os.Bundle;
43import android.os.IBinder;
44import android.provider.ContactsContract.RawContacts;
45import android.util.Log;
46
47import java.io.ByteArrayInputStream;
48import java.io.IOException;
49import java.nio.charset.Charset;
50import java.util.ArrayList;
51import java.util.List;
52
53public class NfcImportVCardActivity extends Activity implements ServiceConnection,
54        VCardImportExportListener {
55    private static final String TAG = "NfcImportVCardActivity";
56
57    private static final int SELECT_ACCOUNT = 1;
58
59    private NdefRecord mRecord;
60    private AccountWithDataSet mAccount;
61
62    /* package */ class ImportTask extends AsyncTask<VCardService, Void, ImportRequest> {
63        @Override
64        public ImportRequest doInBackground(VCardService... services) {
65            ImportRequest request = createImportRequest();
66            if (request == null) {
67                return null;
68            }
69
70            ArrayList<ImportRequest> requests = new ArrayList<ImportRequest>();
71            requests.add(request);
72            services[0].handleImportRequest(requests, NfcImportVCardActivity.this);
73            return request;
74        }
75
76        @Override
77        public void onCancelled() {
78            unbindService(NfcImportVCardActivity.this);
79        }
80
81        @Override
82        public void onPostExecute(ImportRequest request) {
83            unbindService(NfcImportVCardActivity.this);
84        }
85    }
86
87    /* package */ ImportRequest createImportRequest() {
88        VCardParser parser;
89        VCardEntryCounter counter = null;
90        VCardSourceDetector detector = null;
91        int vcardVersion = ImportVCardActivity.VCARD_VERSION_V21;
92        try {
93            ByteArrayInputStream is = new ByteArrayInputStream(mRecord.getPayload());
94            is.mark(0);
95            parser = new VCardParser_V21();
96            try {
97                counter = new VCardEntryCounter();
98                detector = new VCardSourceDetector();
99                parser.addInterpreter(counter);
100                parser.addInterpreter(detector);
101                parser.parse(is);
102            } catch (VCardVersionException e1) {
103                is.reset();
104                vcardVersion = ImportVCardActivity.VCARD_VERSION_V30;
105                parser = new VCardParser_V30();
106                try {
107                    counter = new VCardEntryCounter();
108                    detector = new VCardSourceDetector();
109                    parser.addInterpreter(counter);
110                    parser.addInterpreter(detector);
111                    parser.parse(is);
112                } catch (VCardVersionException e2) {
113                    return null;
114                }
115            } finally {
116                try {
117                    if (is != null) is.close();
118                } catch (IOException e) {
119                }
120            }
121        } catch (IOException e) {
122            Log.e(TAG, "Failed reading vcard data", e);
123            return null;
124        } catch (VCardNestedException e) {
125            Log.w(TAG, "Nested Exception is found (it may be false-positive).");
126            // Go through without throwing the Exception, as we may be able to detect the
127            // version before it
128        } catch (VCardException e) {
129            Log.e(TAG, "Error parsing vcard", e);
130            return null;
131        }
132
133        return new ImportRequest(mAccount, mRecord.getPayload(), null,
134                getString(R.string.nfc_vcard_file_name), detector.getEstimatedType(),
135                detector.getEstimatedCharset(), vcardVersion, counter.getCount());
136    }
137
138    @Override
139    public void onServiceConnected(ComponentName name, IBinder binder) {
140        VCardService service = ((VCardService.MyBinder) binder).getService();
141        new ImportTask().execute(service);
142    }
143
144    @Override
145    public void onServiceDisconnected(ComponentName name) {
146        // Do nothing
147    }
148
149    @Override
150    protected void onCreate(Bundle bundle) {
151        super.onCreate(bundle);
152
153        Intent intent = getIntent();
154        if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
155            Log.w(TAG, "Unknowon intent " + intent);
156            finish();
157        }
158
159        NdefMessage msg = (NdefMessage) intent.getParcelableArrayExtra(
160                NfcAdapter.EXTRA_NDEF_MESSAGES)[0];
161        NdefRecord records[] = msg.getRecords();
162        if (records == null || records.length == 0) {
163            Log.w(TAG, "No records " + intent);
164            finish();
165        }
166
167        NdefRecord record = records[0];
168        String type = new String(record.getType(), Charset.forName("UTF8"));
169        if (record.getTnf() != NdefRecord.TNF_MIME_MEDIA ||
170                (!"text/x-vcard".equalsIgnoreCase(type) && !"text/vcard".equals(type))) {
171            Log.w(TAG, "Not a vcard");
172            //setStatus(getString(R.string.fail_reason_not_supported));
173            return;
174        }
175        mRecord = record;
176
177        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
178        final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true);
179        if (accountList.size() == 0) {
180            mAccount = null;
181        } else if (accountList.size() == 1) {
182            mAccount = accountList.get(0);
183        } else {
184            startActivityForResult(new Intent(this, SelectAccountActivity.class), SELECT_ACCOUNT);
185            return;
186        }
187
188        startImport();
189    }
190
191    @Override
192    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
193        if (requestCode == SELECT_ACCOUNT) {
194            if (resultCode == RESULT_OK) {
195                mAccount = new AccountWithDataSet(
196                        intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME),
197                        intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE),
198                        intent.getStringExtra(SelectAccountActivity.DATA_SET));
199                startImport();
200            } else {
201                finish();
202            }
203        }
204    }
205
206    private void startImport() {
207        // We don't want the service finishes itself just after this connection.
208        Intent intent = new Intent(this, VCardService.class);
209        startService(intent);
210        bindService(intent, this, Context.BIND_AUTO_CREATE);
211    }
212
213    @Override
214    public void onImportProcessed(ImportRequest request, int jobId, int sequence) {
215        // do nothing
216    }
217
218    @Override
219    public void onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount,
220            int totalCount) {
221        // do nothing
222    }
223
224    @Override
225    public void onImportFinished(ImportRequest request, int jobId, Uri uri) {
226        if (isFinishing()) {
227            Log.i(TAG, "Late import -- ignoring");
228            return;
229        }
230
231        if (uri != null) {
232            Uri contactUri = RawContacts.getContactLookupUri(getContentResolver(), uri);
233            Intent intent = new Intent(Intent.ACTION_VIEW, contactUri);
234            startActivity(intent);
235            finish();
236        }
237    }
238
239    @Override
240    public void onImportFailed(ImportRequest request) {
241        if (isFinishing()) {
242            Log.i(TAG, "Late import failure -- ignoring");
243            return;
244        }
245        // TODO: report failure
246    }
247
248    @Override
249    public void onImportCanceled(ImportRequest request, int jobId) {
250        // do nothing
251    }
252
253    @Override
254    public void onExportProcessed(ExportRequest request, int jobId) {
255        // do nothing
256    }
257
258    @Override
259    public void onExportFailed(ExportRequest request) {
260        // do nothing
261    }
262
263    @Override
264    public void onCancelRequest(CancelRequest request, int type) {
265        // do nothing
266    }
267
268    @Override
269    public void onComplete() {
270        // do nothing
271    }
272}
273