Repository: GleasonK/AndroidRTC Branch: master Commit: 9756044380ec Files: 61 Total size: 132.7 KB Directory structure: gitextract_4p2k_p1l/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── androidrtc/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── androidrtc/ │ │ ├── IncomingCallActivity.java │ │ ├── LoginActivity.java │ │ ├── MainActivity.java │ │ ├── VideoChatActivity.java │ │ ├── adapters/ │ │ │ ├── ChatAdapter.java │ │ │ └── HistoryAdapter.java │ │ ├── adt/ │ │ │ ├── ChatMessage.java │ │ │ ├── ChatUser.java │ │ │ └── HistoryItem.java │ │ ├── servers/ │ │ │ └── XirSysRequest.java │ │ └── util/ │ │ ├── Constants.java │ │ └── LogRTCListener.java │ └── res/ │ ├── drawable/ │ │ ├── light_fade_down.xml │ │ ├── light_fade_up.xml │ │ ├── online_circle.xml │ │ ├── round_button.xml │ │ └── round_button_send.xml │ ├── layout/ │ │ ├── activity_incoming_call.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_video_chat.xml │ │ ├── chat_message_row_layout.xml │ │ └── history_row_layout.xml │ ├── menu/ │ │ ├── menu_incoming_call.xml │ │ ├── menu_login.xml │ │ ├── menu_main.xml │ │ └── menu_video_chat.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-v21/ │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── pnwebrtc/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── pnwebrtc/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── pnwebrtc/ │ │ ├── PnPeer.java │ │ ├── PnPeerConnectionClient.java │ │ ├── PnRTCClient.java │ │ ├── PnRTCListener.java │ │ ├── PnRTCMessage.java │ │ └── PnSignalingParams.java │ └── res/ │ └── values/ │ └── strings.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Kevin Gleason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # AndroidRTC An example of native WebRTC on Android using PubNub's Android SDK signaling. ### Big News: PubNub Android SDK for Signaling! ![cover_img](http://kevingleason.me/AndroidRTC/assets/PnWebRTC.png) This means that you can now create video chatting applications natively on Android in a breeze. Best of all, it is fully compatible with the [PubNub Javascript SDK][JS SDK]! That's right, you are minutes away from creating your very own cross platform video-chatting application. [__Get it now!__][PnWebRTC] _NOTE:_ The following demo uses the PubNub Android SDK for signaling to transfer the metadata and establish the peer-to-peer connection. Once the connection is established, the video and voice runs on public Google STUN/TURN servers. Keep in mind, PubNub can provide the signaling for WebRTC, and requires you to combine it with a hosted WebRTC solution. For more detail on what PubNub does, and what PubNub doesn’t do with WebRTC, check out this article: https://support.pubnub.com/support/solutions/articles/14000043715-does-pubnub-provide-webrtc-and-video-chat- ## The AndroidRTC Example App This app shows how to accomplish signling on a standby channel to coordinate users, then hop into a video chat and use the PubNub JavaScript SDK for signaling. You can even make a call to the AndroidRTC App [from the web interface](http://kevingleason.me/AndroidRTC). Users in this app layout must be subscribed to `username-stdby`, a standby channel. The Android side checks if a user is online by checking presence on the standby channel. In this app, a call can be placed by sending a JSON packet to the user's standby channel: {"call_user":"UserName","call_time":currentTimeMillis} Upon accepting the call, the answerer creates the SDP Offer, and video chat begins. ### Incoming Calls AndroidRTC provides a good example of how to handle incoming calls. ### User Messages This app also shows how to send custom user messages. These messages could be chat, game scores, and much more. [PnWebRTC]:https://github.com/GleasonK/pubnub-android-webrtc [JavaDoc]:http://kevingleason.me/pubnub-android-webrtc/ [AndroidRTC]:https://github.com/GleasonK/AndroidRTC/ [JS SDK]:https://github.com/stephenlb/webrtc-sdk ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 21 buildToolsVersion "20.0.0" defaultConfig { applicationId "me.kevingleason.androidrtc" minSdkVersion 17 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } repositories { flatDir { dirs 'libs' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.pubnub:pubnub-android:3.7.4' compile 'io.pristine:libjingle:9694@aar' compile project(':pnwebrtc') } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/GleasonK/algs4/AndroidSDK/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/me/kevingleason/androidrtc/ApplicationTest.java ================================================ package me.kevingleason.androidrtc; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/IncomingCallActivity.java ================================================ package me.kevingleason.androidrtc; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.pubnub.api.Callback; import com.pubnub.api.Pubnub; import org.json.JSONException; import org.json.JSONObject; import me.kevingleason.androidrtc.util.Constants; import me.kevingleason.pnwebrtc.PnPeerConnectionClient; public class IncomingCallActivity extends Activity { private SharedPreferences mSharedPreferences; private String username; private String callUser; private Pubnub mPubNub; private TextView mCallerID; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_incoming_call); this.mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, MODE_PRIVATE); if (!this.mSharedPreferences.contains(Constants.USER_NAME)){ Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); finish(); return; } this.username = this.mSharedPreferences.getString(Constants.USER_NAME, ""); Bundle extras = getIntent().getExtras(); if (extras==null || !extras.containsKey(Constants.CALL_USER)){ Intent intent = new Intent(this, MainActivity.class); startActivity(intent); Toast.makeText(this, "Need to pass username to IncomingCallActivity in intent extras (Constants.CALL_USER).", Toast.LENGTH_SHORT).show(); finish(); return; } this.callUser = extras.getString(Constants.CALL_USER, ""); this.mCallerID = (TextView) findViewById(R.id.caller_id); this.mCallerID.setText(this.callUser); this.mPubNub = new Pubnub(Constants.PUB_KEY, Constants.SUB_KEY); this.mPubNub.setUUID(this.username); } @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_incoming_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(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public void acceptCall(View view){ Intent intent = new Intent(IncomingCallActivity.this, VideoChatActivity.class); intent.putExtra(Constants.USER_NAME, this.username); intent.putExtra(Constants.CALL_USER, this.callUser); startActivity(intent); } /** * Publish a hangup command if rejecting call. * @param view */ public void rejectCall(View view){ JSONObject hangupMsg = PnPeerConnectionClient.generateHangupPacket(this.username); this.mPubNub.publish(this.callUser,hangupMsg, new Callback() { @Override public void successCallback(String channel, Object message) { Intent intent = new Intent(IncomingCallActivity.this, MainActivity.class); startActivity(intent); } }); } @Override protected void onStop() { super.onStop(); if(this.mPubNub!=null){ this.mPubNub.unsubscribeAll(); } } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/LoginActivity.java ================================================ package me.kevingleason.androidrtc; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import me.kevingleason.androidrtc.util.Constants; /** * Login Activity for the first time the app is opened, or when a user clicks the sign out button. * Saves the username in SharedPreferences. */ public class LoginActivity extends Activity { private EditText mUsername; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); mUsername = (EditText) findViewById(R.id.login_username); Bundle extras = getIntent().getExtras(); if (extras != null){ String lastUsername = extras.getString("oldUsername", ""); mUsername.setText(lastUsername); } } @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_login, 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(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * Takes the username from the EditText, check its validity and saves it if valid. * Then, redirects to the MainActivity. * @param view Button clicked to trigger call to joinChat */ public void joinChat(View view){ String username = mUsername.getText().toString(); if (!validUsername(username)) return; SharedPreferences sp = getSharedPreferences(Constants.SHARED_PREFS,MODE_PRIVATE); SharedPreferences.Editor edit = sp.edit(); edit.putString(Constants.USER_NAME, username); edit.apply(); Intent intent = new Intent(this, MainActivity.class); startActivity(intent); } /** * Optional function to specify what a username in your chat app can look like. * @param username The name entered by a user. * @return is username valid */ private boolean validUsername(String username) { if (username.length() == 0) { mUsername.setError("Username cannot be empty."); return false; } if (username.length() > 16) { mUsername.setError("Username too long."); return false; } return true; } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/MainActivity.java ================================================ package me.kevingleason.androidrtc; import android.app.ListActivity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.pubnub.api.Callback; import com.pubnub.api.Pubnub; import com.pubnub.api.PubnubError; import com.pubnub.api.PubnubException; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import me.kevingleason.androidrtc.adapters.HistoryAdapter; import me.kevingleason.androidrtc.adt.HistoryItem; import me.kevingleason.androidrtc.util.Constants; public class MainActivity extends ListActivity { private SharedPreferences mSharedPreferences; private String username; private String stdByChannel; private Pubnub mPubNub; private ListView mHistoryList; private HistoryAdapter mHistoryAdapter; private EditText mCallNumET; private TextView mUsernameTV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, MODE_PRIVATE); if (!this.mSharedPreferences.contains(Constants.USER_NAME)){ Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); finish(); return; } this.username = this.mSharedPreferences.getString(Constants.USER_NAME, ""); this.stdByChannel = this.username + Constants.STDBY_SUFFIX; this.mHistoryList = getListView(); this.mCallNumET = (EditText) findViewById(R.id.call_num); this.mUsernameTV = (TextView) findViewById(R.id.main_username); this.mUsernameTV.setText(this.username); initPubNub(); this.mHistoryAdapter = new HistoryAdapter(this, new ArrayList(), this.mPubNub); this.mHistoryList.setAdapter(this.mHistoryAdapter); } @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(); //noinspection SimplifiableIfStatement switch(id){ case R.id.action_settings: return true; case R.id.action_sign_out: signOut(); return true; } return super.onOptionsItemSelected(item); } @Override protected void onStop() { super.onStop(); if(this.mPubNub!=null){ this.mPubNub.unsubscribeAll(); } } @Override protected void onRestart() { super.onRestart(); if(this.mPubNub==null){ initPubNub(); } else { subscribeStdBy(); } } /** * Subscribe to standby channel so that it doesn't interfere with the WebRTC Signaling. */ public void initPubNub(){ this.mPubNub = new Pubnub(Constants.PUB_KEY, Constants.SUB_KEY); this.mPubNub.setUUID(this.username); subscribeStdBy(); } /** * Subscribe to standby channel */ private void subscribeStdBy(){ try { this.mPubNub.subscribe(this.stdByChannel, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("MA-iPN", "MESSAGE: " + message.toString()); if (!(message instanceof JSONObject)) return; // Ignore if not JSONObject JSONObject jsonMsg = (JSONObject) message; try { if (!jsonMsg.has(Constants.JSON_CALL_USER)) return; //Ignore Signaling messages. String user = jsonMsg.getString(Constants.JSON_CALL_USER); dispatchIncomingCall(user); } catch (JSONException e){ e.printStackTrace(); } } @Override public void connectCallback(String channel, Object message) { Log.d("MA-iPN", "CONNECTED: " + message.toString()); setUserStatus(Constants.STATUS_AVAILABLE); } @Override public void errorCallback(String channel, PubnubError error) { Log.d("MA-iPN","ERROR: " + error.toString()); } }); } catch (PubnubException e){ Log.d("HERE","HEREEEE"); e.printStackTrace(); } } /** * Take the user to a video screen. USER_NAME is a required field. * @param view button that is clicked to trigger toVideo */ public void makeCall(View view){ String callNum = mCallNumET.getText().toString(); if (callNum.isEmpty() || callNum.equals(this.username)){ showToast("Enter a valid user ID to call."); return; } dispatchCall(callNum); } /**TODO: Debate who calls who. Should one be on standby? Or use State API for busy/available * Check that user is online. If they are, dispatch the call by publishing to their standby * channel. If the publish was successful, then change activities over to the video chat. * The called user will then have the option to accept of decline the call. If they accept, * they will be brought to the video chat activity as well, to connect video/audio. If * they decline, a hangup will be issued, and the VideoChat adapter's onHangup callback will * be invoked. * @param callNum Number to publish a call to. */ public void dispatchCall(final String callNum){ final String callNumStdBy = callNum + Constants.STDBY_SUFFIX; this.mPubNub.hereNow(callNumStdBy, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("MA-dC", "HERE_NOW: " +" CH - " + callNumStdBy + " " + message.toString()); try { int occupancy = ((JSONObject) message).getInt(Constants.JSON_OCCUPANCY); if (occupancy == 0) { showToast("User is not online!"); return; } JSONObject jsonCall = new JSONObject(); jsonCall.put(Constants.JSON_CALL_USER, username); jsonCall.put(Constants.JSON_CALL_TIME, System.currentTimeMillis()); mPubNub.publish(callNumStdBy, jsonCall, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("MA-dC", "SUCCESS: " + message.toString()); Intent intent = new Intent(MainActivity.this, VideoChatActivity.class); intent.putExtra(Constants.USER_NAME, username); intent.putExtra(Constants.CALL_USER, callNum); // Only accept from this number? startActivity(intent); } }); } catch (JSONException e) { e.printStackTrace(); } } }); } /** * Handle incoming calls. TODO: Implement an accept/reject functionality. * @param userId */ private void dispatchIncomingCall(String userId){ showToast("Call from: " + userId); Intent intent = new Intent(MainActivity.this, IncomingCallActivity.class); intent.putExtra(Constants.USER_NAME, username); intent.putExtra(Constants.CALL_USER, userId); startActivity(intent); } private void setUserStatus(String status){ try { JSONObject state = new JSONObject(); state.put(Constants.JSON_STATUS, status); this.mPubNub.setState(this.stdByChannel, this.username, state, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("MA-sUS","State Set: " + message.toString()); } }); } catch (JSONException e){ e.printStackTrace(); } } private void getUserStatus(String userId){ String stdByUser = userId + Constants.STDBY_SUFFIX; this.mPubNub.getState(stdByUser, userId, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("MA-gUS", "User Status: " + message.toString()); } }); } /** * Ensures that toast is run on the UI thread. * @param message */ private void showToast(final String message){ runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } }); } /** * Log out, remove username from SharedPreferences, unsubscribe from PubNub, and send user back * to the LoginActivity */ public void signOut(){ this.mPubNub.unsubscribeAll(); SharedPreferences.Editor edit = this.mSharedPreferences.edit(); edit.remove(Constants.USER_NAME); edit.apply(); Intent intent = new Intent(this, LoginActivity.class); intent.putExtra("oldUsername", this.username); startActivity(intent); } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/VideoChatActivity.java ================================================ package me.kevingleason.androidrtc; import android.app.Activity; import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.AudioSource; import org.webrtc.AudioTrack; import org.webrtc.MediaConstraints; import org.webrtc.MediaStream; import org.webrtc.PeerConnection; import org.webrtc.PeerConnectionFactory; import org.webrtc.VideoCapturer; import org.webrtc.VideoCapturerAndroid; import org.webrtc.VideoRenderer; import org.webrtc.VideoRendererGui; import org.webrtc.VideoSource; import org.webrtc.VideoTrack; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; import me.kevingleason.androidrtc.adapters.ChatAdapter; import me.kevingleason.androidrtc.adt.ChatMessage; import me.kevingleason.androidrtc.servers.XirSysRequest; import me.kevingleason.androidrtc.util.Constants; import me.kevingleason.androidrtc.util.LogRTCListener; import me.kevingleason.pnwebrtc.PnPeer; import me.kevingleason.pnwebrtc.PnRTCClient; import me.kevingleason.pnwebrtc.PnSignalingParams; /** * This chat will begin/subscribe to a video chat. * REQUIRED: The intent must contain a */ public class VideoChatActivity extends ListActivity { public static final String VIDEO_TRACK_ID = "videoPN"; public static final String AUDIO_TRACK_ID = "audioPN"; public static final String LOCAL_MEDIA_STREAM_ID = "localStreamPN"; private PnRTCClient pnRTCClient; private VideoSource localVideoSource; private VideoRenderer.Callbacks localRender; private VideoRenderer.Callbacks remoteRender; private GLSurfaceView videoView; private EditText mChatEditText; private ListView mChatList; private ChatAdapter mChatAdapter; private TextView mCallStatus; private String username; private boolean backPressed = false; private Thread backPressedThread = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_chat); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Bundle extras = getIntent().getExtras(); if (extras == null || !extras.containsKey(Constants.USER_NAME)) { Intent intent = new Intent(this, MainActivity.class); startActivity(intent); Toast.makeText(this, "Need to pass username to VideoChatActivity in intent extras (Constants.USER_NAME).", Toast.LENGTH_SHORT).show(); finish(); return; } this.username = extras.getString(Constants.USER_NAME, ""); this.mChatList = getListView(); this.mChatEditText = (EditText) findViewById(R.id.chat_input); this.mCallStatus = (TextView) findViewById(R.id.call_status); // Set up the List View for chatting List ll = new LinkedList(); mChatAdapter = new ChatAdapter(this, ll); mChatList.setAdapter(mChatAdapter); // First, we initiate the PeerConnectionFactory with our application context and some options. PeerConnectionFactory.initializeAndroidGlobals( this, // Context true, // Audio Enabled true, // Video Enabled true, // Hardware Acceleration Enabled null); // Render EGL Context PeerConnectionFactory pcFactory = new PeerConnectionFactory(); this.pnRTCClient = new PnRTCClient(Constants.PUB_KEY, Constants.SUB_KEY, this.username); List servers = getXirSysIceServers(); if (!servers.isEmpty()){ this.pnRTCClient.setSignalParams(new PnSignalingParams()); } // Returns the number of cams & front/back face device name int camNumber = VideoCapturerAndroid.getDeviceCount(); String frontFacingCam = VideoCapturerAndroid.getNameOfFrontFacingDevice(); String backFacingCam = VideoCapturerAndroid.getNameOfBackFacingDevice(); // Creates a VideoCapturerAndroid instance for the device name VideoCapturer capturer = VideoCapturerAndroid.create(frontFacingCam); // First create a Video Source, then we can make a Video Track localVideoSource = pcFactory.createVideoSource(capturer, this.pnRTCClient.videoConstraints()); VideoTrack localVideoTrack = pcFactory.createVideoTrack(VIDEO_TRACK_ID, localVideoSource); // First we create an AudioSource then we can create our AudioTrack AudioSource audioSource = pcFactory.createAudioSource(this.pnRTCClient.audioConstraints()); AudioTrack localAudioTrack = pcFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource); // To create our VideoRenderer, we can use the included VideoRendererGui for simplicity // First we need to set the GLSurfaceView that it should render to this.videoView = (GLSurfaceView) findViewById(R.id.gl_surface); // Then we set that view, and pass a Runnable to run once the surface is ready VideoRendererGui.setView(videoView, null); // Now that VideoRendererGui is ready, we can get our VideoRenderer. // IN THIS ORDER. Effects which is on top or bottom remoteRender = VideoRendererGui.create(0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, false); localRender = VideoRendererGui.create(0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, true); // We start out with an empty MediaStream object, created with help from our PeerConnectionFactory // Note that LOCAL_MEDIA_STREAM_ID can be any string MediaStream mediaStream = pcFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID); // Now we can add our tracks. mediaStream.addTrack(localVideoTrack); mediaStream.addTrack(localAudioTrack); // First attach the RTC Listener so that callback events will be triggered this.pnRTCClient.attachRTCListener(new DemoRTCListener()); // Then attach your local media stream to the PnRTCClient. // This will trigger the onLocalStream callback. this.pnRTCClient.attachLocalMediaStream(mediaStream); // Listen on a channel. This is your "phone number," also set the max chat users. this.pnRTCClient.listenOn("Kevin"); this.pnRTCClient.setMaxConnections(1); // If the intent contains a number to dial, call it now that you are connected. // Else, remain listening for a call. if (extras.containsKey(Constants.CALL_USER)) { String callUser = extras.getString(Constants.CALL_USER, ""); connectToUser(callUser); } } @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_video_chat, 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(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override protected void onPause() { super.onPause(); this.videoView.onPause(); this.localVideoSource.stop(); } @Override protected void onResume() { super.onResume(); this.videoView.onResume(); this.localVideoSource.restart(); } @Override protected void onDestroy() { super.onDestroy(); if (this.localVideoSource != null) { this.localVideoSource.stop(); } if (this.pnRTCClient != null) { this.pnRTCClient.onDestroy(); } } @Override public void onBackPressed() { if (!this.backPressed){ this.backPressed = true; Toast.makeText(this,"Press back again to end.",Toast.LENGTH_SHORT).show(); this.backPressedThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); backPressed = false; } catch (InterruptedException e){ Log.d("VCA-oBP","Successfully interrupted"); } } }); this.backPressedThread.start(); return; } if (this.backPressedThread != null) this.backPressedThread.interrupt(); super.onBackPressed(); } public List getXirSysIceServers(){ List servers = new ArrayList(); try { servers = new XirSysRequest().execute().get(); } catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } return servers; } public void connectToUser(String user) { this.pnRTCClient.connect(user); } public void hangup(View view) { this.pnRTCClient.closeAllConnections(); endCall(); } private void endCall() { startActivity(new Intent(VideoChatActivity.this, MainActivity.class)); finish(); } public void sendMessage(View view) { String message = mChatEditText.getText().toString(); if (message.equals("")) return; // Return if empty ChatMessage chatMsg = new ChatMessage(this.username, message, System.currentTimeMillis()); mChatAdapter.addMessage(chatMsg); JSONObject messageJSON = new JSONObject(); try { messageJSON.put(Constants.JSON_MSG_UUID, chatMsg.getSender()); messageJSON.put(Constants.JSON_MSG, chatMsg.getMessage()); messageJSON.put(Constants.JSON_TIME, chatMsg.getTimeStamp()); this.pnRTCClient.transmitAll(messageJSON); } catch (JSONException e) { e.printStackTrace(); } // Hide keyboard when you send a message. View focusView = this.getCurrentFocus(); if (focusView != null) { InputMethodManager inputManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } mChatEditText.setText(""); } /** * LogRTCListener is used for debugging purposes, it prints all RTC messages. * DemoRTC is just a Log Listener with the added functionality to append screens. */ private class DemoRTCListener extends LogRTCListener { @Override public void onLocalStream(final MediaStream localStream) { super.onLocalStream(localStream); // Will log values VideoChatActivity.this.runOnUiThread(new Runnable() { @Override public void run() { if(localStream.videoTracks.size()==0) return; localStream.videoTracks.get(0).addRenderer(new VideoRenderer(localRender)); } }); } @Override public void onAddRemoteStream(final MediaStream remoteStream, final PnPeer peer) { super.onAddRemoteStream(remoteStream, peer); // Will log values VideoChatActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(VideoChatActivity.this,"Connected to " + peer.getId(), Toast.LENGTH_SHORT).show(); try { if(remoteStream.audioTracks.size()==0 || remoteStream.videoTracks.size()==0) return; mCallStatus.setVisibility(View.GONE); remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(remoteRender)); VideoRendererGui.update(remoteRender, 0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, false); VideoRendererGui.update(localRender, 72, 65, 25, 25, VideoRendererGui.ScalingType.SCALE_ASPECT_FIT, true); } catch (Exception e){ e.printStackTrace(); } } }); } @Override public void onMessage(PnPeer peer, Object message) { super.onMessage(peer, message); // Will log values if (!(message instanceof JSONObject)) return; //Ignore if not JSONObject JSONObject jsonMsg = (JSONObject) message; try { String uuid = jsonMsg.getString(Constants.JSON_MSG_UUID); String msg = jsonMsg.getString(Constants.JSON_MSG); long time = jsonMsg.getLong(Constants.JSON_TIME); final ChatMessage chatMsg = new ChatMessage(uuid, msg, time); VideoChatActivity.this.runOnUiThread(new Runnable() { @Override public void run() { mChatAdapter.addMessage(chatMsg); } }); } catch (JSONException e){ e.printStackTrace(); } } @Override public void onPeerConnectionClosed(PnPeer peer) { super.onPeerConnectionClosed(peer); VideoChatActivity.this.runOnUiThread(new Runnable() { @Override public void run() { mCallStatus.setText("Call Ended..."); mCallStatus.setVisibility(View.VISIBLE); } }); try {Thread.sleep(1500);} catch (InterruptedException e){e.printStackTrace();} Intent intent = new Intent(VideoChatActivity.this, MainActivity.class); startActivity(intent); finish(); } } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/ChatAdapter.java ================================================ package me.kevingleason.androidrtc.adapters; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.widget.ArrayAdapter; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; import me.kevingleason.androidrtc.R; import me.kevingleason.androidrtc.adt.ChatMessage; /** * Created by GleasonK on 6/25/15. */ public class ChatAdapter extends ArrayAdapter { private static final long FADE_TIMEOUT = 3000; private final Context context; private LayoutInflater inflater; private List values; public ChatAdapter(Context context, List values) { super(context, R.layout.chat_message_row_layout, android.R.id.text1, values); this.context = context; this.inflater = LayoutInflater.from(context); this.values=values; } class ViewHolder { TextView sender; TextView message; TextView timeStamp; ChatMessage chatMsg; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ChatMessage chatMsg; if(position >= values.size()){ chatMsg = new ChatMessage("","",0); } // Catch Edge Case else { chatMsg = this.values.get(position); } ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.chat_message_row_layout, parent, false); holder.sender = (TextView) convertView.findViewById(R.id.chat_user); holder.message = (TextView) convertView.findViewById(R.id.chat_message); holder.timeStamp = (TextView) convertView.findViewById(R.id.chat_timestamp); convertView.setTag(holder); Log.d("Adapter", "Recreating fadeout."); } else { holder = (ViewHolder) convertView.getTag(); } holder.sender.setText(chatMsg.getSender() + ": "); holder.message.setText(chatMsg.getMessage()); holder.timeStamp.setText(formatTimeStamp(chatMsg.getTimeStamp())); holder.chatMsg=chatMsg; setFadeOut3(convertView, chatMsg); return convertView; } @Override public int getCount() { return this.values.size(); } @Override public boolean hasStableIds() { return true; } @Override public long getItemId(int position){ if (position >= values.size()){ return -1; } return values.get(position).hashCode(); } public void removeMsg(int loc){ this.values.remove(loc); notifyDataSetChanged(); } public void addMessage(ChatMessage chatMsg){ this.values.add(chatMsg); notifyDataSetChanged(); } private void setFadeOut2(final View view, final ChatMessage message){ Log.i("AdapterFade", "Caling Fade2"); view.animate().setDuration(1000).setStartDelay(2000).alpha(0) .withEndAction(new Runnable() { @Override public void run() { if (values.contains(message)) values.remove(message); notifyDataSetChanged(); view.setAlpha(1); } }); } private void setFadeOut3(final View view, final ChatMessage message){ Log.i("AdapterFade", "Caling Fade3"); long elapsed = System.currentTimeMillis() - message.getTimeStamp(); if (elapsed >= FADE_TIMEOUT){ if (values.contains(message)) values.remove(message); notifyDataSetChanged(); return; } view.animate().setStartDelay(FADE_TIMEOUT - elapsed).setDuration(1500).alpha(0) .withEndAction(new Runnable() { @Override public void run() { if (values.contains(message)){ values.remove(message); } notifyDataSetChanged(); view.setAlpha(1); } }); } private void setFadeOut(final View view, final ChatMessage message){ long elapsed = System.currentTimeMillis() - message.getTimeStamp(); if (elapsed >= FADE_TIMEOUT){ if (values.contains(message)) values.remove(message); notifyDataSetChanged(); return; } view.setHasTransientState(true); Animation fadeOut = new AlphaAnimation(1, 0); fadeOut.setInterpolator(new AccelerateInterpolator()); //and this fadeOut.setStartOffset(FADE_TIMEOUT - elapsed); fadeOut.setDuration(1000); AnimationSet animation = new AnimationSet(false); animation.addAnimation(fadeOut); animation.setRepeatCount(1); view.setAnimation(animation); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { if (values.contains(message)){ values.remove(message); } notifyDataSetChanged(); view.setAlpha(1); view.setHasTransientState(false); } @Override public void onAnimationRepeat(Animation animation) {} }); } /** * Format the long System.currentTimeMillis() to a better looking timestamp. Uses a calendar * object to format with the user's current time zone. * @param timeStamp * @return */ public static String formatTimeStamp(long timeStamp){ // Create a DateFormatter object for displaying date in specified format. SimpleDateFormat formatter = new SimpleDateFormat("h:mm.ss a"); // Create a calendar object that will convert the date and time value in milliseconds to date. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timeStamp); return formatter.format(calendar.getTime()); } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/HistoryAdapter.java ================================================ package me.kevingleason.androidrtc.adapters; import android.app.Activity; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.TextView; import com.pubnub.api.Callback; import com.pubnub.api.Pubnub; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import me.kevingleason.androidrtc.MainActivity; import me.kevingleason.androidrtc.R; import me.kevingleason.androidrtc.adt.ChatUser; import me.kevingleason.androidrtc.adt.HistoryItem; import me.kevingleason.androidrtc.util.Constants; /** * Created by GleasonK on 7/31/15. */ public class HistoryAdapter extends ArrayAdapter { private final Context context; private Pubnub mPubNub; private LayoutInflater inflater; private List values; private Map users; public HistoryAdapter(Context context, List values, Pubnub pubnub) { super(context, R.layout.history_row_layout, android.R.id.text1, values); this.context = context; this.inflater = LayoutInflater.from(context); this.mPubNub = pubnub; this.values = values; this.users = new HashMap(); updateHistory(); } class ViewHolder { TextView user; TextView status; TextView time; ImageButton callBtn; HistoryItem histItem; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final HistoryItem hItem = this.values.get(position); ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.history_row_layout, parent, false); holder.user = (TextView) convertView.findViewById(R.id.history_name); holder.status = (TextView) convertView.findViewById(R.id.history_status); holder.time = (TextView) convertView.findViewById(R.id.history_time); holder.callBtn = (ImageButton) convertView.findViewById(R.id.history_call); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.user.setText(hItem.getUser().getUserId()); holder.time.setText(formatTimeStamp(hItem.getTimeStamp())); holder.status.setText(hItem.getUser().getStatus()); if (hItem.getUser().getStatus().equals(Constants.STATUS_OFFLINE)) getUserStatus(hItem.getUser(), holder.status); holder.callBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ((MainActivity)context).dispatchCall(hItem.getUser().getUserId()); } }); holder.histItem=hItem; return convertView; } @Override public int getCount() { return this.values.size(); } public void removeButton(int loc){ this.values.remove(loc); notifyDataSetChanged(); } private void getUserStatus(final ChatUser user, final TextView statusView){ String stdByUser = user.getUserId() + Constants.STDBY_SUFFIX; this.mPubNub.getState(stdByUser, user.getUserId(), new Callback() { @Override public void successCallback(String channel, Object message) { JSONObject jsonMsg = (JSONObject) message; try { if (!jsonMsg.has(Constants.JSON_STATUS)) return; final String status = jsonMsg.getString(Constants.JSON_STATUS); user.setStatus(status); ((Activity)getContext()).runOnUiThread(new Runnable() { @Override public void run() { statusView.setText(status); } }); } catch (JSONException e){ e.printStackTrace(); } } }); } public void updateHistory(){ final List rtcHistory = new LinkedList(); String usrStdBy = this.mPubNub.getUUID() + Constants.STDBY_SUFFIX; this.mPubNub.history(usrStdBy, 25, new Callback() { @Override public void successCallback(String channel, Object message) { Log.d("HA-uH","HISTORY: " + message.toString()); try { JSONArray historyArray = ((JSONArray) message).getJSONArray(0); for(int i=0; i< historyArray.length(); i++){ JSONObject historyJson = historyArray.getJSONObject(i); String userName = historyJson.getString(Constants.JSON_CALL_USER); long timeStamp = historyJson.getLong(Constants.JSON_CALL_TIME); ChatUser cUser = new ChatUser(userName); if (users.containsKey(userName)){ cUser = users.get(userName); } else { users.put(userName, cUser); } rtcHistory.add(0, new HistoryItem(cUser, timeStamp)); } values = rtcHistory; updateAdapter(); } catch (JSONException e){ // e.printStackTrace(); } } }); } private void updateAdapter(){ ((Activity)context).runOnUiThread(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); } /** * Format the long System.currentTimeMillis() to a better looking timestamp. Uses a calendar * object to format with the user's current time zone. * @param timeStamp * @return */ public static String formatTimeStamp(long timeStamp){ // Create a DateFormatter object for displaying date in specified format. SimpleDateFormat formatter = new SimpleDateFormat("MMM d, h:mm a"); // Create a calendar object that will convert the date and time value in milliseconds to date. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timeStamp); return formatter.format(calendar.getTime()); } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatMessage.java ================================================ package me.kevingleason.androidrtc.adt; /** * Created by GleasonK on 6/25/15. */ public class ChatMessage { private String sender; private String message; private long timeStamp; public ChatMessage(String sender, String message, long timeStamp){ this.sender = sender; this.message = message; this.timeStamp=timeStamp; } public String getSender() { return sender; } public String getMessage() { return message; } public long getTimeStamp() { return timeStamp; } @Override public int hashCode() { return (this.sender + this.message + this.timeStamp).hashCode(); } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatUser.java ================================================ package me.kevingleason.androidrtc.adt; import me.kevingleason.androidrtc.util.Constants; /** * Created by GleasonK on 7/31/15. */ public class ChatUser { private String userId; private String status; public ChatUser(String userId) { this.userId = userId; this.status = Constants.STATUS_OFFLINE; } public ChatUser(String userId, String status) { this.userId = userId; this.status = status; } public String getUserId() { return userId; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public boolean equals(Object o) { if (this==o) return true; if (!(o instanceof ChatUser)) return false; ChatUser cu = (ChatUser)o; return this.userId.equals(((ChatUser) o).getUserId()); } @Override public int hashCode() { return this.getUserId().hashCode(); } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/adt/HistoryItem.java ================================================ package me.kevingleason.androidrtc.adt; /** * Created by GleasonK on 7/31/15. */ public class HistoryItem { private ChatUser user; private Long timeStamp; public HistoryItem(ChatUser user, Long timeStamp){ this.user=user; this.timeStamp=timeStamp; } public ChatUser getUser() { return user; } public Long getTimeStamp() { return timeStamp; } } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/servers/XirSysRequest.java ================================================ package me.kevingleason.androidrtc.servers; import android.os.AsyncTask; import android.util.Log; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.webrtc.PeerConnection; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; /** * Created by GleasonK on 11/12/15. */ public class XirSysRequest extends AsyncTask> { public List doInBackground(Void... params){ List servers = new ArrayList(); HttpClient httpClient = new DefaultHttpClient(); HttpPost request = new HttpPost("https://service.xirsys.com/ice"); List data = new ArrayList(); data.add(new BasicNameValuePair("room", "default")); data.add(new BasicNameValuePair("application", "default")); data.add(new BasicNameValuePair("domain", "kevingleason.me")); data.add(new BasicNameValuePair("ident", "gleasonk")); data.add(new BasicNameValuePair("secret", "b9066b5e-1f75-11e5-866a-c400956a1e19")); data.add(new BasicNameValuePair("secure", "1")); //Encoding POST data try { request.setEntity(new UrlEncodedFormEntity(data)); HttpResponse response = httpClient.execute(request); // write response to log Log.d("Http Post Response:", response.toString()); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); StringBuilder builder = new StringBuilder(); for (String line=null; (line = reader.readLine()) != null;) { builder.append(line).append("\n"); } JSONTokener tokener = new JSONTokener(builder.toString()); JSONObject json = new JSONObject(tokener); if (json.isNull("e")){ JSONArray iceServers = json.getJSONObject("d").getJSONArray("iceServers"); for (int i = 0; i < iceServers.length(); i++) { JSONObject srv = iceServers.getJSONObject(i); PeerConnection.IceServer is; if (srv.has("username")) is = new PeerConnection.IceServer(srv.getString("url"), srv.getString("username"),srv.getString("credential")); else is = new PeerConnection.IceServer(srv.getString("url")); servers.add(is); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e){ e.printStackTrace(); } Log.i("XIRSYS","Servers: " + servers.toString()); return servers; } } /* function get_xirsys_servers() { var servers; $.ajax({ type: 'POST', url: 'https://service.xirsys.com/ice', data: { room: 'default', application: 'default', domain: 'kevingleason.me', ident: 'gleasonk', secret: 'b9066b5e-1f75-11e5-866a-c400956a1e19', secure: 1, }, success: function(res) { console.log(res); res = JSON.parse(res); if (!res.e) servers = res.d.iceServers; }, async: false }); return servers; } */ ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/util/Constants.java ================================================ package me.kevingleason.androidrtc.util; /** * Created by GleasonK on 7/30/15. */ public class Constants { public static final String SHARED_PREFS = "me.kg.androidrtc.SHARED_PREFS"; public static final String USER_NAME = "me.kg.androidrtc.SHARED_PREFS.USER_NAME"; public static final String CALL_USER = "me.kg.androidrtc.SHARED_PREFS.CALL_USER"; public static final String STDBY_SUFFIX = "-stdby"; public static final String PUB_KEY = "pub-c-9d0d75a5-38db-404f-ac2a-884e18b041d8"; // Your Pub Key public static final String SUB_KEY = "sub-c-4e25fb64-37c7-11e5-a477-0619f8945a4f"; // Your Sub Key // public static final String PUB_KEY = "demo"; // Your Pub Key // public static final String SUB_KEY = "demo"; // Your Sub Key public static final String JSON_CALL_USER = "call_user"; public static final String JSON_CALL_TIME = "call_time"; public static final String JSON_OCCUPANCY = "occupancy"; public static final String JSON_STATUS = "status"; // JSON for user messages public static final String JSON_USER_MSG = "user_message"; public static final String JSON_MSG_UUID = "msg_uuid"; public static final String JSON_MSG = "msg_message"; public static final String JSON_TIME = "msg_timestamp"; public static final String STATUS_AVAILABLE = "Available"; public static final String STATUS_OFFLINE = "Offline"; public static final String STATUS_BUSY = "Busy"; } ================================================ FILE: app/src/main/java/me/kevingleason/androidrtc/util/LogRTCListener.java ================================================ package me.kevingleason.androidrtc.util; import android.util.Log; import org.webrtc.MediaStream; import me.kevingleason.pnwebrtc.PnPeer; import me.kevingleason.pnwebrtc.PnRTCListener; import me.kevingleason.pnwebrtc.PnRTCMessage; /** *

