Home | History | Annotate | Download | only in vcard
      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
     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 
    127         public ExportConfirmationListener(String path) {
    128             this(Uri.parse("file://" + path));
    129         }
    130 
    131         public ExportConfirmationListener(Uri uri) {
    132             mDestinationUri = uri;
    133         }
    134 
    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
    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
    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
    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      */
    218     private String getTargetFileForDisplay() {
    219         if (mTargetFileName == null) {
    220             return null;
    221         }
    222         return mBidiFormatter.unicodeWrap(mTargetFileName, TextDirectionHeuristics.LTR);
    223     }
    224 
    225     @Override
    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
    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
    283     protected void onStop() {
    284         super.onStop();
    285 
    286         if (!isFinishing()) {
    287             unbindAndFinish();
    288         }
    289     }
    290 
    291     @Override
    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
    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
    305     public void unbindService(ServiceConnection conn) {
    306         mProcessOngoing = false;
    307         super.unbindService(conn);
    308     }
    309 
    310     private synchronized void unbindAndFinish() {
    311         if (mConnected) {
    312             unbindService(this);
    313             mConnected = false;
    314         }
    315         finish();
    316     }
    317 }
    318