Java tutorial
/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2015, Telestax Inc and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * * For questions related to commercial use licensing, please contact sales@telestax.com. * */ package com.telestax.restcomm_olympus; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.FragmentTransaction; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.ListIterator; import org.mobicents.restcomm.android.client.sdk.RCClient; import org.mobicents.restcomm.android.client.sdk.RCConnection; import org.mobicents.restcomm.android.client.sdk.RCConnectionListener; import org.mobicents.restcomm.android.client.sdk.RCDevice; import org.mobicents.restcomm.android.client.sdk.util.PercentFrameLayout; public class CallActivity extends AppCompatActivity implements RCConnectionListener, View.OnClickListener, KeypadFragment.OnFragmentInteractionListener { private RCConnection connection, pendingConnection; SharedPreferences prefs; private static final String TAG = "CallActivity"; private HashMap<String, Object> connectParams; // = new HashMap<String, Object>(); private HashMap<String, Object> acceptParams; // = new HashMap<String, Object>(); private RCDevice device; private boolean pendingError = false; private boolean activityVisible = false; private boolean muteAudio = false; private boolean muteVideo = false; private boolean isVideo = false; // handler for the timer private Handler timerHandler = new Handler(); int secondsElapsed = 0; private final int PERMISSION_REQUEST_DANGEROUS = 1; private AlertDialog alertDialog; private boolean callOutgoing = true; ImageButton btnMuteAudio, btnMuteVideo; ImageButton btnHangup; ImageButton btnAnswer, btnAnswerAudio; ImageButton btnKeypad; KeypadFragment keypadFragment; TextView lblCall, lblStatus, lblTimer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set window styles for fullscreen-window size. Needs to be done before // adding content. requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); setContentView(R.layout.activity_call); // Initialize UI btnHangup = (ImageButton) findViewById(R.id.button_hangup); btnHangup.setOnClickListener(this); btnAnswer = (ImageButton) findViewById(R.id.button_answer); btnAnswer.setOnClickListener(this); btnAnswerAudio = (ImageButton) findViewById(R.id.button_answer_audio); btnAnswerAudio.setOnClickListener(this); btnMuteAudio = (ImageButton) findViewById(R.id.button_mute_audio); btnMuteAudio.setOnClickListener(this); btnMuteVideo = (ImageButton) findViewById(R.id.button_mute_video); btnMuteVideo.setOnClickListener(this); btnKeypad = (ImageButton) findViewById(R.id.button_keypad); btnKeypad.setOnClickListener(this); lblCall = (TextView) findViewById(R.id.label_call); lblStatus = (TextView) findViewById(R.id.label_status); lblTimer = (TextView) findViewById(R.id.label_timer); alertDialog = new AlertDialog.Builder(CallActivity.this).create(); device = RCClient.listDevices().get(0); PreferenceManager.setDefaultValues(this, "preferences.xml", MODE_PRIVATE, R.xml.preferences, false); prefs = PreferenceManager.getDefaultSharedPreferences(this); // Get Intent parameters. final Intent intent = getIntent(); if (intent.getAction().equals(RCDevice.OUTGOING_CALL)) { btnAnswer.setVisibility(View.INVISIBLE); btnAnswerAudio.setVisibility(View.INVISIBLE); } else { btnAnswer.setVisibility(View.VISIBLE); btnAnswerAudio.setVisibility(View.VISIBLE); } keypadFragment = new KeypadFragment(); lblTimer.setVisibility(View.INVISIBLE); // these might need to be moved to Resume() btnMuteAudio.setVisibility(View.INVISIBLE); btnMuteVideo.setVisibility(View.INVISIBLE); btnKeypad.setVisibility(View.INVISIBLE); activityVisible = true; // open keypad FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(R.id.keypad_fragment_container, keypadFragment); ft.hide(keypadFragment); ft.commit(); //handleCall(intent); } @Override protected void onPause() { super.onPause(); Log.i(TAG, "%% onPause"); /* if (pendingConnection != null) { // incoming ringing pendingConnection.reject(); pendingConnection = null; } else { if (connection != null) { // incoming established or outgoing any state (pending, connecting, connected) if (connection.getState() == RCConnection.ConnectionState.CONNECTED) { connection.disconnect(); } connection = null; pendingConnection = null; } } finish(); */ } @Override protected void onStart() { super.onStart(); Log.i(TAG, "%% onStart"); activityVisible = true; handleCall(getIntent()); } @Override protected void onStop() { super.onStop(); Log.i(TAG, "%% onStop"); activityVisible = false; if (timerHandler != null) { timerHandler.removeCallbacksAndMessages(null); } if (pendingConnection != null) { // incoming ringing pendingConnection.reject(); pendingConnection = null; } else { if (connection != null) { // incoming established or outgoing any state (pending, connecting, connected) if (connection.getState() == RCConnection.ConnectionState.CONNECTED) { connection.disconnect(); } connection = null; pendingConnection = null; } } //finish(); } @Override protected void onResume() { super.onResume(); // The activity has become visible (it is now "resumed"). Log.i(TAG, "%% onResume"); } private void handleCall(Intent intent) { isVideo = intent.getBooleanExtra(RCDevice.EXTRA_VIDEO_ENABLED, false); if (intent.getAction().equals(RCDevice.OUTGOING_CALL)) { String text; if (isVideo) { text = "Video Calling "; } else { text = "Audio Calling "; } lblCall.setText(text + intent.getStringExtra(RCDevice.EXTRA_DID).replaceAll(".*?sip:", "").replaceAll("@.*$", "")); lblStatus.setText("Initiating Call..."); connectParams = new HashMap<String, Object>(); connectParams.put(RCConnection.ParameterKeys.CONNECTION_PEER, intent.getStringExtra(RCDevice.EXTRA_DID)); connectParams.put(RCConnection.ParameterKeys.CONNECTION_VIDEO_ENABLED, intent.getBooleanExtra(RCDevice.EXTRA_VIDEO_ENABLED, false)); connectParams.put(RCConnection.ParameterKeys.CONNECTION_LOCAL_VIDEO, findViewById(R.id.local_video_layout)); connectParams.put(RCConnection.ParameterKeys.CONNECTION_REMOTE_VIDEO, findViewById(R.id.remote_video_layout)); // by default we use VP8 for video as it tends to be more adopted, but you can override that and specify VP9 as follows: //connectParams.put(RCConnection.ParameterKeys.CONNECTION_PREFERRED_VIDEO_CODEC, "VP9"); // *** if you want to add custom SIP headers, please uncomment this //HashMap<String, String> sipHeaders = new HashMap<>(); //sipHeaders.put("X-SIP-Header1", "Value1"); //connectParams.put(RCConnection.ParameterKeys.CONNECTION_CUSTOM_SIP_HEADERS, sipHeaders); handlePermissions(isVideo); } if (intent.getAction().equals(RCDevice.INCOMING_CALL)) { String text; if (isVideo) { text = "Video Call from "; } else { text = "Audio Call from "; } lblCall.setText(text + intent.getStringExtra(RCDevice.EXTRA_DID).replaceAll(".*?sip:", "").replaceAll("@.*$", "")); lblStatus.setText("Call Received..."); //callOutgoing = false; pendingConnection = device.getPendingConnection(); pendingConnection.setConnectionListener(this); // the number from which we got the call String incomingCallDid = intent.getStringExtra(RCDevice.EXTRA_DID); HashMap<String, String> customHeaders = (HashMap<String, String>) intent .getSerializableExtra(RCDevice.EXTRA_CUSTOM_HEADERS); if (customHeaders != null) { Log.i(TAG, "Got custom headers in incoming call: " + customHeaders.toString()); } } } // UI Events public void onClick(View view) { if (view.getId() == R.id.button_hangup) { if (pendingConnection != null) { // incoming ringing lblStatus.setText("Rejecting Call..."); pendingConnection.reject(); pendingConnection = null; } else { if (connection != null) { // incoming established or outgoing any state (pending, connecting, connected) lblStatus.setText("Disconnecting Call..."); connection.disconnect(); connection = null; pendingConnection = null; } else { Log.e(TAG, "Error: not connected/connecting/pending"); } } finish(); } else if (view.getId() == R.id.button_answer) { if (pendingConnection != null) { lblStatus.setText("Answering Call..."); btnAnswer.setVisibility(View.INVISIBLE); btnAnswerAudio.setVisibility(View.INVISIBLE); acceptParams = new HashMap<String, Object>(); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_VIDEO_ENABLED, true); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_LOCAL_VIDEO, findViewById(R.id.local_video_layout)); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_REMOTE_VIDEO, findViewById(R.id.remote_video_layout)); // Check permissions asynchronously and then accept the call handlePermissions(true); } } else if (view.getId() == R.id.button_answer_audio) { if (pendingConnection != null) { lblStatus.setText("Answering Call..."); btnAnswer.setVisibility(View.INVISIBLE); btnAnswerAudio.setVisibility(View.INVISIBLE); acceptParams = new HashMap<String, Object>(); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_VIDEO_ENABLED, false); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_LOCAL_VIDEO, findViewById(R.id.local_video_layout)); acceptParams.put(RCConnection.ParameterKeys.CONNECTION_REMOTE_VIDEO, findViewById(R.id.remote_video_layout)); // Check permissions asynchronously and then accept the call handlePermissions(false); } } else if (view.getId() == R.id.button_keypad) { keypadFragment.setConnection(connection); // show keypad FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.show(keypadFragment); ft.commit(); } else if (view.getId() == R.id.button_mute_audio) { if (connection != null) { if (!muteAudio) { btnMuteAudio.setImageResource(R.drawable.audio_muted_50x50); } else { btnMuteAudio.setImageResource(R.drawable.audio_active_50x50); } muteAudio = !muteAudio; connection.setAudioMuted(muteAudio); } } else if (view.getId() == R.id.button_mute_video) { if (connection != null) { if (!muteVideo) { btnMuteVideo.setImageResource(R.drawable.video_muted_50x50); } else { btnMuteVideo.setImageResource(R.drawable.video_active_50x50); } muteVideo = !muteVideo; connection.setVideoMuted(muteVideo); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_call, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } // RCConnection Listeners public void onConnecting(RCConnection connection) { Log.i(TAG, "RCConnection connecting"); lblStatus.setText("Started Connecting..."); } public void onConnected(RCConnection connection, HashMap<String, String> customHeaders) { Log.i(TAG, "RCConnection connected, customHeaders: " + customHeaders); lblStatus.setText("Connected"); btnMuteAudio.setVisibility(View.VISIBLE); btnMuteVideo.setVisibility(View.VISIBLE); btnKeypad.setVisibility(View.VISIBLE); lblTimer.setVisibility(View.VISIBLE); startTimer(); // reset to no mute at beggining of new call muteAudio = false; muteVideo = false; setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } public void onDisconnected(RCConnection connection) { Log.i(TAG, "RCConnection disconnected"); lblStatus.setText("Disconnected"); btnMuteAudio.setVisibility(View.INVISIBLE); btnMuteVideo.setVisibility(View.INVISIBLE); this.connection = null; pendingConnection = null; setVolumeControlStream(AudioManager.STREAM_MUSIC); if (!pendingError) { finish(); } else { pendingError = false; } } public void onCancelled(RCConnection connection) { Log.i(TAG, "RCConnection cancelled"); lblStatus.setText("Cancelled"); this.connection = null; pendingConnection = null; finish(); } public void onDeclined(RCConnection connection) { Log.i(TAG, "RCConnection declined"); lblStatus.setText("Declined"); this.connection = null; pendingConnection = null; finish(); } public void onDisconnected(RCConnection connection, int errorCode, String errorText) { pendingError = true; showOkAlert("RCConnection Error", errorText); this.connection = null; pendingConnection = null; } public void onError(RCConnection connection, int errorCode, String errorText) { pendingError = true; showOkAlert("RCConnection Error", errorText); this.connection = null; pendingConnection = null; } public void onDigitSent(RCConnection connection, int statusCode, String statusText) { } public void onLocalVideo(RCConnection connection) { } public void onRemoteVideo(RCConnection connection) { } // Handle android permissions needed for Marshmallow (API 23) devices or later private boolean handlePermissions(boolean isVideo) { ArrayList<String> permissions = new ArrayList<>( Arrays.asList(new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.USE_SIP })); if (isVideo) { // Only add CAMERA permission if this is a video call permissions.add(Manifest.permission.CAMERA); } if (!havePermissions(permissions)) { // Dynamic permissions where introduced in M // PERMISSION_REQUEST_DANGEROUS is an app-defined int constant. The callback method (i.e. onRequestPermissionsResult) gets the result of the request. ActivityCompat.requestPermissions(this, permissions.toArray(new String[permissions.size()]), PERMISSION_REQUEST_DANGEROUS); return false; } resumeCall(); return true; } // Checks if user has given 'permissions'. If it has them all, it returns true. If not it returns false and modifies 'permissions' to keep only // the permission that got rejected, so that they can be passed later into requestPermissions() private boolean havePermissions(ArrayList<String> permissions) { boolean allGranted = true; ListIterator<String> it = permissions.listIterator(); while (it.hasNext()) { if (ActivityCompat.checkSelfPermission(this, it.next()) != PackageManager.PERMISSION_GRANTED) { allGranted = false; } else { // permission granted, remove it from permissions it.remove(); } } return allGranted; } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case PERMISSION_REQUEST_DANGEROUS: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the contacts-related task you need to do. resumeCall(); } else { // permission denied, boo! Disable the functionality that depends on this permission. Log.e(TAG, "Error: Permission(s) denied; aborting call"); showOkAlert("Call Error", "Permission(s) denied; aborting call"); } return; } // other 'case' lines to check for other permissions this app might request } } // Resume call after permissions are checked private void resumeCall() { if (connectParams != null) { // outgoing call connection = device.connect(connectParams, this); if (connection == null) { Log.e(TAG, "Error: error connecting"); showOkAlert("RCDevice Error", "Device is Offline"); } } else if (acceptParams != null) { // incoming call pendingConnection.accept(acceptParams); connection = this.pendingConnection; pendingConnection = null; } } // Helpers private void showOkAlert(final String title, final String detail) { if (activityVisible) { if (alertDialog.isShowing()) { Log.w(TAG, "Alert already showing, hiding to show new alert"); alertDialog.hide(); } alertDialog.setTitle(title); alertDialog.setMessage(detail); alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); finish(); } }); alertDialog.show(); } } @Override public void onFragmentInteraction(String action) { if (action.equals("cancel")) { FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.hide(keypadFragment); ft.commit(); } } public void startTimer() { String time = String.format("%02d:%02d:%02d", secondsElapsed / 3600, (secondsElapsed % 3600) / 60, secondsElapsed % 60); lblTimer.setText(time); secondsElapsed++; timerHandler.removeCallbacksAndMessages(null); // schedule a registration update after 'registrationRefresh' seconds Runnable timerRunnable = new Runnable() { @Override public void run() { startTimer(); } }; timerHandler.postDelayed(timerRunnable, 1000); } }