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