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 com.android.contacts.R; 19 import com.android.vcard.VCardComposer; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.ServiceConnection; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.Message; 34 import android.os.Messenger; 35 import android.os.RemoteException; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import java.io.File; 40 41 /** 42 * Shows a dialog confirming the export and asks actual vCard export to {@link VCardService} 43 * 44 * This Activity first connects to VCardService and ask an available file name and shows it to 45 * a user. After the user's confirmation, it send export request with the file name, assuming the 46 * file name is not reserved yet. 47 */ 48 public class ExportVCardActivity extends Activity implements ServiceConnection, 49 DialogInterface.OnClickListener, DialogInterface.OnCancelListener { 50 private static final String LOG_TAG = "VCardExport"; 51 private static final boolean DEBUG = VCardService.DEBUG; 52 53 /** 54 * Handler used when some Message has come from {@link VCardService}. 55 */ 56 private class IncomingHandler extends Handler { 57 @Override 58 public void handleMessage(Message msg) { 59 if (DEBUG) Log.d(LOG_TAG, "IncomingHandler received message."); 60 61 if (msg.arg1 != 0) { 62 Log.i(LOG_TAG, "Message returned from vCard server contains error code."); 63 if (msg.obj != null) { 64 mErrorReason = (String)msg.obj; 65 } 66 showDialog(msg.arg1); 67 return; 68 } 69 70 switch (msg.what) { 71 case VCardService.MSG_SET_AVAILABLE_EXPORT_DESTINATION: 72 if (msg.obj == null) { 73 Log.w(LOG_TAG, "Message returned from vCard server doesn't contain valid path"); 74 mErrorReason = getString(R.string.fail_reason_unknown); 75 showDialog(R.id.dialog_fail_to_export_with_reason); 76 } else { 77 mTargetFileName = (String)msg.obj; 78 if (TextUtils.isEmpty(mTargetFileName)) { 79 Log.w(LOG_TAG, "Destination file name coming from vCard service is empty."); 80 mErrorReason = getString(R.string.fail_reason_unknown); 81 showDialog(R.id.dialog_fail_to_export_with_reason); 82 } else { 83 if (DEBUG) { 84 Log.d(LOG_TAG, 85 String.format("Target file name is set (%s). " + 86 "Show confirmation dialog", mTargetFileName)); 87 } 88 showDialog(R.id.dialog_export_confirmation); 89 } 90 } 91 break; 92 default: 93 Log.w(LOG_TAG, "Unknown message type: " + msg.what); 94 super.handleMessage(msg); 95 } 96 } 97 } 98 99 /** 100 * True when this Activity is connected to {@link VCardService}. 101 * 102 * Should be touched inside synchronized block. 103 */ 104 private boolean mConnected; 105 106 /** 107 * True when users need to do something and this Activity should not disconnect from 108 * VCardService. False when all necessary procedures are done (including sending export request) 109 * or there's some error occured. 110 */ 111 private volatile boolean mProcessOngoing = true; 112 113 private VCardService mService; 114 private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler()); 115 116 // Used temporarily when asking users to confirm the file name 117 private String mTargetFileName; 118 119 // String for storing error reason temporarily. 120 private String mErrorReason; 121 122 private class ExportConfirmationListener implements DialogInterface.OnClickListener { 123 private final Uri mDestinationUri; 124 125 public ExportConfirmationListener(String path) { 126 this(Uri.parse("file://" + path)); 127 } 128 129 public ExportConfirmationListener(Uri uri) { 130 mDestinationUri = uri; 131 } 132 133 public void onClick(DialogInterface dialog, int which) { 134 if (which == DialogInterface.BUTTON_POSITIVE) { 135 if (DEBUG) { 136 Log.d(LOG_TAG, 137 String.format("Try sending export request (uri: %s)", mDestinationUri)); 138 } 139 final ExportRequest request = new ExportRequest(mDestinationUri); 140 // The connection object will call finish(). 141 mService.handleExportRequest(request, new NotificationImportExportListener( 142 ExportVCardActivity.this)); 143 } 144 unbindAndFinish(); 145 } 146 } 147 148 @Override 149 protected void onCreate(Bundle bundle) { 150 super.onCreate(bundle); 151 152 // Check directory is available. 153 final File targetDirectory = new File(getString(R.string.config_export_dir)); 154 if (!(targetDirectory.exists() && 155 targetDirectory.isDirectory() && 156 targetDirectory.canRead()) && 157 !targetDirectory.mkdirs()) { 158 showDialog(R.id.dialog_sdcard_not_found); 159 return; 160 } 161 162 Intent intent = new Intent(this, VCardService.class); 163 164 if (startService(intent) == null) { 165 Log.e(LOG_TAG, "Failed to start vCard service"); 166 mErrorReason = getString(R.string.fail_reason_unknown); 167 showDialog(R.id.dialog_fail_to_export_with_reason); 168 return; 169 } 170 171 if (!bindService(intent, this, Context.BIND_AUTO_CREATE)) { 172 Log.e(LOG_TAG, "Failed to connect to vCard service."); 173 mErrorReason = getString(R.string.fail_reason_unknown); 174 showDialog(R.id.dialog_fail_to_export_with_reason); 175 } 176 // Continued to onServiceConnected() 177 } 178 179 @Override 180 public synchronized void onServiceConnected(ComponentName name, IBinder binder) { 181 if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name"); 182 mConnected = true; 183 mService = ((VCardService.MyBinder) binder).getService(); 184 mService.handleRequestAvailableExportDestination(mIncomingMessenger); 185 // Wait until MSG_SET_AVAILABLE_EXPORT_DESTINATION message is available. 186 } 187 188 // Use synchronized since we don't want to call unbindAndFinish() just after this call. 189 @Override 190 public synchronized void onServiceDisconnected(ComponentName name) { 191 if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()"); 192 mService = null; 193 mConnected = false; 194 if (mProcessOngoing) { 195 // Unexpected disconnect event. 196 Log.w(LOG_TAG, "Disconnected from service during the process ongoing."); 197 mErrorReason = getString(R.string.fail_reason_unknown); 198 showDialog(R.id.dialog_fail_to_export_with_reason); 199 } 200 } 201 202 @Override 203 protected Dialog onCreateDialog(int id, Bundle bundle) { 204 switch (id) { 205 case R.id.dialog_export_confirmation: { 206 return new AlertDialog.Builder(this) 207 .setTitle(R.string.confirm_export_title) 208 .setMessage(getString(R.string.confirm_export_message, mTargetFileName)) 209 .setPositiveButton(android.R.string.ok, 210 new ExportConfirmationListener(mTargetFileName)) 211 .setNegativeButton(android.R.string.cancel, this) 212 .setOnCancelListener(this) 213 .create(); 214 } 215 case R.string.fail_reason_too_many_vcard: { 216 mProcessOngoing = false; 217 return new AlertDialog.Builder(this) 218 .setTitle(R.string.exporting_contact_failed_title) 219 .setMessage(getString(R.string.exporting_contact_failed_message, 220 getString(R.string.fail_reason_too_many_vcard))) 221 .setPositiveButton(android.R.string.ok, this) 222 .create(); 223 } 224 case R.id.dialog_fail_to_export_with_reason: { 225 mProcessOngoing = false; 226 return new AlertDialog.Builder(this) 227 .setTitle(R.string.exporting_contact_failed_title) 228 .setMessage(getString(R.string.exporting_contact_failed_message, 229 mErrorReason != null ? mErrorReason : 230 getString(R.string.fail_reason_unknown))) 231 .setPositiveButton(android.R.string.ok, this) 232 .setOnCancelListener(this) 233 .create(); 234 } 235 case R.id.dialog_sdcard_not_found: { 236 mProcessOngoing = false; 237 return new AlertDialog.Builder(this) 238 .setTitle(R.string.no_sdcard_title) 239 .setIconAttribute(android.R.attr.alertDialogIcon) 240 .setMessage(R.string.no_sdcard_message) 241 .setPositiveButton(android.R.string.ok, this).create(); 242 } 243 } 244 return super.onCreateDialog(id, bundle); 245 } 246 247 @Override 248 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { 249 if (id == R.id.dialog_fail_to_export_with_reason) { 250 ((AlertDialog)dialog).setMessage(mErrorReason); 251 } else if (id == R.id.dialog_export_confirmation) { 252 ((AlertDialog)dialog).setMessage( 253 getString(R.string.confirm_export_message, mTargetFileName)); 254 } else { 255 super.onPrepareDialog(id, dialog, args); 256 } 257 } 258 259 @Override 260 protected void onStop() { 261 super.onStop(); 262 263 if (!isFinishing()) { 264 unbindAndFinish(); 265 } 266 } 267 268 @Override 269 public void onClick(DialogInterface dialog, int which) { 270 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called"); 271 unbindAndFinish(); 272 } 273 274 @Override 275 public void onCancel(DialogInterface dialog) { 276 if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onCancel() is called"); 277 mProcessOngoing = false; 278 unbindAndFinish(); 279 } 280 281 @Override 282 public void unbindService(ServiceConnection conn) { 283 mProcessOngoing = false; 284 super.unbindService(conn); 285 } 286 287 private synchronized void unbindAndFinish() { 288 if (mConnected) { 289 unbindService(this); 290 mConnected = false; 291 } 292 finish(); 293 } 294 }