1/*
2 * Copyright (C) 2010 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
17package com.example.android.sip;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.PendingIntent;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.os.Bundle;
28import android.preference.PreferenceManager;
29import android.util.Log;
30import android.view.*;
31import android.net.sip.*;
32import android.widget.EditText;
33import android.widget.TextView;
34import android.widget.ToggleButton;
35
36import java.text.ParseException;
37
38/**
39 * Handles all calling, receiving calls, and UI interaction in the WalkieTalkie app.
40 */
41public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
42
43    public String sipAddress = null;
44
45    public SipManager manager = null;
46    public SipProfile me = null;
47    public SipAudioCall call = null;
48    public IncomingCallReceiver callReceiver;
49
50    private static final int CALL_ADDRESS = 1;
51    private static final int SET_AUTH_INFO = 2;
52    private static final int UPDATE_SETTINGS_DIALOG = 3;
53    private static final int HANG_UP = 4;
54
55
56    @Override
57    public void onCreate(Bundle savedInstanceState) {
58
59        super.onCreate(savedInstanceState);
60        setContentView(R.layout.walkietalkie);
61
62        ToggleButton pushToTalkButton = (ToggleButton) findViewById(R.id.pushToTalk);
63        pushToTalkButton.setOnTouchListener(this);
64
65        // Set up the intent filter.  This will be used to fire an
66        // IncomingCallReceiver when someone calls the SIP address used by this
67        // application.
68        IntentFilter filter = new IntentFilter();
69        filter.addAction("android.SipDemo.INCOMING_CALL");
70        callReceiver = new IncomingCallReceiver();
71        this.registerReceiver(callReceiver, filter);
72
73        // "Push to talk" can be a serious pain when the screen keeps turning off.
74        // Let's prevent that.
75        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
76
77        initializeManager();
78    }
79
80    @Override
81    public void onStart() {
82        super.onStart();
83        // When we get back from the preference setting Activity, assume
84        // settings have changed, and re-login with new auth info.
85        initializeManager();
86    }
87
88    @Override
89    public void onDestroy() {
90        super.onDestroy();
91        if (call != null) {
92            call.close();
93        }
94
95        closeLocalProfile();
96
97        if (callReceiver != null) {
98            this.unregisterReceiver(callReceiver);
99        }
100    }
101
102    public void initializeManager() {
103        if(manager == null) {
104          manager = SipManager.newInstance(this);
105        }
106
107        initializeLocalProfile();
108    }
109
110    /**
111     * Logs you into your SIP provider, registering this device as the location to
112     * send SIP calls to for your SIP address.
113     */
114    public void initializeLocalProfile() {
115        if (manager == null) {
116            return;
117        }
118
119        if (me != null) {
120            closeLocalProfile();
121        }
122
123        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
124        String username = prefs.getString("namePref", "");
125        String domain = prefs.getString("domainPref", "");
126        String password = prefs.getString("passPref", "");
127
128        if (username.length() == 0 || domain.length() == 0 || password.length() == 0) {
129            showDialog(UPDATE_SETTINGS_DIALOG);
130            return;
131        }
132
133        try {
134            SipProfile.Builder builder = new SipProfile.Builder(username, domain);
135            builder.setPassword(password);
136            me = builder.build();
137
138            Intent i = new Intent();
139            i.setAction("android.SipDemo.INCOMING_CALL");
140            PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
141            manager.open(me, pi, null);
142
143
144            // This listener must be added AFTER manager.open is called,
145            // Otherwise the methods aren't guaranteed to fire.
146
147            manager.setRegistrationListener(me.getUriString(), new SipRegistrationListener() {
148                    public void onRegistering(String localProfileUri) {
149                        updateStatus("Registering with SIP Server...");
150                    }
151
152                    public void onRegistrationDone(String localProfileUri, long expiryTime) {
153                        updateStatus("Ready");
154                    }
155
156                    public void onRegistrationFailed(String localProfileUri, int errorCode,
157                            String errorMessage) {
158                        updateStatus("Registration failed.  Please check settings.");
159                    }
160                });
161        } catch (ParseException pe) {
162            updateStatus("Connection Error.");
163        } catch (SipException se) {
164            updateStatus("Connection error.");
165        }
166    }
167
168    /**
169     * Closes out your local profile, freeing associated objects into memory
170     * and unregistering your device from the server.
171     */
172    public void closeLocalProfile() {
173        if (manager == null) {
174            return;
175        }
176        try {
177            if (me != null) {
178                manager.close(me.getUriString());
179            }
180        } catch (Exception ee) {
181            Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
182        }
183    }
184
185    /**
186     * Make an outgoing call.
187     */
188    public void initiateCall() {
189
190        updateStatus(sipAddress);
191
192        try {
193            SipAudioCall.Listener listener = new SipAudioCall.Listener() {
194                // Much of the client's interaction with the SIP Stack will
195                // happen via listeners.  Even making an outgoing call, don't
196                // forget to set up a listener to set things up once the call is established.
197                @Override
198                public void onCallEstablished(SipAudioCall call) {
199                    call.startAudio();
200                    call.setSpeakerMode(true);
201                    call.toggleMute();
202                    updateStatus(call);
203                }
204
205                @Override
206                public void onCallEnded(SipAudioCall call) {
207                    updateStatus("Ready.");
208                }
209            };
210
211            call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);
212
213        }
214        catch (Exception e) {
215            Log.i("WalkieTalkieActivity/InitiateCall", "Error when trying to close manager.", e);
216            if (me != null) {
217                try {
218                    manager.close(me.getUriString());
219                } catch (Exception ee) {
220                    Log.i("WalkieTalkieActivity/InitiateCall",
221                            "Error when trying to close manager.", ee);
222                    ee.printStackTrace();
223                }
224            }
225            if (call != null) {
226                call.close();
227            }
228        }
229    }
230
231    /**
232     * Updates the status box at the top of the UI with a messege of your choice.
233     * @param status The String to display in the status box.
234     */
235    public void updateStatus(final String status) {
236        // Be a good citizen.  Make sure UI changes fire on the UI thread.
237        this.runOnUiThread(new Runnable() {
238            public void run() {
239                TextView labelView = (TextView) findViewById(R.id.sipLabel);
240                labelView.setText(status);
241            }
242        });
243    }
244
245    /**
246     * Updates the status box with the SIP address of the current call.
247     * @param call The current, active call.
248     */
249    public void updateStatus(SipAudioCall call) {
250        String useName = call.getPeerProfile().getDisplayName();
251        if(useName == null) {
252          useName = call.getPeerProfile().getUserName();
253        }
254        updateStatus(useName + "@" + call.getPeerProfile().getSipDomain());
255    }
256
257    /**
258     * Updates whether or not the user's voice is muted, depending on whether the button is pressed.
259     * @param v The View where the touch event is being fired.
260     * @param event The motion to act on.
261     * @return boolean Returns false to indicate that the parent view should handle the touch event
262     * as it normally would.
263     */
264    public boolean onTouch(View v, MotionEvent event) {
265        if (call == null) {
266            return false;
267        } else if (event.getAction() == MotionEvent.ACTION_DOWN && call != null && call.isMuted()) {
268            call.toggleMute();
269        } else if (event.getAction() == MotionEvent.ACTION_UP && !call.isMuted()) {
270            call.toggleMute();
271        }
272        return false;
273    }
274
275    public boolean onCreateOptionsMenu(Menu menu) {
276        menu.add(0, CALL_ADDRESS, 0, "Call someone");
277        menu.add(0, SET_AUTH_INFO, 0, "Edit your SIP Info.");
278        menu.add(0, HANG_UP, 0, "End Current Call.");
279
280        return true;
281    }
282
283    public boolean onOptionsItemSelected(MenuItem item) {
284        switch (item.getItemId()) {
285            case CALL_ADDRESS:
286                showDialog(CALL_ADDRESS);
287                break;
288            case SET_AUTH_INFO:
289                updatePreferences();
290                break;
291            case HANG_UP:
292                if(call != null) {
293                    try {
294                      call.endCall();
295                    } catch (SipException se) {
296                        Log.d("WalkieTalkieActivity/onOptionsItemSelected",
297                                "Error ending call.", se);
298                    }
299                    call.close();
300                }
301                break;
302        }
303        return true;
304    }
305
306    @Override
307    protected Dialog onCreateDialog(int id) {
308        switch (id) {
309            case CALL_ADDRESS:
310
311                LayoutInflater factory = LayoutInflater.from(this);
312                final View textBoxView = factory.inflate(R.layout.call_address_dialog, null);
313                return new AlertDialog.Builder(this)
314                        .setTitle("Call Someone.")
315                        .setView(textBoxView)
316                        .setPositiveButton(
317                                android.R.string.ok, new DialogInterface.OnClickListener() {
318                                    public void onClick(DialogInterface dialog, int whichButton) {
319                                        EditText textField = (EditText)
320                                                (textBoxView.findViewById(R.id.calladdress_edit));
321                                        sipAddress = textField.getText().toString();
322                                        initiateCall();
323
324                                    }
325                        })
326                        .setNegativeButton(
327                                android.R.string.cancel, new DialogInterface.OnClickListener() {
328                                    public void onClick(DialogInterface dialog, int whichButton) {
329                                        // Noop.
330                                    }
331                        })
332                        .create();
333
334            case UPDATE_SETTINGS_DIALOG:
335                return new AlertDialog.Builder(this)
336                        .setMessage("Please update your SIP Account Settings.")
337                        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
338                            public void onClick(DialogInterface dialog, int whichButton) {
339                                updatePreferences();
340                            }
341                        })
342                        .setNegativeButton(
343                                android.R.string.cancel, new DialogInterface.OnClickListener() {
344                                    public void onClick(DialogInterface dialog, int whichButton) {
345                                        // Noop.
346                                    }
347                        })
348                        .create();
349        }
350        return null;
351    }
352
353    public void updatePreferences() {
354        Intent settingsActivity = new Intent(getBaseContext(),
355                SipSettings.class);
356        startActivity(settingsActivity);
357    }
358}
359