/* * Copyright (c) 2013 The MITRE Corporation, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this work except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Derived from AppRTCActivity from the libjingle / webrtc AppRTCDemo // example application distributed under the following license. /* * libjingle * Copyright 2013, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.mitre.svmp.activities; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Point; import android.os.Bundle; import android.util.Log; import android.view.*; import org.appspot.apprtc.VideoStreamsView; import org.json.JSONException; import org.json.JSONObject; import org.mitre.svmp.apprtc.*; import org.mitre.svmp.client.*; import org.mitre.svmp.protocol.SVMPProtocol; import org.mitre.svmp.protocol.SVMPProtocol.AppsRequest; import org.mitre.svmp.protocol.SVMPProtocol.Request; import org.mitre.svmp.protocol.SVMPProtocol.Response; import org.webrtc.*; import java.util.TimeZone; /** * @author Joe Portner * General purpose activity to display a video feed and allow the user to interact with a remote VM */ public class AppRTCVideoActivity extends AppRTCActivity { private static final String TAG = AppRTCVideoActivity.class.getName(); private MediaConstraints sdpMediaConstraints; private SDPObserver sdpObserver; private VideoStreamsView vsv; private PCObserver pcObserver; private TouchHandler touchHandler; private RotationHandler rotationHandler; private String pkgName; // what app we want to launch when we finish connecting private KeyHandler keyHandler; private ConfigHandler configHandler; @Override public void onCreate(Bundle savedInstanceState) { // Get info passed to Intent final Intent intent = getIntent(); pkgName = intent.getStringExtra("pkgName"); super.onCreate(savedInstanceState); } @Override protected void connectToRoom() { // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging. // Logging.enableTracing( // "/sdcard/trace.txt", // EnumSet.of(Logging.TraceLevel.TRACE_ALL), // Logging.Severity.LS_SENSITIVE); Point displaySize = new Point(); getWindowManager().getDefaultDisplay().getSize(displaySize); vsv = new VideoStreamsView(this, displaySize, performanceAdapter); vsv.setBackgroundColor(Color.DKGRAY); // start this VideoStreamsView with a color of dark gray setContentView(vsv); touchHandler = new TouchHandler(this, displaySize, performanceAdapter); rotationHandler = new RotationHandler(this); keyHandler = new KeyHandler(this); configHandler = new ConfigHandler(this); AppRTCHelper.abortUnless(PeerConnectionFactory.initializeAndroidGlobals(this), "Failed to initializeAndroidGlobals"); //Create observers. sdpObserver = new SDPObserver(this); pcObserver = new PCObserver(this); sdpMediaConstraints = new MediaConstraints(); sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair( "OfferToReceiveAudio", "true")); sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair( "OfferToReceiveVideo", "true")); super.connectToRoom(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // send the updated configuration to the VM (i.e. whether or not a hardware keyboard is plugged in) configHandler.handleConfiguration(newConfig); } public VideoStreamsView getVSV() { return vsv; } public PCObserver getPCObserver() { return pcObserver; } /* public MediaConstraints getSdpMediaConstraints() { return sdpMediaConstraints; } public boolean isInitiator() { return appRtcClient.isInitiator(); } */ // called from PCObserver public MediaConstraints getPCConstraints() { MediaConstraints value = null; if (appRtcClient != null) value = appRtcClient.getSignalingParams().pcConstraints; return value; } @Override protected void startProgressDialog() { vsv.setBackgroundColor(Color.DKGRAY); // if it isn't already set, make the background color dark gray super.startProgressDialog(); } @Override public void onPause() { vsv.onPause(); super.onPause(); } @Override public void onResume() { vsv.onResume(); super.onResume(); } // MessageHandler interface method // Called when the client connection is established @Override public void onOpen() { super.onOpen(); // set up ICE servers pcObserver.onIceServers(appRtcClient.getSignalingParams().iceServers); // send timezone information Request.Builder request = Request.newBuilder(); request.setType(Request.RequestType.TIMEZONE); request.setTimezoneId(TimeZone.getDefault().getID()); sendMessage(request.build()); touchHandler.sendScreenInfoMessage(); rotationHandler.initRotationUpdates(); // send the initial configuration to the VM Configuration config = getResources().getConfiguration(); configHandler.handleConfiguration(config); // tell the VM what app we want to start sendAppsMessage(); PeerConnection pc = pcObserver.getPC(); if (pc != null) pc.createOffer(sdpObserver, sdpMediaConstraints); } // sends "APPS" request to VM; if pkgName is not null, start that app, otherwise go to the Launcher private void sendAppsMessage() { AppsRequest.Builder aBuilder = AppsRequest.newBuilder(); aBuilder.setType(AppsRequest.AppsRequestType.LAUNCH); // if we've been given a package name, start that app if (pkgName != null) aBuilder.setPkgName(pkgName); Request.Builder rBuilder = Request.newBuilder(); rBuilder.setType(Request.RequestType.APPS); rBuilder.setApps(aBuilder); sendMessage(rBuilder.build()); } // MessageHandler interface method // Called when a message is sent from the server, and the SessionService doesn't consume it public boolean onMessage(Response data) { switch (data.getType()) { case APPS: if (data.hasApps() && data.getApps().getType() == SVMPProtocol.AppsResponse.AppsResponseType.EXIT) { // we have exited a remote app; exit back to our parent activity and act accordingly disconnectAndExit(); } break; case SCREENINFO: handleScreenInfo(data); break; case WEBRTC: try { JSONObject json = new JSONObject(data.getWebrtcMsg().getJson()); Log.d(TAG, "Received WebRTC message from peer:\n" + json.toString(4)); String type; // peerconnection_client doesn't put a "type" on candidates try { type = (String) json.get("type"); } catch (JSONException e) { json.put("type", "candidate"); type = (String) json.get("type"); } //Check out the type of WebRTC message. if (type.equals("candidate")) { IceCandidate candidate = new IceCandidate( (String) json.get("id"), json.getInt("label"), (String) json.get("candidate")); getPCObserver().addIceCandidate(candidate); } else if (type.equals("answer") || type.equals("offer")) { SessionDescription sdp = new SessionDescription( SessionDescription.Type.fromCanonicalForm(type), AppRTCHelper.preferISAC((String) json.get("sdp"))); getPCObserver().getPC().setRemoteDescription(sdpObserver, sdp); } else if (type.equals("bye")) { logAndToast(R.string.appRTC_toast_clientHandler_finish); disconnectAndExit(); } else { throw new RuntimeException("Unexpected message: " + data); } } catch (JSONException e) { throw new RuntimeException(e); } break; default: // any messages we don't understand, pass to our parent for processing super.onMessage(data); } return true; } @Override protected void onDisconnectAndExit() { if (rotationHandler != null) rotationHandler.cleanupRotationUpdates(); if (pcObserver != null) pcObserver.quit(); } ///////////////////////////////////////////////////////////////////// // Bridge input callbacks to the Touch Input Handler ///////////////////////////////////////////////////////////////////// private void handleScreenInfo(Response msg) { touchHandler.handleScreenInfoResponse(msg); } @Override public boolean onTouchEvent(MotionEvent event) { return touchHandler.onTouchEvent(event); } // intercept KeyEvent before it is dispatched to the window @Override public boolean dispatchKeyEvent(KeyEvent event) { return keyHandler.tryConsume(event) || super.dispatchKeyEvent(event); } }