video: add basic support

* implement device enumeration DRing API
* implement frame capture DRing API
* add basic video UI

Tuleap: #293
Change-Id: Ia9a364da70f76f1bb9d26b422facefaa9c25ce76
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 90255e9..5dadb98 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -52,6 +52,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
     <uses-feature
         android:name="android.hardware.wifi"
@@ -173,8 +175,9 @@
         <activity
             android:name=".client.CallActivity"
             android:label="@string/app_name"
-            android:screenOrientation="portrait"
-            android:theme="@style/AppThemeWithoutOverlayCompat"
+            android:screenOrientation="fullSensor"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:theme="@style/AppTheme.ActionBar.Transparent"
             android:windowSoftInputMode="adjustPan">
 
             <intent-filter>
diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
index 55d5d31..69f9c2a 100644
--- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
@@ -22,6 +22,10 @@
 
 package cx.ring.client;
 
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.res.Configuration;
+import android.os.Build;
 import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
 
@@ -49,6 +53,7 @@
 import android.os.SystemClock;
 import android.util.Pair;
 import android.view.KeyEvent;
+import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 
@@ -60,22 +65,49 @@
     public static final String ACTION_CALL = BuildConfig.APPLICATION_ID + ".action.call";
 
     private boolean init = false;
+    private View mainView;
+
     private LocalService service;
 
     private CallFragment mCurrentCallFragment;
     private Conference mDisplayedConference;
+    private String savedConferenceId = null;
 
     /* result code sent in case of call failure */
     public static int RESULT_FAILURE = -10;
     private CallProximityManager mProximityManager = null;
 
+    private int currentOrientation = Configuration.ORIENTATION_PORTRAIT;
+    private boolean dimmed = false;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "CallActivity onCreate");
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null)
+            savedConferenceId = savedInstanceState.getString("conference", null);
+        Log.i(TAG, "CallActivity onCreate " + savedConferenceId);
 
+        int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
         Window w = getWindow();
-        w.setFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        w.setFlags(flags, flags);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+        }
+
+        setContentView(R.layout.activity_call_layout);
+        mainView = findViewById(R.id.maincalllayout);
+        mainView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dimmed = !dimmed;
+                if (dimmed) {
+                    hideSystemUI();
+                } else {
+                    showSystemUI();
+                }
+            }
+        });
 
         Intent intent = new Intent(this, LocalService.class);
         bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
@@ -88,6 +120,68 @@
         window.setFormat(PixelFormat.RGBA_8888);
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.w(TAG, "onConfigurationChanged " + newConfig.screenWidthDp);
+
+        currentOrientation = newConfig.orientation;
+
+        // Checks the orientation of the screen
+        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            dimmed = true;
+            hideSystemUI();
+        } else if (currentOrientation == Configuration.ORIENTATION_PORTRAIT){
+            dimmed = false;
+            showSystemUI();
+        }
+
+        //hideSystemUI();
+        super.onConfigurationChanged(newConfig);
+    }
+
+    // This snippet hides the system bars.
+    private void hideSystemUI() {
+        // Set the IMMERSIVE flag.
+        // Set the content to appear under the system bars so that the content
+        // doesn't resize when the system bars hide and show.
+        if (mainView != null) {
+            mainView.setSystemUiVisibility(
+                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE
+                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
+                            | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
+                            | View.SYSTEM_UI_FLAG_IMMERSIVE);
+        }
+    }
+
+    // This snippet shows the system bars. It does this by removing all the flags
+// except for the ones that make the content appear under the system bars.
+    private void showSystemUI() {
+        if (mainView != null) {
+            mainView.setSystemUiVisibility(
+                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE
+                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                            | View.SYSTEM_UI_FLAG_IMMERSIVE);
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (hasFocus && currentOrientation == Configuration.ORIENTATION_LANDSCAPE)
+            hideSystemUI();
+        else
+            showSystemUI();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString("conference", mDisplayedConference.getId());
+    }
+
     private Handler mHandler = new Handler();
     private Runnable mUpdateTimeTask = new Runnable() {
         @Override
@@ -140,7 +234,11 @@
                 mProximityManager = new CallProximityManager(CallActivity.this, CallActivity.this);
                 mProximityManager.startTracking();
 
-                checkExternalCall();
+                if (savedConferenceId != null) {
+                    mDisplayedConference = service.getConference(savedConferenceId);
+                } else {
+                    checkExternalCall();
+                }
 
                 if (mDisplayedConference == null || mDisplayedConference.getParticipants().isEmpty()) {
                     CallActivity.this.finish();
@@ -151,12 +249,16 @@
                 init = true;
             }
 
-            setContentView(R.layout.activity_call_layout);
-            mCurrentCallFragment = (CallFragment) getFragmentManager().findFragmentById(R.id.ongoingcall_pane);
+            FragmentManager fragmentManager = getFragmentManager();
+            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+            mCurrentCallFragment = new CallFragment();
+            fragmentTransaction.add(R.id.maincalllayout, mCurrentCallFragment).commit();
+            hideSystemUI();
         }
 
         @Override
         public void onServiceDisconnected(ComponentName arg0) {
+
         }
     };
 
@@ -228,9 +330,7 @@
 
     @Override
     public void updateDisplayedConference(Conference c) {
-        if(mDisplayedConference.equals(c)){
-            mDisplayedConference = c;
-        }
+        mDisplayedConference = c;
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
index adcd84a..d9f715e 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
@@ -108,6 +108,7 @@
         Conversation conv = s.getConversation(conv_id);
         if (conv == null) {
             long contact_id = CallContact.contactIdFromId(conv_id);
+            Log.w(TAG, "no conversation found, contact_id " + contact_id);
             CallContact contact = null;
             if (contact_id >= 0)
                 contact = s.findContactById(contact_id);
@@ -119,13 +120,17 @@
                         contact = CallContact.buildUnknown(conv_uri);
                 } else {
                     contact = s.findContactByNumber(conv_uri);
-                    if (contact == null)
+                    if (contact == null) {
                         contact = CallContact.buildUnknown(conv_uri);
-                    number = contact.getPhones().get(0).getNumber();
+                        number = contact.getPhones().get(0).getNumber();
+                    } else {
+                        number = conv_uri;
+                    }
                 }
             }
             conv = s.startConversation(contact);
         }
+        Log.w(TAG, "returning " + conv.getContact().getDisplayName() + " " + number);
         return new Pair<>(conv, number);
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
index 691b2de..d17a98e 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
@@ -196,7 +196,7 @@
 
             HashMap<String, String> accountDetails = (HashMap<String, String>) mCallbacks.getRemoteService().getAccountTemplate(mAccountType);
             accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_TYPE, mAccountType);
-            accountDetails.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, "false");
+            accountDetails.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, "true");
             if (mAccountType.equals("RING")) {
                 accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS, "Ring");
                 accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME, "bootstrap.ring.cx");
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java
index 2c8d31e..854ee6a 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java
@@ -408,7 +408,7 @@
         public void setDataset(ArrayList<Codec> codecs) {
             items = new ArrayList<>(codecs.size());
             for (Codec c : codecs)
-                if (c.getType() == Codec.Type.AUDIO)
+                //if (c.getType() == Codec.Type.AUDIO)
                     items.add(c);
         }
 
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
index 1f0dbb5..5102b3c 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
@@ -22,17 +22,22 @@
 package cx.ring.fragments;
 
 import android.app.Activity;
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.support.v4.app.NotificationManagerCompat;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.RemoteException;
 import android.support.v7.app.ActionBar;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.*;
 import android.view.View.OnClickListener;
@@ -43,15 +48,20 @@
 import cx.ring.client.HomeActivity;
 import cx.ring.interfaces.CallInterface;
 
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
 import java.util.Locale;
 
 import cx.ring.model.CallContact;
 import cx.ring.model.Conference;
 import cx.ring.model.SecureSipCall;
 import cx.ring.model.SipCall;
+import cx.ring.service.CallManagerCallBack;
+import cx.ring.service.DRingService;
+import cx.ring.service.IDRingService;
 import cx.ring.service.LocalService;
 
-public class CallFragment extends CallableWrapperFragment implements CallInterface {
+public class CallFragment extends Fragment implements CallInterface {
 
     static private final String TAG = CallFragment.class.getSimpleName();
 
@@ -59,6 +69,7 @@
 
     // Screen wake lock for incoming call
     private WakeLock mScreenWakeLock;
+    private View contactBubbleLayout;
     private ImageView contactBubbleView;
     private TextView contactBubbleTxt;
     private TextView contactBubbleNumTxt;
@@ -68,6 +79,8 @@
     private View securityIndicator;
     private MenuItem speakerPhoneBtn = null;
     private MenuItem addContactBtn = null;
+    private SurfaceView video = null;
+    private SurfaceView videoPreview = null;
 
     ViewSwitcher mSecuritySwitch;
     private TextView mCallStatusTxt;
@@ -76,7 +89,49 @@
 
     private AudioManager audioManager;
 
-    TransferDFragment editName;
+    private boolean haveVideo = false;
+    private int videoWidth = -1, videoHeight = -1;
+    private int previewWidth = -1, previewHeight = -1;
+    private boolean lastVideoSource = true;
+
+    private Conference mCachedConference = null;
+    private ViewGroup rootView = null;
+    private boolean ongoingCall = false;
+
+    @Override
+    public void onAttach(Activity activity) {
+        Log.i(TAG, "onAttach");
+        super.onAttach(activity);
+
+        if (!(activity instanceof Callbacks)) {
+            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        }
+
+        mCallbacks = (Callbacks) activity;
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(CallManagerCallBack.RECORD_STATE_CHANGED);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_OFF);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_ON);
+        intentFilter.addAction(CallManagerCallBack.DISPLAY_SAS);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED);
+        intentFilter.addAction(CallManagerCallBack.ZRTP_NOT_SUPPORTED);
+        intentFilter.addAction(CallManagerCallBack.RTCP_REPORT_RECEIVED);
+
+        intentFilter.addAction(DRingService.VIDEO_EVENT);
+
+        intentFilter.addAction(LocalService.ACTION_CONF_UPDATE);
+
+        getActivity().registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    public void onDetach() {
+        Log.i(TAG, "onDetach");
+        getActivity().unregisterReceiver(mReceiver);
+        mCallbacks = sDummyCallbacks;
+        super.onDetach();
+    }
 
     @Override
     public void onCreate(Bundle savedBundle) {
@@ -87,7 +142,7 @@
 
         setHasOptionsMenu(true);
         PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
-        mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE,
+        mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE,
                 "cx.ring.onIncomingCall");
         mScreenWakeLock.setReferenceCounted(false);
 
