1 /* 2 * Copyright (C) 2009 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 package com.android.contacts.common.vcard; 17 18 import android.app.Activity; 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.IBinder; 30 import android.provider.OpenableColumns; 31 import android.text.BidiFormatter; 32 import android.text.TextDirectionHeuristics; 33 import android.util.Log; 34 35 import com.android.contacts.common.R; 36 import com.android.contacts.common.activity.RequestImportVCardPermissionsActivity; 37 38 /** 39 * Shows a dialog confirming the export and asks actual vCard export to {@link VCardService} 40 * 41 * This Activity first connects to VCardService and ask an available file name and shows it to 42 * a user. After the user's confirmation, it send export request with the file name, assuming the 43 * file name is not reserved yet. 44 */ 45 public class ExportVCardActivity extends Activity implements ServiceConnection, 46 DialogInterface.OnClickListener, DialogInterface.OnCancelListener { 47 private static final String LOG_TAG = "VCardExport"; 48 private static final boolean DEBUG = VCardService.DEBUG; 49 private static final int REQUEST_CREATE_DOCUMENT = 100; 50 51 /** 52 * True when this Activity is connected to {@link VCardService}. 53 * 54 * Should be touched inside synchronized block. 55 */ 56 private boolean mConnected; 57 58 /** 59 * True when users need to do something and this Activity should not disconnect from 60 * VCardService. False when all necessary procedures are done (including sending export request) 61 * or there's some error occured. 62 */ 63 private volatile boolean mProcessOngoing = true; 64 65 private VCardService mService; 66 private static final BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); 67 68 // String for storing error reason temporarily. 69 private String mErrorReason; 70 71 @Override onCreate(Bundle bundle)72 protected void onCreate(Bundle bundle) { 73 super.onCreate(bundle); 74 75 if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) { 76 return; 77 } 78 79 connectVCardService(); 80 } 81 connectVCardService()82 private void connectVCardService() { 83 final String callingActivity = getIntent().getExtras() 84 .getString(VCardCommonArguments.ARG_CALLING_ACTIVITY); 85 Intent intent = new Intent(this, VCardService.class); 86 intent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY, callingActivity); 87 88 if (startService(intent) == null) { 89 Log.e(LOG_TAG, "Failed to start vCard service"); 90 mErrorReason = getString(R.string.fail_reason_unknown); 91 showDialog(R.id.dialog_fail_to_export_with_reason); 92 return; 93 } 94 95 if (!bindService(intent, this, Context.BIND_AUTO_CREATE)) { 96 Log.e(LOG_TAG, "Failed to connect to vCard service."); 97 mErrorReason = getString(R.string.fail_reason_unknown); 98 showDialog(R.id.dialog_fail_to_export_with_reason); 99 } 100 // Continued to onServiceConnected() 101 } 102 103 @Override onActivityResult(int requestCode, int resultCode, Intent data)104 public void onActivityResult(int requestCode, int resultCode, Intent data) { 105 if (requestCode == REQUEST_CREATE_DOCUMENT) { 106 if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { 107 final Uri mTargetFileName = data.getData(); 108 if (DEBUG) Log.d(LOG_TAG, "exporting to " + mTargetFileName); 109 final ExportRequest request = new ExportRequest(mTargetFileName); 110 // The connection object will call finish(). 111 mService.handleExportRequest(request, new NotificationImportExportListener( 112 ExportVCardActivity.this)); 113 } else if (DEBUG) { 114 Log.d(LOG_TAG, "create document cancelled or no data returned"); 115 } 116 unbindAndFinish(); 117 } 118 } 119 120 @Override onServiceConnected(ComponentName name, IBinder binder)121 public synchronized void onServiceConnected(ComponentName name, IBinder binder) { 122 if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name"); 123 mConnected = true; 124 mService = ((VCardService.MyBinder) binder).getService(); 125 126 // Have the user choose where vcards will be exported to 127 final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 128 intent.addCategory(Intent.CATEGORY_OPENABLE); 129 intent.setType(VCardService.X_VCARD_MIME_TYPE); 130 intent.putExtra(Intent.EXTRA_TITLE, mBidiFormatter.unicodeWrap( 131 getString(R.string.exporting_vcard_filename), TextDirectionHeuristics.LTR)); 132 startActivityForResult(intent, REQUEST_CREATE_DOCUMENT); 133 } 134 135 // Use synchronized since we don't want to call unbindAndFinish() just after this call. 136 @Override onServiceDisconnected(ComponentName name)137 public synchronized void onServiceDisconnected(ComponentName name) { 138 if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()"); 139 mService = null; 140 mConnected = false; 141 if (mProcessOngoing) { 142 // Unexpected disconnect event. 143 Log.w(LOG_TAG, "Disconnected from service during the process ongoing."); 144 mErrorReason = getString(R.string.fail_reason_unknown); 145 showDialog(R.id.dialog_fail_to_export_with_reason); 146 } 147 } 148 149 @Override onCreateDialog(int id, Bundle bundle)150 protected Dialog onCreateDialog(int id, Bundle bundle) { 151 switch (id) { 152 case R.string.fail_reason_too_many_vcard: { 153 mProcessOngoing = false; 154 return new AlertDialog.Builder(this) 155 .setTitle(R.string.exporting_contact_failed_title) 156 .setMessage(getString(R.string.exporting_contact_failed_message, 157 getString(R.string.fail_reason_too_many_vcard))) 158 .setPositiveButton(android.R.string.ok, this) 159 .create(); 160 } 161 case R.id.dialog_fail_to_export_with_reason: { 162 mProcessOngoing = false; 163 return new AlertDialog.Builder(this) 164 .setTitle(R.string.exporting_contact_failed_title) 165 .setMessage(getString(R.string.exporting_contact_failed_message, 166 mErrorReason != null ? mErrorReason : 167 getString(R.string.fail_reason_unknown))) 168 .setPositiveButton(android.R.string.ok, this) 169 .setOnCancelListener(this) 170 .create(); 171 } 172 } 173 return super.onCreateDialog(id, bundle); 174 } 175 176 @Override onPrepareDialog(int id, Dialog dialog, Bundle args)177 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { 178 if (id == R.id.dialog_fail_to_export_with_reason) { 179 ((AlertDialog)dialog).setMessage(mErrorReason); 180 } else { 181 super.onPrepareDialog(id, dialog, args); 182 } 183 } 184 185 @Override onClick(DialogInterface dialog, int which)186 public void onClick(DialogInterface dialog, int which) { 187 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called"); 188 unbindAndFinish(); 189 } 190 191 @Override onCancel(DialogInterface dialog)192 public void onCancel(DialogInterface dialog) { 193 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onCancel() is called"); 194 mProcessOngoing = false; 195 unbindAndFinish(); 196 } 197 198 @Override unbindService(ServiceConnection conn)199 public void unbindService(ServiceConnection conn) { 200 mProcessOngoing = false; 201 super.unbindService(conn); 202 } 203 204 /** 205 * Returns the display name for the given openable Uri or null if it could not be resolved. */ getOpenableUriDisplayName(Context context, Uri uri)206 static String getOpenableUriDisplayName(Context context, Uri uri) { 207 if (uri == null) return null; 208 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 209 try { 210 if (cursor != null && cursor.moveToFirst()) { 211 return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 212 } 213 } finally { 214 if (cursor != null) { 215 cursor.close(); 216 } 217 } 218 return null; 219 } 220 unbindAndFinish()221 private synchronized void unbindAndFinish() { 222 if (mConnected) { 223 unbindService(this); 224 mConnected = false; 225 } 226 finish(); 227 } 228 } 229