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.common.vcard;
17
18import android.app.Activity;
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.net.Uri;
27import android.os.Bundle;
28import android.os.Environment;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.Messenger;
33import android.text.TextUtils;
34import android.util.Log;
35
36import com.android.contacts.common.R;
37
38import 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 */
47public 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