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