• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
handleMessage(Message msg)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 
ExportConfirmationListener(String path)127         public ExportConfirmationListener(String path) {
128             this(Uri.parse("file://" + path));
129         }
130 
ExportConfirmationListener(Uri uri)131         public ExportConfirmationListener(Uri uri) {
132             mDestinationUri = uri;
133         }
134 
onClick(DialogInterface dialog, int which)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
onCreate(Bundle bundle)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
onServiceConnected(ComponentName name, IBinder binder)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
onServiceDisconnected(ComponentName name)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      */
getTargetFileForDisplay()218     private String getTargetFileForDisplay() {
219         if (mTargetFileName == null) {
220             return null;
221         }
222         return mBidiFormatter.unicodeWrap(mTargetFileName, TextDirectionHeuristics.LTR);
223     }
224 
225     @Override
onCreateDialog(int id, Bundle bundle)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
onPrepareDialog(int id, Dialog dialog, Bundle args)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
onStop()283     protected void onStop() {
284         super.onStop();
285 
286         if (!isFinishing()) {
287             unbindAndFinish();
288         }
289     }
290 
291     @Override
onClick(DialogInterface dialog, int which)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
onCancel(DialogInterface dialog)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
unbindService(ServiceConnection conn)305     public void unbindService(ServiceConnection conn) {
306         mProcessOngoing = false;
307         super.unbindService(conn);
308     }
309 
unbindAndFinish()310     private synchronized void unbindAndFinish() {
311         if (mConnected) {
312             unbindService(this);
313             mConnected = false;
314         }
315         finish();
316     }
317 }
318