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