ExportVCardActivity.java revision 783a09a8770f4322a45cee456adefbbc71218ece
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 */
16package com.android.contacts.vcard;
17
18import com.android.contacts.R;
19import com.android.vcard.VCardComposer;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.app.Dialog;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.ServiceConnection;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Message;
34import android.os.Messenger;
35import android.os.RemoteException;
36import android.text.TextUtils;
37import android.util.Log;
38
39import 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 */
48public 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 Messenger mOutgoingMessenger;
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                if (trySend(Message.obtain(null, VCardService.MSG_EXPORT_REQUEST, request))) {
142                    Log.i(LOG_TAG, "Successfully sent export request. Finish itself");
143                    unbindAndFinish();
144                }
145            }
146        }
147    }
148
149    @Override
150    protected void onCreate(Bundle bundle) {
151        super.onCreate(bundle);
152
153        // Check directory is available.
154        final File targetDirectory = new File(getString(R.string.config_export_dir));
155        if (!(targetDirectory.exists() &&
156                targetDirectory.isDirectory() &&
157                targetDirectory.canRead()) &&
158                !targetDirectory.mkdirs()) {
159            showDialog(R.id.dialog_sdcard_not_found);
160            return;
161        }
162
163        if (startService(new Intent(this, VCardService.class)) == null) {
164            Log.e(LOG_TAG, "Failed to start vCard service");
165            mErrorReason = getString(R.string.fail_reason_unknown);
166            showDialog(R.id.dialog_fail_to_export_with_reason);
167            return;
168        }
169
170        if (!bindService(new Intent(this, VCardService.class), this, Context.BIND_AUTO_CREATE)) {
171            Log.e(LOG_TAG, "Failed to connect to vCard service.");
172            mErrorReason = getString(R.string.fail_reason_unknown);
173            showDialog(R.id.dialog_fail_to_export_with_reason);
174        }
175        // Continued to onServiceConnected()
176    }
177
178    @Override
179    public synchronized void onServiceConnected(ComponentName name, IBinder service) {
180        if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name");
181        mConnected = true;
182        mOutgoingMessenger = new Messenger(service);
183        final Message message =
184                Message.obtain(null, VCardService.MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION);
185        message.replyTo = mIncomingMessenger;
186        trySend(message);
187        // Wait until MSG_SET_AVAILABLE_EXPORT_DESTINATION message is available.
188    }
189
190    // Use synchronized since we don't want to call unbindAndFinish() just after this call.
191    @Override
192    public synchronized void onServiceDisconnected(ComponentName name) {
193        if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()");
194        mOutgoingMessenger = null;
195        mConnected = false;
196        if (mProcessOngoing) {
197            // Unexpected disconnect event.
198            Log.w(LOG_TAG, "Disconnected from service during the process ongoing.");
199            mErrorReason = getString(R.string.fail_reason_unknown);
200            showDialog(R.id.dialog_fail_to_export_with_reason);
201        }
202    }
203
204    @Override
205    protected Dialog onCreateDialog(int id, Bundle bundle) {
206        switch (id) {
207            case R.id.dialog_export_confirmation: {
208                return new AlertDialog.Builder(this)
209                        .setTitle(R.string.confirm_export_title)
210                        .setMessage(getString(R.string.confirm_export_message, mTargetFileName))
211                        .setPositiveButton(android.R.string.ok,
212                                new ExportConfirmationListener(mTargetFileName))
213                        .setNegativeButton(android.R.string.cancel, this)
214                        .setOnCancelListener(this)
215                        .create();
216            }
217            case R.string.fail_reason_too_many_vcard: {
218                mProcessOngoing = false;
219                return new AlertDialog.Builder(this)
220                        .setTitle(R.string.exporting_contact_failed_title)
221                        .setMessage(getString(R.string.exporting_contact_failed_message,
222                                getString(R.string.fail_reason_too_many_vcard)))
223                        .setPositiveButton(android.R.string.ok, this)
224                        .create();
225            }
226            case R.id.dialog_fail_to_export_with_reason: {
227                mProcessOngoing = false;
228                return new AlertDialog.Builder(this)
229                        .setTitle(R.string.exporting_contact_failed_title)
230                        .setMessage(getString(R.string.exporting_contact_failed_message,
231                                mErrorReason != null ? mErrorReason :
232                                        getString(R.string.fail_reason_unknown)))
233                        .setPositiveButton(android.R.string.ok, this)
234                        .setOnCancelListener(this)
235                        .create();
236            }
237            case R.id.dialog_sdcard_not_found: {
238                mProcessOngoing = false;
239                return new AlertDialog.Builder(this)
240                        .setTitle(R.string.no_sdcard_title)
241                        .setIcon(android.R.drawable.ic_dialog_alert)
242                        .setMessage(R.string.no_sdcard_message)
243                        .setPositiveButton(android.R.string.ok, this).create();
244            }
245        }
246        return super.onCreateDialog(id, bundle);
247    }
248
249    @Override
250    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
251        if (id == R.id.dialog_fail_to_export_with_reason) {
252            ((AlertDialog)dialog).setMessage(mErrorReason);
253        } else if (id == R.id.dialog_export_confirmation) {
254            ((AlertDialog)dialog).setMessage(
255                    getString(R.string.confirm_export_message, mTargetFileName));
256        } else {
257            super.onPrepareDialog(id, dialog, args);
258        }
259    }
260
261    @Override
262    protected void onStop() {
263        super.onStop();
264
265        if (!isFinishing()) {
266            unbindAndFinish();
267        }
268    }
269
270    private boolean trySend(Message message) {
271        try {
272            mOutgoingMessenger.send(message);
273            return true;
274        } catch (RemoteException e) {
275            Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
276            unbindService(this);
277            mErrorReason = getString(R.string.fail_reason_unknown);
278            showDialog(R.id.dialog_fail_to_export_with_reason);
279            return false;
280        }
281    }
282
283    @Override
284    public void onClick(DialogInterface dialog, int which) {
285        if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called");
286        unbindAndFinish();
287    }
288
289    @Override
290    public void onCancel(DialogInterface dialog) {
291        if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onCancel() is called");
292        mProcessOngoing = false;
293        unbindAndFinish();
294    }
295
296    @Override
297    public void unbindService(ServiceConnection conn) {
298        mProcessOngoing = false;
299        super.unbindService(conn);
300    }
301
302    private synchronized void unbindAndFinish() {
303        if (mConnected) {
304            unbindService(this);
305            mConnected = false;
306        }
307        finish();
308    }
309}