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