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.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.R; 38 import com.android.contacts.activities.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.startPermissionActivityIfNeeded(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 String displayName = getOpenableUriDisplayName(this, targetFileName); 140 final ExportRequest request = new ExportRequest(targetFileName, null, displayName); 141 // The connection object will call finish(). 142 mService.handleExportRequest(request, new NotificationImportExportListener( 143 ExportVCardActivity.this)); 144 } else if (DEBUG) { 145 if (mService == null) { 146 Log.d(LOG_TAG, "No vCard service."); 147 } else { 148 Log.d(LOG_TAG, "create document cancelled or no data returned"); 149 } 150 } 151 finish(); 152 } 153 } 154 155 @Override onServiceConnected(ComponentName name, IBinder binder)156 public synchronized void onServiceConnected(ComponentName name, IBinder binder) { 157 if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name"); 158 mConnected = true; 159 mService = ((VCardService.MyBinder) binder).getService(); 160 161 // Have the user choose where vcards will be exported to 162 startActivityForResult(getCreateDocIntent(), REQUEST_CREATE_DOCUMENT); 163 } 164 165 // Use synchronized since we don't want to call finish() just after this call. 166 @Override onServiceDisconnected(ComponentName name)167 public synchronized void onServiceDisconnected(ComponentName name) { 168 if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()"); 169 mService = null; 170 mConnected = false; 171 if (mProcessOngoing) { 172 // Unexpected disconnect event. 173 Log.w(LOG_TAG, "Disconnected from service during the process ongoing."); 174 showErrorDialog(); 175 } 176 } 177 178 @Override onCreateDialog(int id, Bundle bundle)179 protected Dialog onCreateDialog(int id, Bundle bundle) { 180 if (id == R.id.dialog_fail_to_export_with_reason) { 181 mProcessOngoing = false; 182 return new AlertDialog.Builder(this) 183 .setTitle(R.string.exporting_contact_failed_title) 184 .setMessage(getString(R.string.exporting_contact_failed_message, 185 mErrorReason != null ? mErrorReason : 186 getString(R.string.fail_reason_unknown))) 187 .setPositiveButton(android.R.string.ok, this) 188 .setOnCancelListener(this) 189 .create(); 190 } 191 return super.onCreateDialog(id, bundle); 192 } 193 194 @Override onPrepareDialog(int id, Dialog dialog, Bundle args)195 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { 196 if (id == R.id.dialog_fail_to_export_with_reason) { 197 ((AlertDialog)dialog).setMessage(mErrorReason); 198 } else { 199 super.onPrepareDialog(id, dialog, args); 200 } 201 } 202 203 @Override onClick(DialogInterface dialog, int which)204 public void onClick(DialogInterface dialog, int which) { 205 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called"); 206 finish(); 207 } 208 209 @Override onCancel(DialogInterface dialog)210 public void onCancel(DialogInterface dialog) { 211 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onCancel() is called"); 212 mProcessOngoing = false; 213 finish(); 214 } 215 216 @Override unbindService(ServiceConnection conn)217 public void unbindService(ServiceConnection conn) { 218 mProcessOngoing = false; 219 super.unbindService(conn); 220 } 221 222 @Override onDestroy()223 protected void onDestroy() { 224 if (mConnected) { 225 unbindService(this); 226 mConnected = false; 227 } 228 super.onDestroy(); 229 } 230 231 /** 232 * Returns the display name for the given openable Uri or null if it could not be resolved. */ getOpenableUriDisplayName(Context context, Uri uri)233 static String getOpenableUriDisplayName(Context context, Uri uri) { 234 if (uri == null) return null; 235 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 236 try { 237 if (cursor != null && cursor.moveToFirst()) { 238 return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 239 } 240 } finally { 241 if (cursor != null) { 242 cursor.close(); 243 } 244 } 245 return null; 246 } 247 } 248