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 org.restcomm.android.olympus; import android.Manifest; import android.app.AlertDialog; import android.app.DialogFragment; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import com.testfairy.TestFairy; //import net.hockeyapp.android.CrashManager; //import net.hockeyapp.android.UpdateManager; import org.restcomm.android.sdk.RCClient; import org.restcomm.android.sdk.RCDevice; import org.restcomm.android.sdk.RCDeviceListener; import org.restcomm.android.sdk.RCPresenceEvent; import java.util.HashMap; import static org.restcomm.android.olympus.ContactsController.CONTACT_KEY; import static org.restcomm.android.olympus.ContactsController.CONTACT_VALUE; public class MainActivity extends AppCompatActivity implements MainFragment.Callbacks, RCDeviceListener, View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener, AddUserDialogFragment.ContactDialogListener, ServiceConnection, ComponentCallbacks, ComponentCallbacks2 { private RCDevice device = null; boolean serviceBound = false; private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 56; // DEBUG //ArrayList<String []> list = new ArrayList<>(); private static final String TAG = "MainActivity"; SharedPreferences prefs; private HashMap<String, Object> params; private MainFragment listFragment; private AlertDialog alertDialog; private RCConnectivityStatus previousConnectivityStatus = RCConnectivityStatus.RCConnectivityStatusNone; private static final String APP_VERSION = "Restcomm Android Olympus Client " + BuildConfig.VERSION_NAME + "#" + BuildConfig.VERSION_CODE; //"Restcomm Android Olympus Client 1.0.0-BETA4#20"; FloatingActionButton btnAdd; public static String ACTION_DISCONNECTED_BACKGROUND = "org.restcomm.android.olympus.ACTION_DISCONNECTED_BACKGROUND"; private static final int CONNECTION_REQUEST = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i(TAG, "%% onCreate"); Log.i(TAG, "Olympus Version: " + APP_VERSION); // For TestFairy troubleshooting. IMPORTANT: remove for production apps, as TestFairy sends logs, screenshots, etc to their servers //TestFairy.begin(this, "#TESTFAIRY_APP_TOKEN"); TestFairy.begin(this, "52d3ee1de1b7681d1fcbbc3295b2b86a0aa43fcc"); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); // TODO set proper image at xhdpi with 48x48 toolbar.setNavigationIcon(R.drawable.bar_icon_24dp); toolbar.setTitle(getTitle()); listFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.item_list); btnAdd = (FloatingActionButton) findViewById(R.id.imageButton_add); btnAdd.setOnClickListener(this); alertDialog = new AlertDialog.Builder(MainActivity.this, R.style.SimpleAlertStyle).create(); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); prefs = PreferenceManager.getDefaultSharedPreferences(this); // preferences prefs.registerOnSharedPreferenceChangeListener(this); checkPermissions(); // No longer needed, we'll change with toast // set it to wifi by default to avoid the status message when starting with wifi //previousConnectivityStatus = RCConnectivityStatus.RCConnectivityStatusWiFi; } public void checkPermissions() { if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.READ_CONTACTS)) { //Don't need to add anything over here for now --Sagar Vakkala } else { //Didn't add any explanation right now --Sagar Vakkala ActivityCompat.requestPermissions(MainActivity.this, new String[] { Manifest.permission.READ_CONTACTS }, MY_PERMISSIONS_REQUEST_READ_CONTACTS); //Requesting permissions for reading contacts } } } @Override protected void onStart() { super.onStart(); // The activity is about to become visible. Log.i(TAG, "%% onStart"); bindService(new Intent(this, RCDevice.class), this, Context.BIND_AUTO_CREATE); } @Override protected void onResume() { super.onResume(); // The activity has become visible (it is now "resumed"). Log.i(TAG, "%% onResume"); if (device != null) { // needed if we are returning from Message screen that becomes the Device listener device.setDeviceListener(this); } } @Override protected void onPause() { super.onPause(); // Another activity is taking focus (this activity is about to be "paused"). Log.i(TAG, "%% onPause"); } @Override protected void onStop() { super.onStop(); // The activity is no longer visible (it is now "stopped") Log.i(TAG, "%% onStop"); // Unbind from the service if (serviceBound) { //device.detach(); unbindService(this); serviceBound = false; } } @Override protected void onDestroy() { super.onDestroy(); // The activity is about to be destroyed. Log.i(TAG, "%% onDestroy"); /* RCClient.shutdown(); device = null; */ prefs.unregisterOnSharedPreferenceChangeListener(this); } /* private void checkForCrashes() { CrashManager.register(this); } private void checkForUpdates() { // Remove this for store builds! UpdateManager.register(this); } private void unregisterManagers() { UpdateManager.unregister(); } */ public void onLowMemory() { Log.e(TAG, "onLowMemory"); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); Log.e(TAG, "onTrimMemory: " + level); } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); // We get this intent from CallActivity, when the App is in the background and the user has requested hangup via notification // In that case we don't wont to interrupt the user from what they are currently doing in the foreground, so we just finish() if (intent.getAction() != null && intent.getAction().equals(ACTION_DISCONNECTED_BACKGROUND)) { finish(); } } // Callbacks for service binding, passed to bindService() @Override public void onServiceConnected(ComponentName className, IBinder service) { Log.i(TAG, "%% onServiceConnected"); // We've bound to LocalService, cast the IBinder and get LocalService instance RCDevice.RCDeviceBinder binder = (RCDevice.RCDeviceBinder) service; device = binder.getService(); if (!device.isInitialized()) { HashMap<String, Object> params = new HashMap<String, Object>(); params.put(RCDevice.ParameterKeys.INTENT_INCOMING_CALL, new Intent(RCDevice.ACTION_INCOMING_CALL, null, getApplicationContext(), CallActivity.class)); params.put(RCDevice.ParameterKeys.INTENT_INCOMING_MESSAGE, new Intent(RCDevice.ACTION_INCOMING_MESSAGE, null, getApplicationContext(), MessageActivity.class)); params.put(RCDevice.ParameterKeys.SIGNALING_DOMAIN, prefs.getString(RCDevice.ParameterKeys.SIGNALING_DOMAIN, "")); params.put(RCDevice.ParameterKeys.SIGNALING_USERNAME, prefs.getString(RCDevice.ParameterKeys.SIGNALING_USERNAME, "android-sdk")); params.put(RCDevice.ParameterKeys.SIGNALING_PASSWORD, prefs.getString(RCDevice.ParameterKeys.SIGNALING_PASSWORD, "1234")); params.put(RCDevice.ParameterKeys.MEDIA_ICE_URL, prefs.getString(RCDevice.ParameterKeys.MEDIA_ICE_URL, "")); params.put(RCDevice.ParameterKeys.MEDIA_ICE_USERNAME, prefs.getString(RCDevice.ParameterKeys.MEDIA_ICE_USERNAME, "")); params.put(RCDevice.ParameterKeys.MEDIA_ICE_PASSWORD, prefs.getString(RCDevice.ParameterKeys.MEDIA_ICE_PASSWORD, "")); params.put(RCDevice.ParameterKeys.MEDIA_ICE_DOMAIN, prefs.getString(RCDevice.ParameterKeys.MEDIA_ICE_DOMAIN, "")); params.put(RCDevice.ParameterKeys.MEDIA_TURN_ENABLED, prefs.getBoolean(RCDevice.ParameterKeys.MEDIA_TURN_ENABLED, true)); params.put(RCDevice.ParameterKeys.SIGNALING_SECURE_ENABLED, prefs.getBoolean(RCDevice.ParameterKeys.SIGNALING_SECURE_ENABLED, true)); // The SDK provides the user with default sounds for calling, ringing, busy (declined) and message, but the user can override them // by providing their own resource files (i.e. .wav, .mp3, etc) at res/raw passing them with Resource IDs like R.raw.user_provided_calling_sound //params.put(RCDevice.ParameterKeys.RESOURCE_SOUND_CALLING, R.raw.user_provided_calling_sound); //params.put(RCDevice.ParameterKeys.RESOURCE_SOUND_RINGING, R.raw.user_provided_ringing_sound); //params.put(RCDevice.ParameterKeys.RESOURCE_SOUND_DECLINED, R.raw.user_provided_declined_sound); //params.put(RCDevice.ParameterKeys.RESOURCE_SOUND_MESSAGE, R.raw.user_provided_message_sound); // These are for debugging purposes, NOT for release builds! //params.put(RCDevice.ParameterKeys.DEBUG_JAIN_SIP_LOGGING_ENABLED, prefs.getBoolean(RCDevice.ParameterKeys.DEBUG_JAIN_SIP_LOGGING_ENABLED, true)); //params.put(RCDevice.ParameterKeys.DEBUG_JAIN_DISABLE_CERTIFICATE_VERIFICATION, prefs.getBoolean(RCDevice.ParameterKeys.DEBUG_JAIN_DISABLE_CERTIFICATE_VERIFICATION, true)); device.setLogLevel(Log.VERBOSE); device.initialize(getApplicationContext(), params, this); } if (device.getState() == RCDevice.DeviceState.OFFLINE) { getSupportActionBar() .setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorTextSecondary))); } else { getSupportActionBar() .setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorPrimary))); handleExternalCall(); } serviceBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { Log.i(TAG, "%% onServiceDisconnected"); serviceBound = false; } /** * MainFragment Callbacks */ @Override public void onItemSelected(HashMap<String, String> contact, MainFragment.ContactSelectionType type) { Intent intent = new Intent(this, MessageActivity.class); intent.setAction(MessageActivity.ACTION_OPEN_MESSAGE_SCREEN); intent.putExtra(MessageActivity.EXTRA_CONTACT_NAME, contact.get(CONTACT_KEY)); intent.putExtra(RCDevice.EXTRA_DID, contact.get(CONTACT_VALUE)); startActivity(intent); // forward to onActionClicked //onActionClicked(ActionFragment.ActionType.ACTION_TYPE_VIDEO_CALL, contact.get(CONTACT_KEY), contact.get(CONTACT_VALUE)); } public void onContactUpdate(HashMap<String, String> contact, int type) { AddUserDialogFragment newFragment = AddUserDialogFragment.newInstance( AddUserDialogFragment.DIALOG_TYPE_UPDATE_CONTACT, contact.get(CONTACT_KEY), contact.get(CONTACT_VALUE)); newFragment.show(getSupportFragmentManager(), "dialog"); //newFragment.show(getFragmentManager(), "dialog"); } /* public void onAccessoryClicked(HashMap<String, String> contact) { DialogFragment actionFragment = ActionFragment.newInstance(contact.get(CONTACT_KEY), contact.get(CONTACT_VALUE)); actionFragment.show(getFragmentManager(), "dialog-accessory"); } */ /** * Callbacks for AddUserDialogFragment */ public void onDialogPositiveClick(int type, String username, String sipuri) { listFragment.updateContact(type, username, sipuri); } public void onDialogNegativeClick() { } /** * Callbacks for ActionFragment */ /* public void onActionClicked(ActionFragment.ActionType action, String username, String sipuri) { if (action == ActionFragment.ActionType.ACTION_TYPE_VIDEO_CALL) { Intent intent = new Intent(this, CallActivity.class); intent.setAction(RCDevice.ACTION_OUTGOING_CALL); intent.putExtra(RCDevice.EXTRA_DID, sipuri); intent.putExtra(RCDevice.EXTRA_VIDEO_ENABLED, true); startActivityForResult(intent, CONNECTION_REQUEST); } if (action == ActionFragment.ActionType.ACTION_TYPE_AUDIO_CALL) { Intent intent = new Intent(this, CallActivity.class); intent.setAction(RCDevice.ACTION_OUTGOING_CALL); intent.putExtra(RCDevice.EXTRA_DID, sipuri); intent.putExtra(RCDevice.EXTRA_VIDEO_ENABLED, false); startActivityForResult(intent, CONNECTION_REQUEST); } if (action == ActionFragment.ActionType.ACTION_TYPE_TEXT_MESSAGE) { Intent intent = new Intent(this, MessageActivity.class); intent.setAction(MessageActivity.ACTION_OPEN_MESSAGE_SCREEN); intent.putExtra(RCDevice.EXTRA_DID, sipuri); startActivity(intent); } } */ /** * Main Activity onClick */ public void onClick(View view) { if (view.getId() == R.id.imageButton_add) { /* TODO: Issue #380: once we figure out the issue with the backgrounding we need to uncomment this, but also place it to a suitable place Intent intent = new Intent(this, CallActivity.class); intent.setAction(RCDevice.LIVE_CALL); startActivityForResult(intent, CONNECTION_REQUEST); */ //DialogFragment newFragment = AddUserDialogFragment.newInstance(AddUserDialogFragment.DIALOG_TYPE_ADD_CONTACT, "", ""); //newFragment.show(getFragmentManager(), "dialog"); AddUserDialogFragment newFragment = AddUserDialogFragment .newInstance(AddUserDialogFragment.DIALOG_TYPE_ADD_CONTACT, "", ""); newFragment.show(getSupportFragmentManager(), "dialog"); // DEBUG: for adding memory pressure to the App /* for (int i = 0; i < 200; i++) { list.add(new String[100000]); } Log.i(TAG, "List size: " + list.size()); */ } } /** * RCDeviceListener callbacks */ public void onStartListening(RCDevice device, RCDeviceListener.RCConnectivityStatus connectivityStatus) { handleConnectivityUpdate(connectivityStatus, null); } public void onStopListening(RCDevice device) { } public void onStopListening(RCDevice device, int errorCode, String errorText) { if (errorCode == RCClient.ErrorCodes.SUCCESS.ordinal()) { handleConnectivityUpdate(RCConnectivityStatus.RCConnectivityStatusNone, "RCDevice: " + errorText); } else { handleConnectivityUpdate(RCConnectivityStatus.RCConnectivityStatusNone, "RCDevice Error: " + errorText); } } public void onInitialized(RCDevice device, RCDeviceListener.RCConnectivityStatus connectivityStatus, int statusCode, String statusText) { if (statusCode == RCClient.ErrorCodes.SUCCESS.ordinal()) { handleConnectivityUpdate(connectivityStatus, "RCDevice successfully initialized, using: " + connectivityStatus); } else if (statusCode == RCClient.ErrorCodes.ERROR_DEVICE_NO_CONNECTIVITY.ordinal()) { // This is not really an error, since if connectivity comes back the RCDevice will resume automatically handleConnectivityUpdate(connectivityStatus, null); } else { //Toast.makeText(getApplicationContext(), "RCDevice Initialization Error: " + statusText, Toast.LENGTH_LONG).show(); //showOkAlert("RCDevice Initialization Error", statusText); //handleConnectivityUpdate(connectivityStatus, "RCDevice Initialization Error: " + statusText); Toast.makeText(getApplicationContext(), "RCDevice Initialization Error: " + statusText, Toast.LENGTH_LONG).show(); } } public void onInitializationError(int errorCode, String errorText) { Toast.makeText(getApplicationContext(), "RCDevice Initialization Error: " + errorText, Toast.LENGTH_LONG) .show(); } public void onReleased(RCDevice device, int statusCode, String statusText) { if (statusCode != RCClient.ErrorCodes.SUCCESS.ordinal()) { //showOkAlert("RCDevice Release Error", statusText); Toast.makeText(getApplicationContext(), "RCDevice Release Error: " + statusText, Toast.LENGTH_LONG) .show(); } else { handleConnectivityUpdate(RCConnectivityStatus.RCConnectivityStatusNone, "RCDevice Released: " + statusText); } } public void onConnectivityUpdate(RCDevice device, RCConnectivityStatus connectivityStatus) { handleConnectivityUpdate(connectivityStatus, null); } public void handleConnectivityUpdate(RCConnectivityStatus connectivityStatus, String text) { if (text == null) { if (connectivityStatus == RCConnectivityStatus.RCConnectivityStatusNone) { text = "RCDevice connectivity change: Lost connectivity"; } if (connectivityStatus == RCConnectivityStatus.RCConnectivityStatusWiFi) { text = "RCDevice connectivity change: Reestablished connectivity (Wifi)"; } if (connectivityStatus == RCConnectivityStatus.RCConnectivityStatusCellular) { text = "RCDevice connectivity change: Reestablished connectivity (Cellular)"; } if (connectivityStatus == RCConnectivityStatus.RCConnectivityStatusEthernet) { text = "RCDevice connectivity change: Reestablished connectivity (Ethernet)"; } } if (connectivityStatus == RCConnectivityStatus.RCConnectivityStatusNone) { getSupportActionBar() .setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorTextSecondary))); } else { getSupportActionBar() .setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorPrimary))); handleExternalCall(); } Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show(); this.previousConnectivityStatus = connectivityStatus; } // Handle call issued by external App via CALL intent private void handleExternalCall() { // We have connectivity (either wifi or cellular), check if we have any external call requests and service it GlobalPreferences globalPreferences = new GlobalPreferences(getApplicationContext()); String externalCallUriString = globalPreferences.getExternalCallUri(); if (!externalCallUriString.isEmpty()) { Uri externalCallUri = Uri.parse(externalCallUriString); String parsedUriString = ""; if (externalCallUri.getScheme().contains("sip")) { // either 'sip' or 'restcomm-sip' // normalize 'restcomm-sip' and replace with 'sip' //String normalized = externalCallUriString.replace("restcomm-sip", "sip"); // also replace '://' with ':' so that the SIP stack can understand it //parsedUriString = normalized.replace("://", ":"); parsedUriString = externalCallUriString.replace("restcomm-sip", "sip"); } else { // either 'tel', 'restcomm-tel', 'client' or 'restcomm-client'. Return just the host part, like 'bob' or '1235' that the Restcomm SDK can handle parsedUriString = externalCallUri.getSchemeSpecificPart(); } Intent intent = new Intent(this, CallActivity.class); intent.setAction(RCDevice.ACTION_OUTGOING_CALL); intent.putExtra(RCDevice.EXTRA_DID, parsedUriString); intent.putExtra(RCDevice.EXTRA_VIDEO_ENABLED, true); startActivityForResult(intent, CONNECTION_REQUEST); globalPreferences.setExternalCallUri(""); } } public void onMessageSent(RCDevice device, int statusCode, String statusText, String jobId) { } public boolean receivePresenceEvents(RCDevice device) { return false; } public void onPresenceChanged(RCDevice device, RCPresenceEvent presenceEvent) { } /** * Settings Menu callbacks */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } @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_main, 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) { Intent i = new Intent(this, SettingsActivity.class); startActivity(i); } if (id == R.id.action_submit_logs) { Intent intent = new Intent(this, BugReportActivity.class); //intent.setAction(RCDevice.ACTION_OUTGOING_CALL); //intent.putExtra(RCDevice.EXTRA_DID, parsedUriString); //intent.putExtra(RCDevice.EXTRA_VIDEO_ENABLED, true); startActivity(intent); } if (id == R.id.action_about) { DialogFragment newFragment = AboutFragment.newInstance(); newFragment.show(getFragmentManager(), "dialog-about"); } return super.onOptionsItemSelected(item); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } /** * Helpers */ /* private void showOkAlert(final String title, final String detail) { 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(); } }); alertDialog.show(); } */ }