@@ -95,6 +150,17 @@
         if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
             mScreenWakeLock.acquire();
         }
+
+        setRetainInstance(true);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "onDestroy");
+        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
+            mScreenWakeLock.release();
+        }
+        super.onDestroy();
     }
 
     /**
@@ -127,20 +193,71 @@
     }
     private static final Callbacks sDummyCallbacks = new DummyCallbacks();
 
-    @Override
-    public void onAttach(Activity activity) {
-        Log.i(TAG, "onAttach");
-        super.onAttach(activity);
+    public class CallReceiver extends BroadcastReceiver {
+        private final String TAG = CallReceiver.class.getSimpleName();
 
-        if (!(activity instanceof Callbacks)) {
-            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.w(TAG, "onReceive " + action);
+            if (action.contentEquals(LocalService.ACTION_CONF_UPDATE)) {
+                confUpdate();
+            } else if (action.contentEquals(DRingService.VIDEO_EVENT)) {
+                if (video == null)
+                    return;
+                Conference conf = getConference();
+                if (intent.hasExtra("start")) {
+                    Log.w(TAG, "VIDEO_EVENT start");
+                    video.setVisibility(View.VISIBLE);
+                    videoPreview.setVisibility(View.VISIBLE);
+                } else if (intent.hasExtra("camera")) {
+                    Log.w(TAG, "VIDEO_EVENT camera " + intent.getBooleanExtra("camera", false));
+                    previewWidth = intent.getIntExtra("width", 0);
+                    previewHeight = intent.getIntExtra("height", 0);
+                }
+                else if (conf != null && conf.getId().equals(intent.getStringExtra("call"))) {
+                    if (video != null) {
+                        haveVideo = intent.getBooleanExtra("started", false);
+                        if (haveVideo) {
+                            Log.w(TAG, "VIDEO_EVENT started");
+                            video.setVisibility(View.VISIBLE);
+                            videoPreview.setVisibility(View.VISIBLE);
+                            videoWidth = intent.getIntExtra("width", 0);
+                            videoHeight = intent.getIntExtra("height", 0);
+                        } else {
+                            Log.w(TAG, "VIDEO_EVENT stopped");
+                            video.setVisibility(View.GONE);
+                            videoPreview.setVisibility(View.GONE);
+                        }
+                    }
+                    refreshState();
+                }
+                resetVideoSizes();
+            }
+            else if (action.contentEquals(CallManagerCallBack.RECORD_STATE_CHANGED)) {
+                recordingChanged((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"), intent.getStringExtra("file"));
+            } else if (action.contentEquals(CallManagerCallBack.ZRTP_OFF)) {
+                secureZrtpOff((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
+            } else if (action.contentEquals(CallManagerCallBack.ZRTP_ON)) {
+                secureZrtpOn((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
+            } else if (action.contentEquals(CallManagerCallBack.DISPLAY_SAS)) {
+                displaySAS((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
+            } else if (action.contentEquals(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED)) {
+                zrtpNegotiationFailed((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
+            } else if (action.contentEquals(CallManagerCallBack.ZRTP_NOT_SUPPORTED)) {
+                zrtpNotSupported((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
+            } else if (action.contentEquals(CallManagerCallBack.RTCP_REPORT_RECEIVED)) {
+                rtcpReportReceived(null, null); // FIXME
+            } else {
+                Log.e(TAG, "Unknown action: " + intent.getAction());
+            }
         }
-
-        mCallbacks = (Callbacks) activity;
     }
+    private final CallReceiver mReceiver = new CallReceiver();
 
     public void refreshState() {
         Conference conf = getConference();
+
         if (conf == null)  {
             contactBubbleView.setImageBitmap(null);
             contactBubbleTxt.setText("");
@@ -190,6 +307,9 @@
     public boolean onOptionsItemSelected(MenuItem item) {
         super.onOptionsItemSelected(item);
         switch (item.getItemId()) {
+            case android.R.id.home:
+                mCallbacks.terminateCall();
+                break;
             case R.id.menuitem_chat:
                 Intent intent = new Intent()
                         .setClass(getActivity(), ConversationActivity.class)
@@ -205,19 +325,51 @@
                 audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
                 getActivity().invalidateOptionsMenu();
                 break;
+            case R.id.menuitem_camera_flip:
+                lastVideoSource = !lastVideoSource;
+                try {
+                    mCallbacks.getRemoteService().switchInput(getConference().getId(), lastVideoSource);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                item.setIcon(lastVideoSource ? R.drawable.ic_camera_front_white_24dp : R.drawable.ic_camera_rear_white_24dp);
+                break;
         }
         return true;
     }
 
     @Override
-    public void onDetach() {
-        super.onDetach();
-        mCallbacks = sDummyCallbacks;
+    public void onStop() {
+        Log.w(TAG, "onStop()");
+
+        Conference c = getConference();
+        c.resumeVideo = haveVideo;
+
+        DRingService.videoSurfaces.remove(c.getId());
+        DRingService.mCameraPreviewSurface.clear();
+        try {
+            IDRingService service = mCallbacks.getRemoteService();
+            if (service != null) {
+                service.videoSurfaceRemoved(c.getId());
+                service.videoPreviewSurfaceRemoved();
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+
+        super.onStop();
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
+    public void onStart() {
+        super.onStart();
+        Conference c = getConference();
+        if (c != null && c.resumeVideo) {
+            haveVideo = true;
+            video.setVisibility(View.VISIBLE);
+            videoPreview.setVisibility(View.VISIBLE);
+            c.resumeVideo = false;
+        }
     }
 
     @Override
@@ -225,14 +377,14 @@
         Log.w(TAG, "onResume()");
         super.onResume();
         //initializeWiFiListener();
-        refreshState();
-
         Conference c = getConference();
         if (c != null) {
             c.mVisible = true;
             NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity());
             notificationManager.cancel(c.notificationId);
         }
+
+        refreshState();
     }
 
     @Override
@@ -240,9 +392,7 @@
         Log.w(TAG, "onPause()");
         super.onPause();
         //getActivity().unregisterReceiver(wifiReceiver);
-        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
-            mScreenWakeLock.release();
-        }
+
         Conference c = getConference();
         if (c != null) {
             c.mVisible = false;
@@ -266,8 +416,10 @@
 
         String newState = c.getState();
         if (c.isOnGoing()) {
+            ongoingCall = true;
             initNormalStateDisplay();
         } else if (c.isRinging()) {
+            ongoingCall = false;
             mCallStatusTxt.setText(newState);
 
             if (c.isIncoming()) {
@@ -283,6 +435,11 @@
     }
 
     @Override
+    public void recordingChanged(Conference c, String callID, String filename) {
+
+    }
+
+    @Override
     public void secureZrtpOn(Conference updated, String id) {
         Log.i(TAG, "secureZrtpOn");
         mCallbacks.updateDisplayedConference(updated);
@@ -316,6 +473,11 @@
     }
 
     @Override
+    public void rtcpReportReceived(Conference c, HashMap<String, Integer> stats) {
+
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         SipCall transfer;
@@ -345,17 +507,63 @@
                     break;
                 case Activity.RESULT_CANCELED:
                 default:
-                    initNormalStateDisplay();
+                    confUpdate();
                     break;
             }
         }
     }
 
+    void resetVideoSizes() {
+        double video_ratio = videoWidth / (double) videoHeight;
+        double screen_ratio = rootView.getWidth() / (double) rootView.getHeight();
+
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) video.getLayoutParams();
+        int oldW = params.width;
+        int oldH = params.height;
+        if (video_ratio >= screen_ratio) {
+            params.width = RelativeLayout.LayoutParams.MATCH_PARENT;
+            params.height = (int)(videoHeight * (double) rootView.getWidth() / (double) videoWidth);
+        } else {
+            params.height = RelativeLayout.LayoutParams.MATCH_PARENT;
+            params.width = (int)(videoWidth * (double) rootView.getHeight() / (double) videoHeight);
+        }
+
+        if (oldW != params.width || oldH != params.height) {
+            Log.w(TAG, "onLayoutChange " + params.width + " x " + params.height);
+            video.setLayoutParams(params);
+        }
+
+        DisplayMetrics metrics = getResources().getDisplayMetrics();
+        FrameLayout.LayoutParams params_preview = (FrameLayout.LayoutParams) videoPreview.getLayoutParams();
+        oldW = params_preview.width;
+        oldH = params_preview.height;
+        double preview_max_dim = Math.max(previewWidth, previewHeight);
+        double preview_ratio = metrics.density * 160. / preview_max_dim;
+        params_preview.width = (int) (previewWidth * preview_ratio);
+        params_preview.height = (int) (previewHeight * preview_ratio);
+        if (oldW != params_preview.width || oldH != params_preview.height) {
+            Log.w(TAG, "onLayoutChange " + params_preview.width + " x " + params_preview.height);
+            videoPreview.setLayoutParams(params_preview);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (videoPreview.getVisibility() == View.VISIBLE) {
+            try {
+                mCallbacks.getRemoteService().videoPreviewSurfaceAdded();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         Log.i(TAG, "onCreateView");
-        final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_call, container, false);
+        rootView = (ViewGroup) inflater.inflate(R.layout.frag_call, container, false);
 
+        contactBubbleLayout = rootView.findViewById(R.id.contact_bubble_layout);
         contactBubbleView = (ImageView) rootView.findViewById(R.id.contact_bubble);
         contactBubbleTxt = (TextView) rootView.findViewById(R.id.contact_bubble_txt);
         contactBubbleNumTxt = (TextView) rootView.findViewById(R.id.contact_bubble_num_txt);
@@ -365,14 +573,106 @@
         mCallStatusTxt = (TextView) rootView.findViewById(R.id.call_status_txt);
         mSecuritySwitch = (ViewSwitcher) rootView.findViewById(R.id.security_switcher);
         securityIndicator = rootView.findViewById(R.id.security_indicator);
+
+        video = (SurfaceView)rootView.findViewById(R.id.video_preview_surface);
+        video.getHolder().setFormat(PixelFormat.RGBA_8888);
+        video.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                Log.i(TAG, "video surfaceCreated");
+                contactBubbleLayout.setVisibility(View.GONE);
+                Conference c = getConference();
+                DRingService.videoSurfaces.put(c.getId(), new WeakReference<>(holder));
+                try {
+                    mCallbacks.getRemoteService().videoSurfaceAdded(c.getId());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                Log.i(TAG, "video surfaceChanged " + format + ", " + width + " x " + height);
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                Log.i(TAG, "video surfaceDestroyed");
+                Conference c = getConference();
+                DRingService.videoSurfaces.remove(c.getId());
+                try {
+                    IDRingService service = mCallbacks.getRemoteService();
+                    if (service != null)
+                        service.videoSurfaceRemoved(c.getId());
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+
+        rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View parent, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                resetVideoSizes();
+            }
+        });
+        rootView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
+            @Override
+            public void onSystemUiVisibilityChange(int visibility) {
+                boolean ui = (visibility & (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN)) == 0;
+                if (ongoingCall) {
+                    hangupButton.setVisibility(ui ? View.VISIBLE : View.GONE);
+                }
+            }
+        });
+
+        videoPreview = (SurfaceView)rootView.findViewById(R.id.camera_preview_surface);
+        videoPreview.getHolder().setFormat(PixelFormat.RGBA_8888);
+        videoPreview.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                DRingService.mCameraPreviewSurface = new WeakReference<>(holder);
+                try {
+                    mCallbacks.getRemoteService().videoPreviewSurfaceAdded();
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                Log.i(TAG, "videoPreview surfaceChanged " + format + ", " + width + " x " + height);
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                if (videoPreview != null && DRingService.mCameraPreviewSurface.get() == holder) {
+                    DRingService.mCameraPreviewSurface.clear();
+                }
+                try {
+                    IDRingService service = mCallbacks.getRemoteService();
+                    if (service != null)
+                        service.videoPreviewSurfaceRemoved();
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+        videoPreview.setZOrderMediaOverlay(true);
+
         return rootView;
     }
 
     public Conference getConference() {
-        return mCallbacks.getDisplayedConference();
+        Conference c = mCallbacks.getDisplayedConference();
+        if (c != null) {
+            if (mCachedConference != c)
+                mCachedConference = c;
+            return c;
+        }
+        return mCachedConference;
     }
 
-
     private void initContactDisplay(final SipCall call) {
         CallContact contact = call.getContact();
         final String name = contact.getDisplayName();
@@ -384,7 +684,9 @@
             contactBubbleNumTxt.setText(call.getNumber());
         }
         new ContactPictureTask(getActivity(), contactBubbleView, contact).run();
-        mCallbacks.getSupportActionBar().setTitle(name);
+        ActionBar ab = mCallbacks.getSupportActionBar();
+        ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
+        ab.setTitle(name);
     }
 
     private void initNormalStateDisplay() {
@@ -410,6 +712,8 @@
             }
         });
 
+        contactBubbleLayout.setVisibility(haveVideo ? View.GONE : View.VISIBLE);
+
         updateSecurityDisplay();
     }
 
@@ -466,26 +770,24 @@
         mSecuritySwitch.setVisibility(View.VISIBLE);
     }
 
-    protected Bitmap getContactPhoto(CallContact contact, int size) {
+    /*protected Bitmap getContactPhoto(CallContact contact, int size) {
         if (contact.getPhotoId() > 0) {
             return ContactPictureTask.loadContactPhoto(getActivity().getContentResolver(), contact.getId());
         } else {
             return ContactPictureTask.decodeSampledBitmapFromResource(getResources(), R.drawable.ic_contact_picture, size, size);
         }
-    }
+    }*/
 
     private void initIncomingCallDisplay() {
         Log.i(TAG, "Start incoming display");
-        if (mCallbacks.getService().getAccount(getConference().getParticipants().get(0).getAccount()).isAutoanswerEnabled()) {
+        final SipCall call = getConference().getParticipants().get(0);
+        if (mCallbacks.getService().getAccount(call.getAccount()).isAutoanswerEnabled()) {
             try {
-                mCallbacks.getRemoteService().accept(getConference().getParticipants().get(0).getCallId());
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            } catch (NullPointerException e) {
+                mCallbacks.getRemoteService().accept(call.getCallId());
+            } catch (Exception e) {
                 e.printStackTrace();
             }
         } else {
-            final SipCall call = getConference().getParticipants().get(0);
             initContactDisplay(call);
             acceptButton.setVisibility(View.VISIBLE);
             acceptButton.setOnClickListener(new OnClickListener() {
@@ -583,9 +885,7 @@
                     mCallbacks.getRemoteService().playDtmf(toSend);
                     break;
             }
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        } catch (NullPointerException e) {
+        } catch (Exception e) {
             e.printStackTrace();
         }
     }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java
index 0bc6b8a..ba63dd0 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java
@@ -148,9 +148,10 @@
     }
 
     public void updateLists() {
-        if (mCallbacks.getService() != null && mConferenceAdapter != null) {
-            mConferenceAdapter.updateDataset(mCallbacks.getService().getConversations());
-            if (mCallbacks.getService().isConnected()) {
+        LocalService service = mCallbacks.getService();
+        if (service != null && mConferenceAdapter != null) {
+            mConferenceAdapter.updateDataset(service.getConversations());
+            if (service.isConnected()) {
                 error_msg_pane.setVisibility(View.GONE);
             } else {
                 error_msg_pane.setVisibility(View.VISIBLE);
@@ -354,7 +355,7 @@
         mListAdapter = new ContactsAdapter(getActivity(), (HomeActivity)getActivity(), service.get40dpContactCache(), service.getThreadPool());
         mGridAdapter = new StarredContactsAdapter(getActivity());
 
-        mConferenceAdapter.updateDataset(mCallbacks.getService().getConversations());
+        mConferenceAdapter.updateDataset(service.getConversations());
         list.setAdapter(mConferenceAdapter);
 
         return inflatedView;
@@ -393,7 +394,10 @@
         Uri baseUri = null;
         if (args != null)
             baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(args.getString("filter")));
-        ContactsLoader l = new ContactsLoader(getActivity(), baseUri, mCallbacks.getService().getContactCache());
+        LocalService service = mCallbacks.getService();
+        if (service == null)
+            return null;
+        ContactsLoader l = new ContactsLoader(getActivity(), baseUri, service.getContactCache());
         l.forceLoad();
         return l;
     }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java
deleted file mode 100644
index 8d21157..0000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU 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 General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package cx.ring.fragments;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.app.Fragment;
-import android.util.Log;
-import cx.ring.interfaces.CallInterface;
-import cx.ring.model.Conference;
-import cx.ring.service.CallManagerCallBack;
-import cx.ring.service.LocalService;
-
-import java.util.HashMap;
-
-public abstract class CallableWrapperFragment extends Fragment implements CallInterface
-{
-    private final CallReceiver mReceiver = new CallReceiver();
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CallManagerCallBack.RECORD_STATE_CHANGED);
-        intentFilter.addAction(CallManagerCallBack.ZRTP_OFF);
-        intentFilter.addAction(CallManagerCallBack.ZRTP_ON);
-        intentFilter.addAction(CallManagerCallBack.DISPLAY_SAS);
-        intentFilter.addAction(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED);
-        intentFilter.addAction(CallManagerCallBack.ZRTP_NOT_SUPPORTED);
-        intentFilter.addAction(CallManagerCallBack.RTCP_REPORT_RECEIVED);
-
-        intentFilter.addAction(LocalService.ACTION_CONF_UPDATE);
-
-        getActivity().registerReceiver(mReceiver, intentFilter);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        getActivity().unregisterReceiver(mReceiver);
-    }
-
-    @Override
-    public void confUpdate() {
-    }
-
-    @Override
-    public void recordingChanged(Conference c, String callID, String filename) {
-    }
-
-    @Override
-    public void secureZrtpOn(Conference c, String id) {
-    }
-
-    @Override
-    public void secureZrtpOff(Conference c, String id) {
-    }
-
-    @Override
-    public void displaySAS(Conference c, String securedCallID) {
-    }
-
-    @Override
-    public void zrtpNegotiationFailed(Conference c, String securedCallID) {
-    }
-
-    @Override
-    public void zrtpNotSupported(Conference c, String securedCallID) {
-    }
-
-    @Override
-    public void rtcpReportReceived(Conference c, HashMap<String, Integer> stats) {
-    }
-
-
-    public class CallReceiver extends BroadcastReceiver {
-        private final String TAG = CallReceiver.class.getSimpleName();
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if(intent.getAction().contentEquals(LocalService.ACTION_CONF_UPDATE)) {
-                confUpdate();
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.RECORD_STATE_CHANGED)) {
-                recordingChanged((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"), intent.getStringExtra("file"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_OFF)) {
-                secureZrtpOff((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_ON)) {
-                secureZrtpOn((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.DISPLAY_SAS)) {
-                displaySAS((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_NEGOTIATION_FAILED)) {
-                zrtpNegotiationFailed((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.ZRTP_NOT_SUPPORTED)) {
-                zrtpNotSupported((Conference) intent.getParcelableExtra("conference"), intent.getStringExtra("call"));
-            } else if (intent.getAction().contentEquals(CallManagerCallBack.RTCP_REPORT_RECEIVED)) {
-                rtcpReportReceived(null, null); // FIXME
-            } else {
-                Log.e(TAG, "Unknown action: " + intent.getAction());
-            }
-
-        }
-
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/model/Conference.java b/ring-android/app/src/main/java/cx/ring/model/Conference.java
index c851511..32a8d76 100644
--- a/ring-android/app/src/main/java/cx/ring/model/Conference.java
+++ b/ring-android/app/src/main/java/cx/ring/model/Conference.java
@@ -50,6 +50,7 @@
     public int notificationId;
     // true if this conference is currently presented to the user.
     public boolean mVisible = false;
+    public boolean resumeVideo = false;
 
     private final static Random rand = new Random();
 
diff --git a/ring-android/app/src/main/java/cx/ring/model/Conversation.java b/ring-android/app/src/main/java/cx/ring/model/Conversation.java
index fbb426a..12b738c 100644
--- a/ring-android/app/src/main/java/cx/ring/model/Conversation.java
+++ b/ring-android/app/src/main/java/cx/ring/model/Conversation.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.database.ContentObservable;
 import android.support.v4.app.NotificationCompat;
+import android.util.Log;
 import android.util.Pair;
 
 import java.util.ArrayList;
@@ -203,6 +204,7 @@
                 last = e.getKey();
             }
         }
+        Log.i(TAG, "getLastAccountUsed " + last);
         return last;
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/model/SipCall.java b/ring-android/app/src/main/java/cx/ring/model/SipCall.java
index 9a68bae..1308aa3 100644
--- a/ring-android/app/src/main/java/cx/ring/model/SipCall.java
+++ b/ring-android/app/src/main/java/cx/ring/model/SipCall.java
@@ -47,6 +47,8 @@
     private int mCallType;
     private int mCallState = State.NONE;
 
+    private String videoSource = null;
+
     public SipCall(String id, String account, SipUri number, int direction) {
         mCallID = id;
         mAccount = account;
@@ -83,8 +85,7 @@
                 call_details.get("PEER_NUMBER"),
                 Integer.parseInt(call_details.get("CALL_TYPE")));
         mCallState = stateFromString(call_details.get("CALL_STATE"));
-        isPeerHolding = call_details.get("PEER_HOLDING").contentEquals("true");
-        isAudioMuted = call_details.get("AUDIO_MUTED").contentEquals("true");
+        setDetails(call_details);
     }
 
     public String getRecordPath() {
@@ -100,15 +101,20 @@
         return mCallState;
     }
 
-    public void setDetails(HashMap<String, String> details) {
+    public void setDetails(Map<String, String> details) {
         isPeerHolding = "true".equals(details.get("PEER_HOLDING"));
         isAudioMuted = "true".equals(details.get("AUDIO_MUTED"));
+        videoSource = details.get("VIDEO_SOURCE");
     }
 
     public long getDuration() {
         return isMissed() ? 0 : timestampEnd - timestampStart;
     }
 
+    public String getVideoSource() {
+        return videoSource;
+    }
+
     public interface Direction {
         int INCOMING = 0;
         int OUTGOING = 1;
diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
index 75708b6..04910f7 100644
--- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
+++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java
@@ -98,7 +98,7 @@
         for (AccountDetail.PreferenceEntry p : privateArray) {
             map.put(p.mKey, p.mValue);
         }
-        map.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, "false");
+        map.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, "true");
 
         return map;
     }
diff --git a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
index a3ec767..63b78a5 100644
--- a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
+++ b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java
@@ -31,13 +31,14 @@
 
 public class ConfigurationManagerCallback extends ConfigurationCallback {
 
-    private DRingService mService;
-    private static final String TAG = "ConfigurationManagerCb";
+    private static final String TAG = ConfigurationManagerCallback.class.getSimpleName();
 
     static public final String ACCOUNTS_CHANGED = "accounts-changed";
     static public final String ACCOUNT_STATE_CHANGED = "account-State-changed";
     static public final String INCOMING_TEXT = "incoming--txt-msg";
 
+    private final DRingService mService;
+
     public ConfigurationManagerCallback(DRingService context) {
         super();
         mService = context;
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
index c9c2108..003d53f 100644
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
@@ -28,10 +28,14 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.hardware.Camera;
 import android.media.AudioManager;
 import android.os.Handler;
 
+import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -42,6 +46,11 @@
 import android.util.Log;
 
 import cx.ring.BuildConfig;
+
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+
 import cx.ring.model.Codec;
 import cx.ring.utils.SwigNativeConverter;
 
@@ -53,6 +62,7 @@
     private static HandlerThread executorThread;
 
     static public final String DRING_CONNECTION_CHANGED = BuildConfig.APPLICATION_ID + ".event.DRING_CONNECTION_CHANGE";
+    static public final String VIDEO_EVENT = BuildConfig.APPLICATION_ID + ".event.VIDEO_EVENT";
 
     private Handler handler = new Handler();
     private static int POLLING_TIMEOUT = 50;
@@ -72,6 +82,21 @@
 
     private ConfigurationManagerCallback configurationCallback;
     private CallManagerCallBack callManagerCallBack;
+    private VideoManagerCallback videoManagerCallback;
+
+    class Shm {
+        String id;
+        String path;
+        int w, h;
+        boolean mixer;
+        long window = 0;
+    };
+
+    static public WeakReference<SurfaceHolder> mCameraPreviewSurface = new WeakReference<>(null);
+    static public Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<String, WeakReference<SurfaceHolder>>());
+    private final Map<String, Shm> videoInputs = new HashMap<>();
+    private Camera previewCamera = null;
+    private VideoParams previewParams = null;
 
     static private final IntentFilter RINGER_FILTER = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
     private final BroadcastReceiver ringerModeListener = new BroadcastReceiver() {
@@ -147,6 +172,202 @@
         return mExecutor;
     }
 
+    public void decodingStarted(String id, String shm_path, int w, int h, boolean is_mixer) {
+        Log.i(TAG, "DRingService.decodingStarted() " + id + " " + w + "x" + h);
+        Shm shm = new Shm();
+        shm.id = id;
+        shm.path = shm_path;
+        shm.w = w;
+        shm.h = h;
+        shm.mixer = is_mixer;
+        videoInputs.put(id, shm);
+        WeakReference<SurfaceHolder> w_holder = videoSurfaces.get(id);
+        if (w_holder != null) {
+            SurfaceHolder holder = w_holder.get();
+            if (holder != null)
+                startVideo(shm, holder);
+        }
+    }
+
+    public void decodingStopped(String id) {
+        Log.i(TAG, "DRingService.decodingStopped() " + id);
+        Shm shm = videoInputs.remove(id);
+        if (shm != null)
+            stopVideo(shm);
+    }
+
+    private void startVideo(Shm input, SurfaceHolder holder) {
+        Log.i(TAG, "DRingService.startVideo() " + input.id);
+        input.window = RingserviceJNI.acquireNativeWindow(holder.getSurface());
+        if (input.window == 0) {
+            Log.i(TAG, "DRingService.startVideo() no window ! " + input.id);
+            Intent intent = new Intent(VIDEO_EVENT);
+            intent.putExtra("start", true);
+            sendBroadcast(intent);
+            return;
+        }
+        RingserviceJNI.setNativeWindowGeometry(input.window, input.w, input.h);
+        RingserviceJNI.registerVideoCallback(input.id, input.window);
+
+        Intent intent = new Intent(VIDEO_EVENT);
+        intent.putExtra("started", true);
+        intent.putExtra("call", input.id);
+        intent.putExtra("width", input.w);
+        intent.putExtra("height", input.h);
+        sendBroadcast(intent);
+    }
+
+    private void stopVideo(Shm input) {
+        Log.i(TAG, "DRingService.stopVideo() " + input.id);
+        if (input.window != 0) {
+            RingserviceJNI.unregisterVideoCallback(input.id, input.window);
+            RingserviceJNI.releaseNativeWindow(input.window);
+            input.window = 0;
+        }
+
+        Intent intent = new Intent(VIDEO_EVENT);
+        intent.putExtra("started", false);
+        intent.putExtra("call", input.id);
+        sendBroadcast(intent);
+    }
+
+    static public class VideoParams {
+        public VideoParams(int id, int format, int width, int height, int rate) {
+            this.id = id;
+            this.format = format;
+            this.width = width;
+            this.height = height;
+            this.rate = rate;
+        }
+
+        public int id;
+        public int format;
+        public int width;
+        public int height;
+        public int rate;
+        public int rotation;
+    }
+
+    public int setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) {
+        android.hardware.Camera.CameraInfo info =
+                new android.hardware.Camera.CameraInfo();
+        android.hardware.Camera.getCameraInfo(cameraId, info);
+        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        int rotation = windowManager.getDefaultDisplay().getRotation();
+        int degrees = 0;
+        switch (rotation) {
+            case Surface.ROTATION_0: degrees = 0; break;
+            case Surface.ROTATION_90: degrees = 90; break;
+            case Surface.ROTATION_180: degrees = 180; break;
+            case Surface.ROTATION_270: degrees = 270; break;
+        }
+
+        int result;
+        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (info.orientation + degrees) % 360;
+            result = (360 - result) % 360;  // compensate the mirror
+        } else {  // back-facing
+            result = (info.orientation - degrees + 360) % 360;
+        }
+        camera.setDisplayOrientation(result);
+        return result;
+    }
+
+    public void startCapture(VideoParams p) {
+        stopCapture();
+
+        SurfaceHolder surface = mCameraPreviewSurface.get();
+        if (surface == null) {
+            Log.w(TAG, "Can't start capture: no surface registered.");
+            previewParams = p;
+            Intent intent = new Intent(VIDEO_EVENT);
+            intent.putExtra("start", true);
+            sendBroadcast(intent);
+            return;
+        }
+
+        if (p == null) {
+            Log.w(TAG, "startCapture: no video parameters ");
+            return;
+        }
+        Log.w(TAG, "startCapture " + p.id);
+
+        final Camera preview;
+        try {
+            preview = Camera.open(p.id);
+            p.rotation = setCameraDisplayOrientation(p.id, preview);
+        } catch (Exception e) {
+            Log.e(TAG, e.getMessage());
+            return;
+        }
+
+        try {
+            surface.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+            preview.setPreviewDisplay(surface);
+        } catch (IOException e) {
+            Log.e(TAG, e.getMessage());
+            return;
+        }
+
+        Camera.Parameters parameters = preview.getParameters();
+        parameters.setPreviewFormat(p.format);
+        parameters.setPreviewSize(p.width, p.height);
+        for (int[] fps : parameters.getSupportedPreviewFpsRange()) {
+            if (p.rate >= fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
+                    p.rate <= fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
+                parameters.setPreviewFpsRange(fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                        fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+            }
+        }
+
+        try {
+            preview.setParameters(parameters);
+        } catch (RuntimeException e) {
+            Log.e(TAG, e.getMessage());
+        }
+
+        preview.setPreviewCallback(videoManagerCallback);
+        preview.setErrorCallback(new Camera.ErrorCallback() {
+            @Override
+            public void onError(int error, Camera cam) {
+                Log.w(TAG, "Camera onError " + error);
+                if (preview == cam)
+                    stopCapture();
+            }
+        });
+        preview.startPreview();
+
+        previewCamera = preview;
+        previewParams = p;
+
+        Intent intent = new Intent(VIDEO_EVENT);
+        intent.putExtra("camera", p.id == 1);
+        intent.putExtra("started", true);
+        boolean invert = p.rotation == 90 || p.rotation == 270;
+        intent.putExtra("width", invert ? p.height : p.width);
+        intent.putExtra("height", invert ? p.width : p.height);
+        sendBroadcast(intent);
+    }
+
+    public void stopCapture() {
+        Log.w(TAG, "stopCapture " + previewCamera);
+        if (previewCamera != null) {
+            final Camera preview = previewCamera;
+            final VideoParams p = previewParams;
+            previewCamera = null;
+            preview.setPreviewCallback(null);
+            preview.stopPreview();
+            preview.release();
+
+            Intent intent = new Intent(VIDEO_EVENT);
+            intent.putExtra("camera", p.id == 1);
+            intent.putExtra("started", false);
+            intent.putExtra("width", p.width);
+            intent.putExtra("height", p.height);
+            sendBroadcast(intent);
+        }
+    }
+
     // Executes immediate tasks in a single executorThread.
     public static class SipServiceExecutor extends Handler {
 
@@ -263,6 +484,9 @@
         handler.removeCallbacks(pollEvents);
         if (isPjSipStackStarted) {
             Ringservice.fini();
+            configurationCallback = null;
+            callManagerCallBack = null;
+            videoManagerCallback = null;
             isPjSipStackStarted = false;
             Log.i(TAG, "PjSIPStack stopped");
             Intent intent = new Intent(DRING_CONNECTION_CHANGED);
@@ -290,7 +514,8 @@
 
         configurationCallback = new ConfigurationManagerCallback(this);
         callManagerCallBack = new CallManagerCallBack(this);
-        Ringservice.init(configurationCallback, callManagerCallBack);
+        videoManagerCallback = new VideoManagerCallback(this);
+        Ringservice.init(configurationCallback, callManagerCallBack, videoManagerCallback);
 
         ringerModeChanged(((AudioManager) getSystemService(Context.AUDIO_SERVICE)).getRingerMode());
         registerReceiver(ringerModeListener, RINGER_FILTER);
@@ -300,6 +525,8 @@
         Intent intent = new Intent(DRING_CONNECTION_CHANGED);
         intent.putExtra("connected", isPjSipStackStarted);
         sendBroadcast(intent);
+
+        videoManagerCallback.init();
     }
 
     // Enforce same thread contract to ensure we do not call from somewhere else
@@ -1181,5 +1408,43 @@
             });
         }
 
+        public void videoSurfaceAdded(String id)
+        {
+            Log.i(TAG, "DRingService.videoSurfaceAdded() " + id);
+            Shm shm = videoInputs.get(id);
+            SurfaceHolder holder = videoSurfaces.get(id).get();
+            if (shm != null && holder != null && shm.window == 0)
+                startVideo(shm, holder);
+        }
+
+        public void videoSurfaceRemoved(String id)
+        {
+            Log.i(TAG, "DRingService.videoSurfaceRemoved() " + id);
+            Shm shm = videoInputs.get(id);
+            if (shm != null)
+                stopVideo(shm);
+        }
+
+        public void videoPreviewSurfaceAdded() {
+            Log.i(TAG, "DRingService.videoPreviewSurfaceChanged()");
+            startCapture(previewParams);
+        }
+
+        public void videoPreviewSurfaceRemoved() {
+            Log.i(TAG, "DRingService.videoPreviewSurfaceChanged()");
+            stopCapture();
+        }
+
+        public void switchInput(final String id, final boolean front) {
+            getExecutor().execute(new SipRunnable() {
+                @Override
+                protected void doRun() throws SameThreadException, RemoteException {
+                    String uri = "camera://" + (front ? videoManagerCallback.cameraFront : videoManagerCallback.cameraBack);
+                    Log.i(TAG, "DRingService.switchInput() " + uri);
+                    Ringservice.switchInput(id, uri);
+                }
+            });
+        }
+
     };
 }
