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.database.Cursor;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.IBinder;
30import android.provider.OpenableColumns;
31import android.text.BidiFormatter;
32import android.text.TextDirectionHeuristics;
33import android.util.Log;
34
35import com.android.contacts.common.R;
36import com.android.contacts.common.activity.RequestImportVCardPermissionsActivity;
37
38/**
39 * Shows a dialog confirming the export and asks actual vCard export to {@link VCardService}
40 *
41 * This Activity first connects to VCardService and ask an available file name and shows it to
42 * a user. After the user's confirmation, it send export request with the file name, assuming the
43 * file name is not reserved yet.
44 */
45public class ExportVCardActivity extends Activity implements ServiceConnection,
46        DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
47    private static final String LOG_TAG = "VCardExport";
48    private static final boolean DEBUG = VCardService.DEBUG;
49    private static final int REQUEST_CREATE_DOCUMENT = 100;
50
51    /**
52     * True when this Activity is connected to {@link VCardService}.
53     *
54     * Should be touched inside synchronized block.
55     */
56    private boolean mConnected;
57
58    /**
59     * True when users need to do something and this Activity should not disconnect from
60     * VCardService. False when all necessary procedures are done (including sending export request)
61     * or there's some error occured.
62     */
63    private volatile boolean mProcessOngoing = true;
64
65    private VCardService mService;
66    private static final BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
67
68    // String for storing error reason temporarily.
69    private String mErrorReason;
70
71    @Override
72    protected void onCreate(Bundle bundle) {
73        super.onCreate(bundle);
74
75        if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
76            return;
77        }
78
79        connectVCardService();
80    }
81
82    private void connectVCardService() {
83        final String callingActivity = getIntent().getExtras()
84                .getString(VCardCommonArguments.ARG_CALLING_ACTIVITY);
85        Intent intent = new Intent(this, VCardService.class);
86        intent.putExtra(VCardCommonArguments.ARG_CALLING_ACTIVITY, callingActivity);
87
88        if (startService(intent) == null) {
89            Log.e(LOG_TAG, "Failed to start vCard service");
90            mErrorReason = getString(R.string.fail_reason_unknown);
91            showDialog(R.id.dialog_fail_to_export_with_reason);
92            return;
93        }
94
95        if (!bindService(intent, this, Context.BIND_AUTO_CREATE)) {
96            Log.e(LOG_TAG, "Failed to connect to vCard service.");
97            mErrorReason = getString(R.string.fail_reason_unknown);
98            showDialog(R.id.dialog_fail_to_export_with_reason);
99        }
100        // Continued to onServiceConnected()
101    }
102
103    @Override
104    public void onActivityResult(int requestCode, int resultCode, Intent data) {
105        if (requestCode == REQUEST_CREATE_DOCUMENT) {
106            if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
107                final Uri mTargetFileName = data.getData();
108                if (DEBUG) Log.d(LOG_TAG, "exporting to " + mTargetFileName);
109                final ExportRequest request = new ExportRequest(mTargetFileName);
110                // The connection object will call finish().
111                mService.handleExportRequest(request, new NotificationImportExportListener(
112                        ExportVCardActivity.this));
113            } else if (DEBUG) {
114                Log.d(LOG_TAG, "create document cancelled or no data returned");
115            }
116            unbindAndFinish();
117        }
118    }
119
120    @Override
121    public synchronized void onServiceConnected(ComponentName name, IBinder binder) {
122        if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name");
123        mConnected = true;
124        mService = ((VCardService.MyBinder) binder).getService();
125
126        // Have the user choose where vcards will be exported to
127        final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
128        intent.addCategory(Intent.CATEGORY_OPENABLE);
129        intent.setType(VCardService.X_VCARD_MIME_TYPE);
130        intent.putExtra(Intent.EXTRA_TITLE, mBidiFormatter.unicodeWrap(
131                getString(R.string.exporting_vcard_filename), TextDirectionHeuristics.LTR));
132        startActivityForResult(intent, REQUEST_CREATE_DOCUMENT);
133    }
134
135    // Use synchronized since we don't want to call unbindAndFinish() just after this call.
136    @Override
137    public synchronized void onServiceDisconnected(ComponentName name) {
138        if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()");
139        mService = null;
140        mConnected = false;
141        if (mProcessOngoing) {
142            // Unexpected disconnect event.
143            Log.w(LOG_TAG, "Disconnected from service during the process ongoing.");
144            mErrorReason = getString(R.string.fail_reason_unknown);
145            showDialog(R.id.dialog_fail_to_export_with_reason);
146        }
147    }
148
149    @Override
150    protected Dialog onCreateDialog(int id, Bundle bundle) {
151        switch (id) {
152            case R.string.fail_reason_too_many_vcard: {
153                mProcessOngoing = false;
154                return new AlertDialog.Builder(this)
155                        .setTitle(R.string.exporting_contact_failed_title)
156                        .setMessage(getString(R.string.exporting_contact_failed_message,
157                                getString(R.string.fail_reason_too_many_vcard)))
158                        .setPositiveButton(android.R.string.ok, this)
159                        .create();
160            }
161            case R.id.dialog_fail_to_export_with_reason: {
162                mProcessOngoing = false;
163                return new AlertDialog.Builder(this)
164                        .setTitle(R.string.exporting_contact_failed_title)
165                        .setMessage(getString(R.string.exporting_contact_failed_message,
166                                mErrorReason != null ? mErrorReason :
167                                        getString(R.string.fail_reason_unknown)))
168                        .setPositiveButton(android.R.string.ok, this)
169                        .setOnCancelListener(this)
170                        .create();
171            }
172        }
173        return super.onCreateDialog(id, bundle);
174    }
175
176    @Override
177    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
178        if (id == R.id.dialog_fail_to_export_with_reason) {
179            ((AlertDialog)dialog).setMessage(mErrorReason);
180        } else {
181            super.onPrepareDialog(id, dialog, args);
182        }
183    }
184
185    @Override
186    public void onClick(DialogInterface dialog, int which) {
187        if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called");
188        unbindAndFinish();
189    }
190
191    @Override
192    public void onCancel(DialogInterface dialog) {
193        if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onCancel() is called");
194        mProcessOngoing = false;
195        unbindAndFinish();
196    }
197
198    @Override
199    public void unbindService(ServiceConnection conn) {
200        mProcessOngoing = false;
201        super.unbindService(conn);
202    }
203
204    /**
205     * Returns the display name for the given openable Uri or null if it could not be resolved. */
206    static String getOpenableUriDisplayName(Context context, Uri uri) {
207        if (uri == null) return null;
208        final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
209        try {
210            if (cursor != null && cursor.moveToFirst()) {
211                return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
212            }
213        } finally {
214            if (cursor != null)  {
215                cursor.close();
216            }
217        }
218        return null;
219    }
220
221    private synchronized void unbindAndFinish() {
222        if (mConnected) {
223            unbindService(this);
224            mConnected = false;
225        }
226        finish();
227    }
228}
229