Created by GleasonK on 7/23/15.

*/ public class LogRTCListener extends PnRTCListener { @Override public void onCallReady(String callId) { Log.i("RTCListener", "OnCallReady - " + callId); } @Override public void onConnected(String userId) { Log.i("RTCListener", "OnConnected - " + userId); } @Override public void onPeerStatusChanged(PnPeer peer) { Log.i("RTCListener", "OnPeerStatusChanged - " + peer.toString()); } @Override public void onPeerConnectionClosed(PnPeer peer) { Log.i("RTCListener", "OnPeerConnectionClosed - " + peer.toString()); } @Override public void onLocalStream(MediaStream localStream) { Log.i("RTCListener", "OnLocalStream - " + localStream.toString()); } @Override public void onAddRemoteStream(MediaStream remoteStream, PnPeer peer) { Log.i("RTCListener", "OnAddRemoteStream - " + peer.toString()); } @Override public void onRemoveRemoteStream(MediaStream remoteStream, PnPeer peer) { Log.i("RTCListener", "OnRemoveRemoteStream - " + peer.toString()); } @Override public void onMessage(PnPeer peer, Object message) { Log.i("RTCListener", "OnMessage - " + message.toString()); } @Override public void onDebug(PnRTCMessage message) { Log.i("RTCListener", "OnDebug - " + message.getMessage()); } } ================================================ FILE: app/src/main/res/drawable/light_fade_down.xml ================================================ ================================================ FILE: app/src/main/res/drawable/light_fade_up.xml ================================================ ================================================ FILE: app/src/main/res/drawable/online_circle.xml ================================================ ================================================ FILE: app/src/main/res/drawable/round_button.xml ================================================ ================================================ FILE: app/src/main/res/drawable/round_button_send.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_incoming_call.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_login.xml ================================================