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