diff --git a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
index 2faa8db..20bf9d3 100644
--- a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
+++ b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
@@ -80,7 +80,14 @@
 
     void transfer(in String callID, in String to);
     void attendedTransfer(in String transferID, in String targetID);
-    
+
+    /* Video */
+    void switchInput(in String call, in boolean front);
+    void videoSurfaceAdded(in String call);
+    void videoSurfaceRemoved(in String call);
+    void videoPreviewSurfaceAdded();
+    void videoPreviewSurfaceRemoved();
+
     /* Conference related methods */
 
     void removeConference(in String confID);
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
index e4e3735..a76f58c 100644
--- a/ring-android/app/src/main/java/cx/ring/service/LocalService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
@@ -115,7 +115,7 @@
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
     public static final int PERMISSIONS_REQUEST = 57;
 
-    public final static String[] REQUIRED_RUNTIME_PERMISSIONS = {Manifest.permission.RECORD_AUDIO};
+    public final static String[] REQUIRED_RUNTIME_PERMISSIONS = {Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};
 
     private IDRingService mService = null;
     private final ContactsContentObserver contactContentObserver = new ContactsContentObserver();
diff --git a/ring-android/app/src/main/java/cx/ring/service/VideoManagerCallback.java b/ring-android/app/src/main/java/cx/ring/service/VideoManagerCallback.java
new file mode 100644
index 0000000..731cc31
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/service/VideoManagerCallback.java
@@ -0,0 +1,149 @@
+/*
+ *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Damien Riegel <damien.riegel@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.service;
+
+import android.hardware.Camera;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+
+public class VideoManagerCallback extends VideoCallback implements Camera.PreviewCallback
+{
+    private static final String TAG = VideoManagerCallback.class.getSimpleName();
+
+    private final DRingService mService;
+    private final HashMap<String, DRingService.VideoParams> params = new HashMap<>();
+
+    public int cameraFront = 0;
+    public int cameraBack = 0;
+
+    public VideoManagerCallback(DRingService s) {
+        mService = s;
+    }
+
+    public void init() {
+        int number_cameras = getNumberOfCameras();
+        Camera.CameraInfo camInfo = new Camera.CameraInfo();
+        for(int i = 0; i < number_cameras; i++) {
+            RingserviceJNI.addVideoDevice(Integer.toString(i));
+            Camera.getCameraInfo(i, camInfo);
+            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
+                cameraFront = i;
+            else
+                cameraBack = i;
+            Log.d(TAG, "Camera number " + i);
+        }
+        RingserviceJNI.setDefaultDevice(Integer.toString(cameraFront));
+    }
+
+    @Override
+    public void decodingStarted(String id, String shm_path, int w, int h, boolean is_mixer) {
+        mService.decodingStarted(id, shm_path, w, h, is_mixer);
+    }
+
+    @Override
+    public void decodingStopped(String id, String shm_path, boolean is_mixer) {
+        mService.decodingStopped(id);
+    }
+
+    public void onPreviewFrame(byte[] data, Camera camera) {
+        int ptr = RingserviceJNI.obtainFrame(data.length);
+        if (ptr != 0)
+            RingserviceJNI.setVideoFrame(data, data.length, ptr);
+        RingserviceJNI.releaseFrame(ptr);
+    }
+
+    public void setParameters(String camid, int format, int width, int height, int rate) {
+        int id = Integer.valueOf(camid);
+        DRingService.VideoParams p = new DRingService.VideoParams(id, format, width, height, rate);
+        params.put(camid, p);
+    }
+
+    public void startCapture(String camid) {
+        DRingService.VideoParams p = params.get(camid);
+        if (p == null)
+            return;
+
+        mService.startCapture(p);
+    }
+
+    public void stopCapture() {
+        mService.stopCapture();
+    }
+
+    @Override
+    public void getCameraInfo(String camid, IntVect formats, UintVect sizes, UintVect rates) {
+        int id = Integer.valueOf(camid);
+
+        if (id < 0 || id >= getNumberOfCameras())
+            return;
+
+        Camera cam;
+        try {
+            cam = Camera.open(id);
+        } catch (Exception e) {
+            Log.d(TAG, e.getMessage());
+            return;
+        }
+
+        Camera.CameraInfo camInfo =     new Camera.CameraInfo();
+        Camera.getCameraInfo(id, camInfo);
+
+        Camera.Parameters param = cam.getParameters();
+        cam.release();
+
+        getFormats(param, formats);
+        getSizes(param, sizes);
+        getRates(param, rates);
+    }
+
+    private int getNumberOfCameras() {
+        return Camera.getNumberOfCameras();
+    }
+
+    private void getFormats(Camera.Parameters param, IntVect formats_) {
+        for(int fmt : param.getSupportedPreviewFormats()) {
+            formats_.add(fmt);
+        }
+    }
+
+    private void getSizes(Camera.Parameters param, UintVect sizes) {
+        int sw = 1280, sh = 720;
+        for(Camera.Size s : param.getSupportedPreviewSizes()) {
+            if (s.width < sw) {
+                sw = s.width;
+                sh = s.height;
+            }
+        }
+        Log.d(TAG, "Supported size: " + sw + " x " + sh);
+        sizes.add(sw);
+        sizes.add(sh);
+    }
+
+    private void getRates(Camera.Parameters param, UintVect rates_) {
+        for(int fps[] : param.getSupportedPreviewFpsRange()) {
+            int rate = (fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + fps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX])/2;
+            rates_.add(rate);
+        }
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/views/FitsWindowsLayout.java b/ring-android/app/src/main/java/cx/ring/views/FitsWindowsLayout.java
new file mode 100644
index 0000000..fec3d8d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/FitsWindowsLayout.java
@@ -0,0 +1,40 @@
+package cx.ring.views;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+public class FitsWindowsLayout extends FrameLayout {
+
+    private Rect windowInsets = new Rect();
+    private Rect tempInsets = new Rect();
+
+    public FitsWindowsLayout(Context context) {
+        super(context);
+    }
+
+    public FitsWindowsLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FitsWindowsLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        windowInsets.set(insets);
+        super.fitSystemWindows(insets);
+        return false;
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        tempInsets.set(windowInsets);
+        super.fitSystemWindows(tempInsets);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/jni/Android.mk b/ring-android/app/src/main/jni/Android.mk
index 7e190b6..d935c67 100644
--- a/ring-android/app/src/main/jni/Android.mk
+++ b/ring-android/app/src/main/jni/Android.mk
@@ -127,6 +127,7 @@
 					-lgmp -lgnutls -lopendht \
 					-lavformat -lavcodec -lavutil \
 					-lopus -lspeex \
+					-landroid \
 					$(CPP_STATIC)
 
 
diff --git a/ring-android/app/src/main/jni/jni_interface.i b/ring-android/app/src/main/jni/jni_interface.i
index 3205a23..0e28150 100644
--- a/ring-android/app/src/main/jni/jni_interface.i
+++ b/ring-android/app/src/main/jni/jni_interface.i
@@ -151,6 +151,7 @@
   }
 %}
 %template(Blob) vector<uint8_t>;
+%template(FloatVect) vector<float>;
 }
 
 /* not parsed by SWIG but needed by generated C files */
