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