blob: 897d6611e7bf264a9c436f6f9211fbcd78963e99 [file] [log] [blame]
alisionf76de3b2013-04-16 15:35:22 -04001/*
2 * Copyright (C) 2004-2012 Savoir-Faire Linux Inc.
3 *
4 * Author: Adrien Beraud <adrien.beraud@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 * Additional permission under GNU GPL version 3 section 7:
21 *
22 * If you modify this program, or any covered work, by linking or
23 * combining it with the OpenSSL project's OpenSSL library (or a
24 * modified version of that library), containing parts covered by the
25 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
26 * grants you additional permission to convey the resulting work.
27 * Corresponding Source for a non-source form of such a combination
28 * shall include the source code for the parts of OpenSSL used as well
29 * as that of the covered work.
30 */
31package com.savoirfairelinux.sflphone.fragments;
32
alisionf76de3b2013-04-16 15:35:22 -040033import java.util.ArrayList;
alisione2a38e12013-04-25 14:20:20 -040034import java.util.Random;
alisionf76de3b2013-04-16 15:35:22 -040035
36import android.app.Activity;
37import android.app.AlertDialog;
38import android.app.ListFragment;
39import android.app.LoaderManager;
alisionf76de3b2013-04-16 15:35:22 -040040import android.content.Context;
41import android.content.CursorLoader;
42import android.content.DialogInterface;
alision371b77e2013-04-23 14:51:26 -040043import android.content.Intent;
alisionf76de3b2013-04-16 15:35:22 -040044import android.content.Loader;
45import android.database.Cursor;
alisionf76de3b2013-04-16 15:35:22 -040046import android.net.Uri;
47import android.os.Bundle;
alisiona4325152013-04-19 11:10:03 -040048import android.os.RemoteException;
alisionf76de3b2013-04-16 15:35:22 -040049import android.provider.ContactsContract.CommonDataKinds.Phone;
50import android.provider.ContactsContract.CommonDataKinds.SipAddress;
51import android.provider.ContactsContract.Contacts;
52import android.util.Log;
53import android.view.LayoutInflater;
54import android.view.View;
alisione2a38e12013-04-25 14:20:20 -040055import android.view.View.OnClickListener;
alision43a9b362013-05-01 16:30:15 -040056import android.view.ViewGroup;
alisionf76de3b2013-04-16 15:35:22 -040057import android.widget.AdapterView;
alision371b77e2013-04-23 14:51:26 -040058import android.widget.AdapterView.OnItemClickListener;
alisionf76de3b2013-04-16 15:35:22 -040059import android.widget.AdapterView.OnItemLongClickListener;
alision43a9b362013-05-01 16:30:15 -040060import android.widget.Button;
61import android.widget.EditText;
62import android.widget.ImageButton;
alisionf76de3b2013-04-16 15:35:22 -040063import android.widget.ListView;
alision43a9b362013-05-01 16:30:15 -040064import android.widget.Toast;
alisionf76de3b2013-04-16 15:35:22 -040065
66import com.savoirfairelinux.sflphone.R;
alisionf76de3b2013-04-16 15:35:22 -040067import com.savoirfairelinux.sflphone.account.AccountSelectionSpinner;
alisiona4325152013-04-19 11:10:03 -040068import com.savoirfairelinux.sflphone.adapters.CallElementAdapter;
alision371b77e2013-04-23 14:51:26 -040069import com.savoirfairelinux.sflphone.client.CallActivity;
alisionf76de3b2013-04-16 15:35:22 -040070import com.savoirfairelinux.sflphone.client.SFLPhoneHomeActivity;
71import com.savoirfairelinux.sflphone.client.SFLphoneApplication;
alisiond9e29442013-04-17 16:10:18 -040072import com.savoirfairelinux.sflphone.client.receiver.AccountListReceiver;
alisione2a38e12013-04-25 14:20:20 -040073import com.savoirfairelinux.sflphone.client.receiver.CallListReceiver;
alisionf76de3b2013-04-16 15:35:22 -040074import com.savoirfairelinux.sflphone.model.SipCall;
75import com.savoirfairelinux.sflphone.service.ISipService;
76
77/**
78 * Main list of Call Elements. We don't manage contacts ourself so they are
79 */
80public class CallElementListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
alisiond9e29442013-04-17 16:10:18 -040081 private static final String TAG = CallElementListFragment.class.getSimpleName();
alisionf76de3b2013-04-16 15:35:22 -040082 private CallElementAdapter mAdapter;
83 private String mCurFilter;
84 private SFLPhoneHomeActivity sflphoneHome;
alisionf76de3b2013-04-16 15:35:22 -040085 private ISipService service;
alisione2a38e12013-04-25 14:20:20 -040086 ImageButton buttonCall;
alision43a9b362013-05-01 16:30:15 -040087 Button attendedTransfer, conference;
alisione2a38e12013-04-25 14:20:20 -040088 EditText phoneField;
alisiona4325152013-04-19 11:10:03 -040089 private AccountSelectionSpinner mAccountSelectionSpinner;
alisionf76de3b2013-04-16 15:35:22 -040090 private AccountListReceiver mAccountList;
alisiona4325152013-04-19 11:10:03 -040091 private boolean isReady = false;
92
alisionf76de3b2013-04-16 15:35:22 -040093 static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY };
94 static final String[] CONTACTS_PHONES_PROJECTION = new String[] { Phone.NUMBER, Phone.TYPE };
95 static final String[] CONTACTS_SIP_PROJECTION = new String[] { SipAddress.SIP_ADDRESS, SipAddress.TYPE };
96
alision371b77e2013-04-23 14:51:26 -040097 private Callbacks mCallbacks = sDummyCallbacks;
98 /**
99 * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity.
100 */
101 private static Callbacks sDummyCallbacks = new Callbacks() {
102 @Override
103 public void onCallSelected(SipCall c) {
104 }
105
alisione2a38e12013-04-25 14:20:20 -0400106 @Override
107 public ISipService getService() {
108 // TODO Auto-generated method stub
109 return null;
110 }
alision371b77e2013-04-23 14:51:26 -0400111 };
112
113 /**
114 * The Activity calling this fragment has to implement this interface
115 *
116 */
117 public interface Callbacks {
118 public void onCallSelected(SipCall c);
alision43a9b362013-05-01 16:30:15 -0400119
alisione2a38e12013-04-25 14:20:20 -0400120 public ISipService getService();
alision371b77e2013-04-23 14:51:26 -0400121
122 }
123
alisionf76de3b2013-04-16 15:35:22 -0400124 @Override
125 public void onAttach(Activity activity) {
126 super.onAttach(activity);
127 sflphoneHome = (SFLPhoneHomeActivity) activity;
128 service = ((SFLphoneApplication) sflphoneHome.getApplication()).getSipService();
alision371b77e2013-04-23 14:51:26 -0400129 if (!(activity instanceof Callbacks)) {
130 throw new IllegalStateException("Activity must implement fragment's callbacks.");
131 }
132
133 mCallbacks = (Callbacks) activity;
alisionf76de3b2013-04-16 15:35:22 -0400134 Log.w(TAG, "onAttach() service=" + service + ", accountList=" + mAccountList);
135 }
alision43a9b362013-05-01 16:30:15 -0400136
alision371b77e2013-04-23 14:51:26 -0400137 @Override
alision43a9b362013-05-01 16:30:15 -0400138 public void onDetach() {
alision371b77e2013-04-23 14:51:26 -0400139 super.onDetach();
140 mCallbacks = sDummyCallbacks;
141 }
alisionf76de3b2013-04-16 15:35:22 -0400142
143 public String getSelectedAccount() {
alision371b77e2013-04-23 14:51:26 -0400144 return mAccountSelectionSpinner.getAccount();
alisionf76de3b2013-04-16 15:35:22 -0400145 }
146
147 /**
148 * Runnable that fill information in a contact card asynchroniously.
149 */
alision43a9b362013-05-01 16:30:15 -0400150 /*
151 * public static class InfosLoader implements Runnable { private View view; private long cid; private ContentResolver cr;
152 *
153 * public InfosLoader(Context context, View element, long contact_id) { cid = contact_id; cr = context.getContentResolver(); view = element; }
154 *
155 * public static Bitmap loadContactPhoto(ContentResolver cr, long id) { Uri uri =
156 * ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); InputStream input =
157 * ContactsContract.Contacts.openContactPhotoInputStream(cr, uri); if (input == null) { return null; } return BitmapFactory.decodeStream(input); }
158 *
159 * @Override public void run() { final Bitmap photo_bmp = loadContactPhoto(cr, cid);
160 *
161 * Cursor phones = cr.query(CommonDataKinds.Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION, CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]
162 * { Long.toString(cid) }, null);
163 *
164 * final List<String> numbers = new ArrayList<String>(); while (phones.moveToNext()) { String number =
165 * phones.getString(phones.getColumnIndex(CommonDataKinds.Phone.NUMBER)); // int type =
166 * phones.getInt(phones.getColumnIndex(CommonDataKinds.Phone.TYPE)); numbers.add(number); } phones.close(); // TODO: same for SIP adresses.
167 *
168 * final Bitmap bmp = photo_bmp; view.post(new Runnable() {
169 *
170 * @Override public void run() { } }); } }
171 */
alisionf76de3b2013-04-16 15:35:22 -0400172
alisionf76de3b2013-04-16 15:35:22 -0400173 public CallElementListFragment() {
174 super();
175 }
176
177 public void setAccountList(AccountListReceiver accountList) {
178 mAccountList = accountList;
179 }
180
181 public void addCall(SipCall c) {
182 Log.i(TAG, "Adding call " + c.mCallInfo.mDisplayName);
183 mAdapter.add(c);
184 }
185
alision371b77e2013-04-23 14:51:26 -0400186 // public void removeCall(SipCall c) {
187 // Log.i(TAG, "Removing call " + c.mCallInfo.mDisplayName);
188 // mAdapter.remove(c);
189 // }
alisionf76de3b2013-04-16 15:35:22 -0400190
191 @Override
192 public void onActivityCreated(Bundle savedInstanceState) {
193 super.onActivityCreated(savedInstanceState);
194
195 // Give some text to display if there is no data. In a real
196 // application this would come from a resource.
197 // setEmptyText("No phone numbers");
198
199 // We have a menu item to show in action bar.
200 setHasOptionsMenu(true);
201
202 // Create an empty adapter we will use to display the loaded data.
alision5f899632013-04-22 17:26:56 -0400203 ArrayList<SipCall> calls = new ArrayList<SipCall>();
alisionf76de3b2013-04-16 15:35:22 -0400204 mAdapter = new CallElementAdapter(getActivity(), calls);
205 setListAdapter(mAdapter);
206
207 // Start out with a progress indicator.
208 // setListShown(false);
209
210 // Prepare the loader. Either re-connect with an existing one,
211 // or start a new one.
212 // getLoaderManager().initLoader(0, null, this)
213
214 final Context context = getActivity();
215 ListView lv = getListView();
216 lv.setOnItemLongClickListener(new OnItemLongClickListener() {
217 @Override
218 public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
219 Log.i(TAG, "On Long Click");
220 final CharSequence[] items = { "Hang up Call", "Send Message", "Add to Conference" };
221 final SipCall call = (SipCall) mAdapter.getItem(pos);
alisiona4325152013-04-19 11:10:03 -0400222 // // FIXME
223 // service = sflphoneApplication.getSipService();
alisionf76de3b2013-04-16 15:35:22 -0400224 AlertDialog.Builder builder = new AlertDialog.Builder(context);
225 builder.setTitle("Action to perform with " + call.mCallInfo.mDisplayName).setCancelable(true)
226 .setItems(items, new DialogInterface.OnClickListener() {
227 public void onClick(DialogInterface dialog, int item) {
228 Log.i(TAG, "Selected " + items[item]);
229 switch (item) {
230 case 0:
231 call.notifyServiceHangup(service);
232 break;
233 case 1:
234 call.sendTextMessage();
235 // Need to hangup this call immediately since no way to do it after this action
236 call.notifyServiceHangup(service);
237 break;
238 case 2:
239 call.addToConference();
240 // Need to hangup this call immediately since no way to do it after this action
241 call.notifyServiceHangup(service);
242 break;
243 default:
244 break;
245 }
246 }
247 });
248 AlertDialog alert = builder.create();
249 alert.show();
250
251 return true;
252 }
253 });
alision371b77e2013-04-23 14:51:26 -0400254
255 lv.setOnItemClickListener(new OnItemClickListener() {
256
257 @Override
258 public void onItemClick(AdapterView<?> arg0, View v, int pos, long arg3) {
259 mCallbacks.onCallSelected((SipCall) mAdapter.getItem(pos));
260
261 }
262
263 });
264 }
265
266 public void launchCallActivity(SipCall call) {
267 Log.i(TAG, "Launch Call Activity");
268 Bundle bundle = new Bundle();
269 bundle.putParcelable("CallInfo", call.mCallInfo);
270 Intent intent = new Intent().setClass(getActivity(), CallActivity.class);
271 intent.putExtras(bundle);
272 getActivity().startActivity(intent);
alisionf76de3b2013-04-16 15:35:22 -0400273 }
274
275 @Override
276 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
alisiona4325152013-04-19 11:10:03 -0400277 Log.i(TAG, "onCreateView");
alisionf76de3b2013-04-16 15:35:22 -0400278 View inflatedView = inflater.inflate(R.layout.frag_call_element, container, false);
279
alisiona4325152013-04-19 11:10:03 -0400280 mAccountSelectionSpinner = (AccountSelectionSpinner) inflatedView.findViewById(R.id.account_selection_button);
alisione2a38e12013-04-25 14:20:20 -0400281 phoneField = (EditText) inflatedView.findViewById(R.id.phoneNumberTextEntry);
282 buttonCall = (ImageButton) inflatedView.findViewById(R.id.buttonCall);
283
284 buttonCall.setOnClickListener(new OnClickListener() {
285 @Override
286 public void onClick(View v) {
287 processingNewCallAction();
288 }
289 });
alision43a9b362013-05-01 16:30:15 -0400290
291 attendedTransfer = (Button) inflatedView.findViewById(R.id.button_attended);
292 attendedTransfer.setOnClickListener(new OnClickListener() {
293
294 @Override
295 public void onClick(View v) {
296 if (mAdapter.getCount() == 2) {
297 try {
298 service.attendedTransfer(mAdapter.getItem(0).getCallId(), mAdapter.getItem(1).getCallId());
299 mAdapter.clear();
300 } catch (RemoteException e) {
301 Log.e(TAG, e.toString());
302 }
303 } else {
304 Toast.makeText(getActivity(), "You need two calls one on Hold the other current to bind them", Toast.LENGTH_LONG).show();
305 }
306
307 }
308 });
alisione2a38e12013-04-25 14:20:20 -0400309
alision43a9b362013-05-01 16:30:15 -0400310 conference = (Button) inflatedView.findViewById(R.id.button_conf);
311 conference.setOnClickListener(new OnClickListener() {
312
313 @Override
314 public void onClick(View v) {
315 if (mAdapter.getCount() == 2) {
316 try {
317 service.joinParticipant(mAdapter.getItem(0).getCallId(), mAdapter.getItem(1).getCallId());
318 } catch (RemoteException e) {
319 Log.e(TAG, e.toString());
320 }
321 } else {
322 Toast.makeText(getActivity(), "You need two calls one on Hold the other current to create a conference", Toast.LENGTH_LONG).show();
323 }
324 }
325 });
326
alisiona4325152013-04-19 11:10:03 -0400327 isReady = true;
alisione2a38e12013-04-25 14:20:20 -0400328 if (mCallbacks.getService() != null) {
alision43a9b362013-05-01 16:30:15 -0400329
alisione2a38e12013-04-25 14:20:20 -0400330 onServiceSipBinded(mCallbacks.getService());
alisiona4325152013-04-19 11:10:03 -0400331 }
alisionf76de3b2013-04-16 15:35:22 -0400332 return inflatedView;
333 }
alision43a9b362013-05-01 16:30:15 -0400334
alisione2a38e12013-04-25 14:20:20 -0400335 public void processingNewCallAction() {
336 // String accountID = mAccountList.currentAccountID;
337 String accountID = mAccountSelectionSpinner.getAccount();
alision43a9b362013-05-01 16:30:15 -0400338
alisione2a38e12013-04-25 14:20:20 -0400339 String to = phoneField.getText().toString();
340
341 Random random = new Random();
342 String callID = Integer.toString(random.nextInt());
343 SipCall.CallInfo info = new SipCall.CallInfo();
344
345 info.mCallID = callID;
346 info.mAccountID = accountID;
347 info.mDisplayName = "Cool Guy!";
348 info.mPhone = to;
349 info.mEmail = "coolGuy@coolGuy.com";
350 info.mCallType = SipCall.CALL_TYPE_OUTGOING;
351
352 SipCall call = CallListReceiver.getCallInstance(info);
353 mCallbacks.onCallSelected(call);
alisione2a38e12013-04-25 14:20:20 -0400354
355 try {
356 service.placeCall(info.mAccountID, info.mCallID, info.mPhone);
357 } catch (RemoteException e) {
358 Log.e(TAG, "Cannot call service method", e);
359 }
360 addCall(call);
361 }
362
alisionf76de3b2013-04-16 15:35:22 -0400363 @Override
364 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
365
alisiona4325152013-04-19 11:10:03 -0400366 Log.i(TAG, "onCreateLoader");
alisionf76de3b2013-04-16 15:35:22 -0400367 Uri baseUri;
368
369 if (mCurFilter != null) {
370 baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter));
371 } else {
372 baseUri = Contacts.CONTENT_URI;
373 }
374
375 // Now create and return a CursorLoader that will take care of
376 // creating a Cursor for the data being displayed.
377 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME
378 + " != '' ))";
379 // String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.DISPLAY_NAME + " != '' ))";
380
381 return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
382 }
383
384 @Override
385 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
alisiona4325152013-04-19 11:10:03 -0400386 Log.i(TAG, "onLoadFinished");
alisionf76de3b2013-04-16 15:35:22 -0400387 // Swap the new cursor in. (The framework will take care of closing the
388 // old cursor once we return.)
389 // mAdapter.swapCursor(data);
390
391 // The list should now be shown.
392 /*
393 * if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); }
394 */
395 }
396
397 @Override
398 public void onLoaderReset(Loader<Cursor> loader) {
399 // This is called when the last Cursor provided to onLoadFinished()
400 // above is about to be closed. We need to make sure we are no
401 // longer using it.
402 // mAdapter.swapCursor(null);
403 }
alisiona4325152013-04-19 11:10:03 -0400404
405 /**
406 * Called by activity to pass a reference to sipservice to Fragment.
407 *
408 * @param isip
409 */
410 public void onServiceSipBinded(ISipService isip) {
411
412 if (isReady) {
413 service = isip;
414 ArrayList<String> accountList;
415 try {
alisione2a38e12013-04-25 14:20:20 -0400416 accountList = (ArrayList<String>) mCallbacks.getService().getAccountList();
417 mAccountSelectionSpinner.populate(mCallbacks.getService(), accountList);
alisiona4325152013-04-19 11:10:03 -0400418 } catch (RemoteException e) {
419 Log.i(TAG, e.toString());
420 }
421 }
422
423 }
424
alision371b77e2013-04-23 14:51:26 -0400425 public void updateCall(String iD, String newState) {
426 mAdapter.update(iD, newState);
427
428 }
429
alisionf76de3b2013-04-16 15:35:22 -0400430}