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