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