@@ -167,6 +168,7 @@
 %include "managerimpl.i"
 %include "callmanager.i"
 %include "configurationmanager.i"
+%include "videomanager.i"
 
 #include "dring/callmanager_interface.h"
 
@@ -175,13 +177,14 @@
  * that are not declared elsewhere in the c++ code
  */
 
-void init(ConfigurationCallback* confM, Callback* callM) {
+void init(ConfigurationCallback* confM, Callback* callM, VideoCallback* videoM) {
     using namespace std::placeholders;
 
     using std::bind;
     using DRing::exportable_callback;
     using DRing::CallSignal;
     using DRing::ConfigurationSignal;
+    using DRing::VideoSignal;
 
     using SharedCallback = std::shared_ptr<DRing::CallbackWrapperBase>;
 
@@ -249,16 +252,23 @@
 #endif
 */
 
+    const std::map<std::string, SharedCallback> videoEvHandlers = {
+        exportable_callback<VideoSignal::GetCameraInfo>(bind(&VideoCallback::getCameraInfo, videoM, _1, _2, _3, _4)),
+        exportable_callback<VideoSignal::SetParameters>(bind(&VideoCallback::setParameters, videoM, _1, _2, _3, _4, _5)),
+        exportable_callback<VideoSignal::StartCapture>(bind(&VideoCallback::startCapture, videoM, _1)),
+        exportable_callback<VideoSignal::StopCapture>(bind(&VideoCallback::stopCapture, videoM)),
+        exportable_callback<VideoSignal::DecodingStarted>(bind(&VideoCallback::decodingStarted, videoM, _1, _2, _3, _4, _5)),
+        exportable_callback<VideoSignal::DecodingStopped>(bind(&VideoCallback::decodingStopped, videoM, _1, _2, _3)),
+    };
+
     if (!DRing::init(static_cast<DRing::InitFlag>(DRing::DRING_FLAG_DEBUG)))
         return -1;
 
     registerCallHandlers(callEvHandlers);
     registerConfHandlers(configEvHandlers);
-/*    registerPresHandlers(presEvHandlers);
-#ifdef RING_VIDEO
+/*    registerPresHandlers(presEvHandlers); */
     registerVideoHandlers(videoEvHandlers);
-#endif
-*/
+
     DRing::start();
 }
 
diff --git a/ring-android/app/src/main/jni/videomanager.i b/ring-android/app/src/main/jni/videomanager.i
new file mode 100644
index 0000000..a40d527
--- /dev/null
+++ b/ring-android/app/src/main/jni/videomanager.i
@@ -0,0 +1,200 @@
+/*
+ *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
+ *
+ *  Authors: Damien Riegel <damien.riegel@savoirfairelinux.com>
+ *           Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *           Ciro Santilli <ciro.santilli@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%include <std_shared_ptr.i>
+%header %{
+#include <functional>
+#include <list>
+#include <mutex>
+
+#include "dring/dring.h"
+#include "dring/videomanager_interface.h"
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android/log.h>
+
+class VideoCallback {
+public:
+    virtual ~VideoCallback(){}
+    virtual void getCameraInfo(const std::string& device, std::vector<int> *formats, std::vector<unsigned> *sizes, std::vector<unsigned> *rates) {}
+    virtual void setParameters(const std::string, const int format, const int width, const int height, const int rate) {}
+    virtual void startCapture(const std::string& camid) {}
+    virtual void stopCapture() {}
+    virtual void decodingStarted(const std::string& id, const std::string& shm_path, int w, int h, bool is_mixer) {}
+    virtual void decodingStopped(const std::string& id, const std::string& shm_path, bool is_mixer) {}
+};
+%}
+
+%feature("director") VideoCallback;
+
+%{
+
+std::map<ANativeWindow*, std::unique_ptr<DRing::FrameBuffer>> windows {};
+std::mutex windows_mutex;
+
+JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_setVideoFrame(JNIEnv *jenv, jclass jcls, void * jarg1, jint jarg2, jlong jarg3)
+{
+    jenv->GetByteArrayRegion(jarg1, 0, jarg2, jarg3);
+}
+
+JNIEXPORT jlong JNICALL Java_cx_ring_service_RingserviceJNI_acquireNativeWindow(JNIEnv *jenv, jclass jcls, jobject javaSurface)
+{
+    return (jlong)(intptr_t)ANativeWindow_fromSurface(jenv, javaSurface);
+}
+
+JNIEXPORT jlong JNICALL Java_cx_ring_service_RingserviceJNI_releaseNativeWindow(JNIEnv *jenv, jclass jcls, jlong window_)
+{
+    std::lock_guard<std::mutex> guard(windows_mutex);
+    ANativeWindow *window = (ANativeWindow*)((intptr_t) window_);
+    ANativeWindow_release(window);
+}
+
+JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_setNativeWindowGeometry(JNIEnv *jenv, jclass jcls, jlong window_, int width, int height)
+{
+    ANativeWindow *window = (ANativeWindow*)((intptr_t) window_);
+    ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_RGBA_8888);
+}
+
+void AndroidDisplayCb(ANativeWindow *window, std::unique_ptr<DRing::FrameBuffer> frame)
+{
+    std::lock_guard<std::mutex> guard(windows_mutex);
+    try {
+        auto& i = windows.at(window);
+        ANativeWindow_Buffer buffer;
+        if (ANativeWindow_lock(window, &buffer, NULL) == 0) {
+            if (buffer.bits && frame && frame->ptr) {
+                if (buffer.stride == frame->width)
+                    memcpy(buffer.bits, frame->ptr, frame->width * frame->height * 4);
+                else {
+                    size_t line_size_in = frame->width * 4;
+                    size_t line_size_out = buffer.stride * 4;
+                    for (size_t i=0, n=frame->height; i<n; i++)
+                        memcpy(buffer.bits + line_size_out * i, frame->ptr + line_size_in * i, line_size_in);
+                }
+            }
+            else
+                __android_log_print(ANDROID_LOG_WARN, "videomanager.i", "Can't copy surface");
+            ANativeWindow_unlockAndPost(window);
+        } else {
+            __android_log_print(ANDROID_LOG_WARN, "videomanager.i", "Can't lock surface");
+        }
+        i = std::move(frame);
+    } catch (...) {
+        __android_log_print(ANDROID_LOG_WARN, "videomanager.i", "Can't copy frame: no window");
+    }
+}
+
+std::unique_ptr<DRing::FrameBuffer> sinkTargetPullCallback(ANativeWindow *window, std::size_t bytes)
+{
+    try {
+        std::unique_ptr<DRing::FrameBuffer> ret;
+        {
+            std::lock_guard<std::mutex> guard(windows_mutex);
+            ret = std::move(windows.at(window));
+        }
+        if (not ret) {
+            __android_log_print(ANDROID_LOG_WARN, "videomanager.i", "Creating new video buffer of %zu kib", bytes/1024);
+            ret.reset(new DRing::FrameBuffer());
+        }
+        ret->storage.resize(bytes);
+        ret->ptr = ret->storage.data();
+        ret->ptrSize = bytes;
+        return ret;
+    } catch (...) {
+        return {};
+    }
+}
+
+JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_registerVideoCallback(JNIEnv *jenv, jclass jcls, jstring sinkId, jlong window)
+{
+    if(!sinkId) {
+        SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null string");
+        return 0;
+    }
+    const char *arg1_pstr = (const char *)jenv->GetStringUTFChars(sinkId, 0);
+    if (!arg1_pstr)
+        return 0;
+    const std::string sink(arg1_pstr);
+    jenv->ReleaseStringUTFChars(sinkId, arg1_pstr);
+
+    ANativeWindow* nativeWindow = (ANativeWindow*)((intptr_t) window);
+    auto f_display_cb = std::bind(&AndroidDisplayCb, nativeWindow, std::placeholders::_1);
+    auto p_display_cb = std::bind(&sinkTargetPullCallback, nativeWindow, std::placeholders::_1);
+
+    std::lock_guard<std::mutex> guard(windows_mutex);
+    windows.emplace(nativeWindow, nullptr);
+    DRing::registerSinkTarget(sink, DRing::SinkTarget {.pull=p_display_cb, .push=f_display_cb});
+}
+
+JNIEXPORT void JNICALL Java_cx_ring_service_RingserviceJNI_unregisterVideoCallback(JNIEnv *jenv, jclass jcls, jstring sinkId, jlong window)
+{
+    if(!sinkId) {
+        SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null string");
+        return 0;
+    }
+    const char *arg1_pstr = (const char *)jenv->GetStringUTFChars(sinkId, 0);
+    if (!arg1_pstr)
+        return 0;
+    const std::string sink(arg1_pstr);
+    jenv->ReleaseStringUTFChars(sinkId, arg1_pstr);
+
+    std::lock_guard<std::mutex> guard(windows_mutex);
+    DRing::registerSinkTarget(sink, DRing::SinkTarget {});
+    windows.erase(window);
+}
+
+%}
+%native(setVideoFrame) void setVideoFrame(void *, int, long);
+%native(acquireNativeWindow) jlong acquireNativeWindow(jobject);
+%native(releaseNativeWindow) void releaseNativeWindow(jlong);
+%native(setNativeWindowGeometry) void setNativeWindowGeometry(jlong, int, int);
+%native(registerVideoCallback) void registerVideoCallback(jstring, jlong);
+%native(unregisterVideoCallback) void unregisterVideoCallback(jstring, jlong);
+
+
+namespace DRing {
+
+void setDefaultDevice(const std::string& name);
+std::string getDefaultDevice();
+
+void startCamera();
+void stopCamera();
+bool hasCameraStarted();
+bool switchInput(const std::string& resource);
+bool switchToCamera();
+
+void addVideoDevice(const std::string &node);
+void removeVideoDevice(const std::string &node);
+long obtainFrame(int length);
+void releaseFrame(long frame);
+void registerSinkTarget(const std::string& sinkId, const DRing::SinkTarget& target);
+}
+
+class VideoCallback {
+public:
+    virtual ~VideoCallback(){}
+    virtual void getCameraInfo(const std::string& device, std::vector<int> *formats, std::vector<unsigned> *sizes, std::vector<unsigned> *rates){}
+    virtual void setParameters(const std::string, const int format, const int width, const int height, const int rate) {}
+    virtual void startCapture(const std::string& camid) {}
+    virtual void stopCapture() {}
+    virtual void decodingStarted(const std::string& id, const std::string& shm_path, int w, int h, bool is_mixer) {}
+    virtual void decodingStopped(const std::string& id, const std::string& shm_path, bool is_mixer) {}
+};
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..f36dcd4
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..4a5494f
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..0392d64
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..e1f0f1f
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..a80ccde
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..b0768e3
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..3eb24d1
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..8392b2a
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..951bc12
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..8b2415b
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/layout/activity_call_layout.xml b/ring-android/app/src/main/res/layout/activity_call_layout.xml
index 95a192e..e8f973d 100644
--- a/ring-android/app/src/main/res/layout/activity_call_layout.xml
+++ b/ring-android/app/src/main/res/layout/activity_call_layout.xml
@@ -19,11 +19,12 @@
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.          
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<cx.ring.views.FitsWindowsLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/slidingpanelayout"
+    android:id="@+id/maincalllayout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@color/cardview_dark_background"
     tools:context=".client.CallActivity">
 
     <!--
@@ -37,12 +38,12 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="right|center_vertical" />
--->
+
     <fragment
         android:id="@+id/ongoingcall_pane"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:name="cx.ring.fragments.CallFragment"
-        tools:layout="@layout/frag_call" />
+        tools:layout="@layout/frag_call" />-->
 
-</FrameLayout>
\ No newline at end of file
+</cx.ring.views.FitsWindowsLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_call.xml b/ring-android/app/src/main/res/layout/frag_call.xml
index d76d6ea..1ea59b9 100644
--- a/ring-android/app/src/main/res/layout/frag_call.xml
+++ b/ring-android/app/src/main/res/layout/frag_call.xml
@@ -1,161 +1,200 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<?xml version="1.0" encoding="utf-8"?><!--
+Copyright (C) 2004-2016 Savoir-faire Linux Inc.
+
+Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".client.CallActivity">
 
-    <RelativeLayout
-        android:id="@+id/contact_bubble_layout"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_centerHorizontal="true"
-        android:layout_centerVertical="true"
-        android:layout_marginBottom="16dp">
+    <SurfaceView
+        android:id="@+id/video_preview_surface"
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:layout_alignParentEnd="false"
+        android:layout_alignParentStart="false"
+        android:layout_centerInParent="true"
+        android:layout_gravity="center"
+        android:visibility="gone" />
 
-        <ImageView
-            android:id="@+id/contact_bubble"
-            android:layout_width="200dp"
-            android:layout_height="200dp"
+    <FrameLayout
+        android:id="@+id/inner_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true">
+
+        <SurfaceView
+            android:id="@+id/camera_preview_surface"
+            android:layout_width="160dp"
+            android:layout_height="120dp"
+            android:layout_alignParentBottom="true"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentRight="true"
+            android:layout_margin="8dp"
+            android:layout_gravity="bottom|right"
+            android:visibility="gone" />
+
+        <LinearLayout
+            android:id="@+id/contact_bubble_layout"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
             android:layout_centerHorizontal="true"
-            android:layout_marginLeft="16dp"
-            android:layout_marginRight="16dp"
-            android:layout_alignParentStart="false"
-            android:layout_alignParentTop="true"
-            android:layout_marginBottom="16dp" />
-
-        <TextView
-            android:id="@+id/contact_bubble_txt"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginBottom="16dp"
+            android:orientation="vertical"
             android:gravity="center_horizontal"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:textColor="@color/text_color_primary"
-            android:text="Adrien Béraud"
-            android:layout_below="@+id/contact_bubble" />
+            android:layout_gravity="center_vertical">
 
-        <TextView
-            android:id="@+id/contact_bubble_num_txt"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_horizontal"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textColor="@color/text_color_secondary"
-            android:text="(514) 279-2035"
-            android:layout_alignParentEnd="false"
-            android:layout_below="@+id/contact_bubble_txt" />
+            <ImageView
+                android:id="@+id/contact_bubble"
+                android:layout_width="160dp"
+                android:layout_height="160dp"
+                android:layout_alignParentStart="false"
+                android:layout_alignParentTop="true"
+                android:layout_centerHorizontal="true"
+                android:layout_marginBottom="16dp"
+                android:layout_marginLeft="16dp"
+                android:layout_marginRight="16dp" />
 
-    </RelativeLayout>
+            <TextView
+                android:id="@+id/contact_bubble_txt"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/contact_bubble"
+                android:gravity="center_horizontal"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:textColor="@color/text_color_primary_dark" />
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:layout_alignParentBottom="true"
-        android:layout_centerHorizontal="true"
-        android:layout_margin="24dp">
+            <TextView
+                android:id="@+id/contact_bubble_num_txt"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="false"
+                android:layout_below="@+id/contact_bubble_txt"
+                android:gravity="center_horizontal"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/text_color_secondary_dark"
+                android:singleLine="true"
+                android:ellipsize="middle"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp" />
 
-        <android.support.design.widget.FloatingActionButton
-            android:id="@+id/call_refuse_btn"
+            <TextView
+                android:id="@+id/call_status_txt"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:textColor="@color/text_color_primary_dark"
+                android:textSize="16sp"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        </LinearLayout>
+
+        <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:contentDescription="@string/action_call_decline"
+            android:orientation="horizontal"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_margin="12dp"
+            android:layout_gravity="bottom|center_horizontal">
+
+            <android.support.design.widget.FloatingActionButton
+                android:id="@+id/call_refuse_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:contentDescription="@string/action_call_decline"
+                android:src="@drawable/ic_call_end_white_24dp"
+                app:backgroundTint="@color/error_red"
+                app:elevation="6dp"
+                app:pressedTranslationZ="12dp"
+                app:rippleColor="@android:color/white" />
+
+            <android.support.design.widget.FloatingActionButton
+                android:id="@+id/call_accept_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="16dp"
+                android:contentDescription="@string/action_call_accept"
+                android:src="@drawable/ic_call_white_24dp"
+                app:backgroundTint="#4caf50"
+                app:elevation="6dp"
+                app:pressedTranslationZ="12dp"
+                app:rippleColor="@android:color/white" />
+        </LinearLayout>
+
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/call_hangup_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_margin="28dp"
             android:src="@drawable/ic_call_end_white_24dp"
             app:backgroundTint="@color/error_red"
             app:elevation="6dp"
             app:pressedTranslationZ="12dp"
-            app:rippleColor="@android:color/white" />
+            app:rippleColor="@android:color/white"
+            android:layout_gravity="bottom|center_horizontal" />
 
-        <android.support.design.widget.FloatingActionButton
-            android:id="@+id/call_accept_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="16dp"
-            android:contentDescription="@string/action_call_accept"
-            android:src="@drawable/ic_call_white_24dp"
-            app:backgroundTint="#4caf50"
-            app:elevation="6dp"
-            app:pressedTranslationZ="12dp"
-            app:rippleColor="@android:color/white" />
-    </LinearLayout>
+        <RelativeLayout
+            android:id="@+id/call_status_bar"
+            android:layout_width="match_parent"
+            android:layout_height="?android:attr/actionBarSize"
+            android:layout_alignParentTop="true"
+            android:visibility="visible">
 
-    <android.support.design.widget.FloatingActionButton
-        android:id="@+id/call_hangup_btn"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerHorizontal="true"
-        android:layout_margin="40dp"
-        android:src="@drawable/ic_call_end_white_24dp"
-        app:backgroundTint="@color/error_red"
-        app:elevation="6dp"
-        app:pressedTranslationZ="12dp"
-        app:rippleColor="@android:color/white"
-        android:layout_alignParentBottom="true" />
-
-    <RelativeLayout
-        android:id="@+id/call_status_bar"
-        android:layout_width="match_parent"
-        android:layout_height="?android:attr/actionBarSize"
-        android:layout_alignParentTop="true"
-        android:visibility="visible">
-
-        <ImageView
-            android:id="@+id/image_call"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_gravity="left"
-            android:layout_marginLeft="15dp"
-            android:layout_marginRight="10dp"
-            android:src="@drawable/ic_call_white_24dp"
-            android:tint="@android:color/black" />
-
-        <TextView
-            android:id="@+id/call_status_txt"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:layout_toRightOf="@+id/image_call"
-            android:textColor="@color/text_color_primary"
-            android:textSize="12sp" />
-
-        <ViewSwitcher
-            android:id="@+id/security_switcher"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:visibility="gone">
-
-            <Button
-                android:id="@+id/confirm_sas"
+            <ViewSwitcher
+                android:id="@+id/security_switcher"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:textColor="@color/white"
-                android:textSize="12sp" />
+                android:layout_centerVertical="true"
+                android:visibility="gone">
+
+                <Button
+                    android:id="@+id/confirm_sas"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:textColor="@android:color/white"
+                    android:textSize="12sp" />
+
+                <ImageView
+                    android:id="@+id/lock_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="end|center_vertical" />
+            </ViewSwitcher>
 
             <ImageView
-                android:id="@+id/lock_image"
+                android:id="@+id/security_indicator"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_gravity="end|center_vertical" />
-        </ViewSwitcher>
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:layout_margin="16dp"
+                android:src="@drawable/ic_lock_white_24dp"
+                android:tint="#4caf50"
+                android:visibility="gone" />
 
-
-        <ImageView
-            android:id="@+id/security_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentRight="true"
-            android:layout_centerVertical="true"
-            android:layout_margin="16dp"
-            android:src="@drawable/ic_lock_white_24dp"
-            android:tint="#4caf50"
-            android:visibility="gone" />
-
-    </RelativeLayout>
-
-</RelativeLayout>
\ No newline at end of file
+        </RelativeLayout>
+    </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/menu/ac_call.xml b/ring-android/app/src/main/res/menu/ac_call.xml
index 04f7155..6f78d8e 100644
--- a/ring-android/app/src/main/res/menu/ac_call.xml
+++ b/ring-android/app/src/main/res/menu/ac_call.xml
@@ -17,6 +17,12 @@
         android:checkable="true" />
 
     <item
+        android:id="@+id/menuitem_camera_flip"
+        app:showAsAction="always"
+        android:icon="@drawable/ic_camera_front_white_24dp"
+        android:title="@string/ab_action_flipcamera" />
+
+    <item
         android:id="@+id/menuitem_addcontact"
         app:showAsAction="always"
         android:icon="@drawable/ic_person_add_white_24dp"
diff --git a/ring-android/app/src/main/res/values-v21/styles.xml b/ring-android/app/src/main/res/values-v21/styles.xml
index 89b7d60..7f22be9 100644
--- a/ring-android/app/src/main/res/values-v21/styles.xml
+++ b/ring-android/app/src/main/res/values-v21/styles.xml
@@ -14,12 +14,31 @@
 
     </style>
 
+    <style name="AppTheme.ActionBar.Transparent" parent="style/Theme.AppCompat">
+        <item name="colorAccent">@color/color_primary_dark</item>
+        <item name="colorPrimary">@color/color_primary_light</item>
+        <item name="colorPrimaryDark">@color/color_primary_dark</item>
+        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
+        <item name="android:actionBarStyle">@style/ActionBar.Transparent</item>
+        <item name="actionBarStyle">@style/ActionBar.Transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="windowActionBarOverlay">true</item>
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+    </style>
+
     <style name="NativeActionBar" parent="@android:style/Widget.ActionBar">
         <item name="android:background">@color/actionbar</item>
         <item name="android:titleTextStyle">@style/NativeActionBar.Text</item>
         <item name="android:elevation">4dp</item>
     </style>
 
+    <style name="ActionBar.Transparent" parent="@android:style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse">
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:height">@dimen/abc_action_bar_default_height_material</item>
+        <item name="background">@android:color/transparent</item>
+    </style>
+
     <style name="NativeActionBar.Text" parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
     </style>
 </resources>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index 6782b19..bff090b 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -131,5 +131,6 @@
     <string name="share_via">Share via</string>
     <string name="write_a_message">Write a message</string>
     <string name="scan_qr">Scan QR Code</string>
+    <string name="ab_action_flipcamera">Flip camera</string>
 
 </resources>
diff --git a/ring-android/app/src/main/res/values/styles.xml b/ring-android/app/src/main/res/values/styles.xml
index 4d7f03a..41c4471 100644
--- a/ring-android/app/src/main/res/values/styles.xml
+++ b/ring-android/app/src/main/res/values/styles.xml
@@ -35,6 +35,19 @@
         <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
     </style>
 
+    <style name="AppTheme.ActionBar.Transparent" parent="style/Theme.AppCompat">
+        <item name="colorAccent">@color/color_primary_dark</item>
+        <item name="colorPrimary">@color/color_primary_light</item>
+        <item name="colorPrimaryDark">@color/color_primary_dark</item>
+        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
+        <item name="android:actionBarStyle">@style/ActionBar.Transparent</item>
+        <item name="actionBarStyle">@style/ActionBar.Transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="windowActionBarOverlay">true</item>
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+    </style>
+
     <style name="AppThemeWithoutOverlayCompatNoShadow" parent="AppThemeWithoutOverlayCompat">
         <item name="android:windowContentOverlay">@null</item>
     </style>
@@ -45,6 +58,12 @@
         <item name="elevation">4dp</item>
     </style>
 
+    <style name="ActionBar.Transparent" parent="@android:style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse">
+        <item name="android:background">@null</item>
+        <item name="background">@null</item>
+        <item name="android:height">@dimen/abc_action_bar_default_height_material</item>
+    </style>
+
     <style name="MyActionBar" parent="@style/Widget.AppCompat.ActionBar.Solid">
         <item name="android:height">@dimen/abc_action_bar_default_height_material</item>
         <item name="android:textColorPrimary">@color/text_color_primary_dark</item>