CallFragment: add MVP model

Refactor CallFragment by adding MVP model, moving video management from
RingApplication to HardwareServiceImpl and removing use of
VideoCallbackManager

Change-Id: Ie75a0d33fa138590911d19d113df362ade29b9f4
Reviewed-by: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
Tuleap: #1360
diff --git a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
index a1f5dc8..7f766b7 100644
--- a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
+++ b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
@@ -26,19 +26,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.ImageFormat;
-import android.hardware.Camera;
 import android.media.AudioManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.WindowManager;
 
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -61,7 +53,6 @@
 import cx.ring.service.CallManagerCallBack;
 import cx.ring.service.ConfigurationManagerCallback;
 import cx.ring.service.LocalService;
-import cx.ring.service.VideoManagerCallback;
 import cx.ring.services.AccountService;
 import cx.ring.services.CallService;
 import cx.ring.services.ConferenceService;
@@ -78,7 +69,6 @@
 
     private final static String TAG = RingApplication.class.getName();
     public final static String DRING_CONNECTION_CHANGED = BuildConfig.APPLICATION_ID + ".event.DRING_CONNECTION_CHANGE";
-    public final static String VIDEO_EVENT = BuildConfig.APPLICATION_ID + ".event.VIDEO_EVENT";
     public static final int PERMISSIONS_REQUEST = 57;
 
     private RingInjectionComponent mRingInjectionComponent;
@@ -87,20 +77,12 @@
     // Android Specific callbacks handlers. They rely on low level services notifications
     private ConfigurationManagerCallback mConfigurationCallback;
     private CallManagerCallBack mCallManagerCallBack;
-    public VideoManagerCallback mVideoManagerCallback;
 
     // true Daemon callbacks handlers. The notify the Android ones
     private Callback mCallAndConferenceCallbackHandler;
     private ConfigurationCallback mAccountAndContactCallbackHandler;
     private PresenceCallback mPresenceCallbackHandler;
     private VideoCallback mHardwareCallbackHandler;
-
-    public final Map<String, RingApplication.Shm> videoInputs = new HashMap<>();
-    public static WeakReference<SurfaceHolder> mCameraPreviewSurface = new WeakReference<>(null);
-    public static Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<String, WeakReference<SurfaceHolder>>());
-    private Camera previewCamera = null;
-    public RingApplication.VideoParams previewParams = null;
-
     /**
      * Handler to run tasks that needs to be on main thread (UI updates)
      */
@@ -187,8 +169,6 @@
             return;
         }
 
-        final boolean isVideoAllowed = mDeviceRuntimeService.hasVideoPermission();
-
         Future<Boolean> startResult = mExecutor.submit(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
@@ -196,7 +176,6 @@
                 // observe them)
                 mConfigurationCallback = new ConfigurationManagerCallback(getApplicationContext());
                 mCallManagerCallBack = new CallManagerCallBack(getApplicationContext());
-                mVideoManagerCallback = new VideoManagerCallback(RingApplication.this);
 
                 // mCallAndConferenceCallbackHandler is a wrapper to handle CallCallbacks and ConferenceCallbacks
                 mCallAndConferenceCallbackHandler = mDaemonService.getDaemonCallbackHandler(
@@ -213,7 +192,6 @@
                 mConferenceService.addObserver(mCallManagerCallBack);
                 mAccountService.addObserver(mConfigurationCallback);
                 mContactService.addObserver(mConfigurationCallback);
-                mHardwareService.addObserver(mVideoManagerCallback);
 
                 mDaemonService.startDaemon(
                         mCallAndConferenceCallbackHandler,
@@ -224,8 +202,9 @@
                 ringerModeChanged(((AudioManager) getSystemService(Context.AUDIO_SERVICE)).getRingerMode());
                 registerReceiver(ringerModeListener, RINGER_FILTER);
 
-                if (isVideoAllowed) {
-                    mVideoManagerCallback.init();
+                if(mDeviceRuntimeService.hasVideoPermission()) {
+                    //initVideo is called here to give time to the application to initialize hardware cameras
+                    mHardwareService.initVideo();
                 }
 
                 return true;
@@ -246,23 +225,6 @@
         mAccountService.loadAccountsFromDaemon(mPreferencesService.isConnectedWifiAndMobile());
     }
 
-    public void restartVideo() {
-
-        final boolean isVideoAllowed = mDeviceRuntimeService.hasVideoPermission();
-
-        Future<Boolean> result = mExecutor.submit(new Callable<Boolean>() {
-            @Override
-            public Boolean call() throws Exception {
-                if (isVideoAllowed) {
-                    mVideoManagerCallback.init();
-                }
-                return true;
-            }
-        });
-
-        FutureUtils.getFutureResult(result);
-    }
-
     public void terminateDaemon() {
         Future<Boolean> stopResult = mExecutor.submit(new Callable<Boolean>() {
             @Override
@@ -271,7 +233,6 @@
                 mDaemonService.stopDaemon();
                 mConfigurationCallback = null;
                 mCallManagerCallBack = null;
-                mVideoManagerCallback = null;
                 Intent intent = new Intent(DRING_CONNECTION_CHANGED);
                 intent.putExtra("connected", mDaemonService.isStarted());
                 sendBroadcast(intent);
@@ -339,245 +300,4 @@
     public void permissionHasBeenAsked(String permission) {
         mPermissionsBeingAsked.remove(permission);
     }
-
-    public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
-        Log.i(TAG, "DRingService.decodingStarted() " + id + " " + width + "x" + height);
-        Shm shm = new Shm();
-        shm.id = id;
-        shm.path = shmPath;
-        shm.w = width;
-        shm.h = height;
-        shm.mixer = isMixer;
-        videoInputs.put(id, shm);
-        WeakReference<SurfaceHolder> weakSurfaceHolder = videoSurfaces.get(id);
-        if (weakSurfaceHolder != null) {
-            SurfaceHolder holder = weakSurfaceHolder.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);
-        }
-    }
-
-    public void startVideo(Shm input, SurfaceHolder holder) {
-        Log.i(TAG, "DRingService.startVideo() " + input.id);
-
-        input.window = mHardwareService.startVideo(input.id, holder.getSurface(), input.w, input.h);
-
-        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;
-        }
-
-        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);
-    }
-
-    public void stopVideo(Shm input) {
-        Log.i(TAG, "DRingService.stopVideo() " + input.id);
-        if (input.window != 0) {
-            mHardwareService.stopVideo(input.id, input.window);
-            input.window = 0;
-        }
-
-        Intent intent = new Intent(VIDEO_EVENT);
-        intent.putExtra("started", false);
-        intent.putExtra("call", input.id);
-        sendBroadcast(intent);
-    }
-
-    static public int rotationToDegrees(int rotation) {
-        switch (rotation) {
-            case Surface.ROTATION_0:
-                return 0;
-            case Surface.ROTATION_90:
-                return 90;
-            case Surface.ROTATION_180:
-                return 180;
-            case Surface.ROTATION_270:
-                return 270;
-        }
-        return 0;
-    }
-
-    public void setVideoRotation(VideoParams videoParams, Camera.CameraInfo info) {
-        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
-        int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
-        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
-            videoParams.rotation = (info.orientation + rotation + 360) % 360;
-        } else {
-            videoParams.rotation = (info.orientation - rotation + 360) % 360;
-        }
-    }
-
-    private void setCameraDisplayOrientation(int camId, android.hardware.Camera camera) {
-        android.hardware.Camera.CameraInfo info =
-                new android.hardware.Camera.CameraInfo();
-        android.hardware.Camera.getCameraInfo(camId, info);
-        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
-        int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
-        int result;
-        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
-            result = (info.orientation + rotation) % 360;
-            result = (360 - result) % 360;  // compensate the mirror
-        } else {  // back-facing
-            result = (info.orientation - rotation + 360) % 360;
-        }
-        camera.setDisplayOrientation(result);
-    }
-
-    public void startCapture(final VideoParams videoParams) {
-        stopCapture();
-
-        SurfaceHolder surface = mCameraPreviewSurface.get();
-        if (surface == null) {
-            Log.w(TAG, "Can't start capture: no surface registered.");
-            previewParams = videoParams;
-            Intent intent = new Intent(VIDEO_EVENT);
-            intent.putExtra("start", true);
-            sendBroadcast(intent);
-            return;
-        }
-
-        if (videoParams == null) {
-            Log.w(TAG, "startCapture: no video parameters ");
-            return;
-        }
-        Log.d(TAG, "startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation);
-
-        final Camera preview;
-        try {
-            preview = Camera.open(videoParams.id);
-            setCameraDisplayOrientation(videoParams.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(videoParams.format);
-        parameters.setPreviewSize(videoParams.width, videoParams.height);
-        parameters.setRotation(0);
-
-        for (int[] fps : parameters.getSupportedPreviewFpsRange()) {
-            if (videoParams.rate >= fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
-                    videoParams.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, "Error while settings preview parameters", e);
-        }
-
-        preview.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
-            @Override
-            public void onPreviewFrame(byte[] data, Camera camera) {
-                mHardwareService.setVideoFrame(data, videoParams.width, videoParams.height, videoParams.rotation);
-                preview.addCallbackBuffer(data);
-            }
-        });
-
-        // enqueue first buffer
-        int bufferSize = parameters.getPreviewSize().width * parameters.getPreviewSize().height * ImageFormat.getBitsPerPixel(parameters.getPreviewFormat()) / 8;
-        preview.addCallbackBuffer(new byte[bufferSize]);
-
-        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 = videoParams;
-
-        Intent intent = new Intent(VIDEO_EVENT);
-        intent.putExtra("camera", videoParams.id == 1);
-        intent.putExtra("started", true);
-        intent.putExtra("width", videoParams.rotWidth);
-        intent.putExtra("height", videoParams.rotHeight);
-        sendBroadcast(intent);
-    }
-
-    public void stopCapture() {
-        Log.d(TAG, "stopCapture " + previewCamera);
-        if (previewCamera != null) {
-            final Camera preview = previewCamera;
-            final VideoParams p = previewParams;
-            previewCamera = null;
-            preview.setPreviewCallback(null);
-            preview.setErrorCallback(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);
-        }
-    }
-
-    static public class Shm {
-        String id;
-        String path;
-        int w, h;
-        boolean mixer;
-        public long window = 0;
-    }
-
-    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;
-        int format;
-
-        // size as captured by Android
-        public int width;
-        public int height;
-
-        //size, rotated, as seen by the daemon
-        public int rotWidth;
-        public int rotHeight;
-
-        int rate;
-        int rotation;
-    }
 }
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 e73417b..a79ba0b 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
@@ -24,67 +24,29 @@
 
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.SystemClock;
 import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
-import android.util.Pair;
 import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-
-import javax.inject.Inject;
 
 import cx.ring.BuildConfig;
 import cx.ring.R;
-import cx.ring.application.RingApplication;
-import cx.ring.facades.ConversationFacade;
 import cx.ring.fragments.CallFragment;
-import cx.ring.model.CallContact;
-import cx.ring.model.Conference;
-import cx.ring.model.Conversation;
-import cx.ring.model.SipCall;
 import cx.ring.model.Uri;
-import cx.ring.model.Account;
-import cx.ring.service.IDRingService;
-import cx.ring.service.LocalService;
-import cx.ring.services.AccountService;
-import cx.ring.utils.CallProximityManager;
+import cx.ring.services.NotificationService;
 
-import static cx.ring.service.LocalService.Callbacks;
-
-public class CallActivity extends AppCompatActivity implements Callbacks, CallFragment.ConversationCallbacks, CallProximityManager.ProximityDirector {
+public class CallActivity extends AppCompatActivity {
     static final String TAG = CallActivity.class.getSimpleName();
 
     public static final String ACTION_CALL = BuildConfig.APPLICATION_ID + ".action.call";
 
-    @Inject
-    AccountService mAccountService;
-
-    @Inject
-    ConversationFacade mConversationFacade;
-
-    private boolean init = false;
     private View mMainView;
 
-    private LocalService service;
-
-    private CallFragment mCurrentCallFragment;
-    private Conference mDisplayedConference;
-    private String mSavedConferenceId = 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;
@@ -92,20 +54,6 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (savedInstanceState != null)
-            mSavedConferenceId = savedInstanceState.getString("conference", null);
-        Log.d(TAG, "CallActivity onCreate " + mSavedConferenceId);
-
-        // Dependency injection
-        ((RingApplication) getApplication()).getRingInjectionComponent().inject(this);
-
-        int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-        Window w = getWindow();
-        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);
 
@@ -122,20 +70,41 @@
             }
         });
 
-        Intent intent = new Intent(this, LocalService.class);
-        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-    }
+        android.net.Uri u = getIntent().getData();
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Window window = getWindow();
-        window.setFormat(PixelFormat.RGBA_8888);
+        String action = getIntent().getAction();
+        if (Intent.ACTION_CALL.equals(action) || ACTION_CALL.equals(action)) {
+            Uri number = new Uri(u.getSchemeSpecificPart());
+            Log.d(TAG, "number " + number);
+
+            boolean hasVideo = getIntent().getBooleanExtra("video", false);
+            String accountId = getIntent().getStringExtra("account");
+
+
+            // Reload a new view
+            FragmentManager fragmentManager = getFragmentManager();
+            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+            CallFragment callFragment = CallFragment.newInstance(CallFragment.ACTION_PLACE_CALL,
+                    accountId,
+                    number,
+                    hasVideo);
+            fragmentTransaction.replace(R.id.main_call_layout, callFragment).commit();
+
+        } else if (Intent.ACTION_VIEW.equals(action)) {
+            String confId = getIntent().getStringExtra(NotificationService.KEY_CALL_ID);
+            Log.d(TAG, "conf " + confId);
+            // Reload a new view
+            FragmentManager fragmentManager = getFragmentManager();
+            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+            CallFragment callFragment = CallFragment.newInstance(CallFragment.ACTION_GET_CALL,
+                    confId);
+            fragmentTransaction.replace(R.id.main_call_layout, callFragment).commit();
+        }
+
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        Log.d(TAG, "onConfigurationChanged " + newConfig.screenWidthDp);
 
         currentOrientation = newConfig.orientation;
 
@@ -148,12 +117,6 @@
             showSystemUI();
         }
 
-        // Reload a new view
-        FragmentManager fragmentManager = getFragmentManager();
-        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
-        mCurrentCallFragment = new CallFragment();
-        fragmentTransaction.replace(R.id.main_call_layout, mCurrentCallFragment).commit();
-
         super.onConfigurationChanged(newConfig);
     }
 
@@ -188,199 +151,10 @@
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
-        if (hasFocus && currentOrientation == Configuration.ORIENTATION_LANDSCAPE)
+        if (hasFocus && currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
             hideSystemUI();
-        else
+        } else {
             showSystemUI();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        if (mDisplayedConference != null)
-            outState.putString("conference", mDisplayedConference.getId());
-    }
-
-    private Handler mHandler = new Handler();
-    private Runnable mUpdateTimeTask = new Runnable() {
-        @Override
-        public void run() {
-            if (mCurrentCallFragment != null)
-                mCurrentCallFragment.updateTime();
-            mHandler.postAtTime(this, SystemClock.uptimeMillis() + 1000);
         }
-    };
-
-    /* activity no more in foreground */
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mHandler.removeCallbacks(mUpdateTimeTask);
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.d(TAG, "CallActivity onDestroy");
-        unbindService(mConnection);
-        if (mProximityManager != null) {
-            mProximityManager.stopTracking();
-            mProximityManager.release(0);
-        }
-
-        super.onDestroy();
-    }
-
-    /**
-     * Defines callbacks for service binding, passed to bindService()
-     */
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @SuppressWarnings("unchecked")
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder binder) {
-            service = ((LocalService.LocalBinder) binder).getService();
-
-            if (!init) {
-                mProximityManager = new CallProximityManager(CallActivity.this, CallActivity.this);
-                mProximityManager.startTracking();
-
-                if (mSavedConferenceId != null) {
-                    mDisplayedConference = mConversationFacade.getConference(mSavedConferenceId);
-                } else {
-                    checkExternalCall();
-                }
-
-                if (mDisplayedConference == null || mDisplayedConference.getParticipants().isEmpty()) {
-                    Log.e(TAG, "Conference displayed is null or empty");
-                    CallActivity.this.finish();
-                    return;
-                }
-
-                Log.i(TAG, "CallActivity onCreate in:" + mDisplayedConference.isIncoming() +
-                        " out:" + mDisplayedConference.isOnGoing());
-                CallContact contact = mDisplayedConference.getParticipants().get(0).getContact();
-                if (null != contact) {
-                    Log.i(TAG, "CallActivity onCreate contact:" + contact.getDisplayName());
-                }
-
-                init = true;
-            }
-
-            FragmentManager fragmentManager = getFragmentManager();
-            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
-            mCurrentCallFragment = new CallFragment();
-            fragmentTransaction.add(R.id.main_call_layout, mCurrentCallFragment).commit();
-            hideSystemUI();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName arg0) {
-
-        }
-    };
-
-    private Pair<Account, Uri> guess(Uri number, String account_id) {
-        Account a = mAccountService.getAccount(account_id);
-        Conversation conv = mConversationFacade.findOrStartConversationByNumber(number);
-
-        // Guess account from number
-        if (a == null && number != null)
-            a = mAccountService.guessAccount(number);
-
-        // Guess number from account/call history
-        if (a != null && (number == null || number.isEmpty()))
-            number = new Uri(conv.getLastNumberUsed(a.getAccountID()));
-
-        // If no account found, use first active
-        if (a == null)
-            a = mAccountService.getAccounts().get(0);
-
-        // If no number found, use first from contact
-        if (number == null || number.isEmpty())
-            number = conv.getContact().getPhones().get(0).getNumber();
-
-        return new Pair<>(a, number);
-    }
-
-    private boolean checkExternalCall() {
-        Log.d(TAG, "intent " + getIntent().toString());
-
-        if (getIntent() == null) {
-            terminateCall();
-            return false;
-        }
-
-        android.net.Uri u = getIntent().getData();
-        if (u == null) {
-            terminateCall();
-            return false;
-        }
-
-        Log.d(TAG, "uri " + u.toString());
-
-        String action = getIntent().getAction();
-        if (Intent.ACTION_CALL.equals(action) || ACTION_CALL.equals(action)) {
-            Uri number = new Uri(u.getSchemeSpecificPart());
-            Log.d(TAG, "number " + number);
-
-            boolean hasVideo = getIntent().getBooleanExtra("video", false);
-            Pair<Account, Uri> g = guess(number, getIntent().getStringExtra("account"));
-
-            SipCall call = new SipCall(null, g.first.getAccountID(), g.second, SipCall.Direction.OUTGOING);
-            call.muteVideo(!hasVideo);
-
-            mDisplayedConference = mConversationFacade.placeCall(call);
-        } else if (Intent.ACTION_VIEW.equals(action)) {
-            String conf_id = u.getLastPathSegment();
-            Log.d(TAG, "conf " + conf_id);
-            mDisplayedConference = mConversationFacade.getConference(conf_id);
-        }
-
-        return false;
-    }
-
-    @Override
-    public IDRingService getRemoteService() {
-        return service.getRemoteService();
-    }
-
-    @Override
-    public LocalService getService() {
-        return service;
-    }
-
-    @Override
-    public Conference getDisplayedConference() {
-        return mDisplayedConference;
-    }
-
-    @Override
-    public void updateDisplayedConference(Conference c) {
-        mDisplayedConference = c;
-    }
-
-    @Override
-    public void terminateCall() {
-        mHandler.removeCallbacks(mUpdateTimeTask);
-        finish();
-    }
-
-    @Override
-    public void startTimer() {
-        mHandler.postDelayed(mUpdateTimeTask, 0);
-    }
-
-    @Override
-    public boolean shouldActivateProximity() {
-        return true;
-    }
-
-    @Override
-    public void onProximityTrackingChanged(boolean acquired) {
-    }
-
-    @Override
-    public void onBackPressed() {
-        mCurrentCallFragment.onBackPressed();
-        super.onBackPressed();
     }
 }
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 bf7c03c..091abe7 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
@@ -34,6 +34,7 @@
 import butterknife.ButterKnife;
 import cx.ring.R;
 import cx.ring.facades.ConversationFacade;
+import cx.ring.fragments.CallFragment;
 import cx.ring.fragments.ConversationFragment;
 
 public class ConversationActivity extends AppCompatActivity {
@@ -96,8 +97,8 @@
 
         if (mConversationFragment == null) {
             Bundle bundle = new Bundle();
-            bundle.putString("conversationID", getIntent().getData().getLastPathSegment());
-            bundle.putString("number", getIntent().getStringExtra("number"));
+            bundle.putString(ConversationFragment.KEY_CONVERSATION_ID, getIntent().getData().getLastPathSegment());
+            bundle.putString(CallFragment.KEY_NUMBER, getIntent().getStringExtra(CallFragment.KEY_NUMBER));
 
             mConversationFragment = new ConversationFragment();
             mConversationFragment.setArguments(bundle);
diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
index ac3f9e1..2acf485 100644
--- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
@@ -67,6 +67,7 @@
 import cx.ring.about.AboutFragment;
 import cx.ring.application.RingApplication;
 import cx.ring.fragments.AccountsManagementFragment;
+import cx.ring.fragments.CallFragment;
 import cx.ring.fragments.ConversationFragment;
 import cx.ring.fragments.SmartListFragment;
 import cx.ring.model.Account;
@@ -80,6 +81,7 @@
 import cx.ring.service.LocalService;
 import cx.ring.services.AccountService;
 import cx.ring.services.DeviceRuntimeService;
+import cx.ring.services.HardwareService;
 import cx.ring.services.NotificationService;
 import cx.ring.services.PreferencesService;
 import cx.ring.settings.SettingsFragment;
@@ -133,6 +135,9 @@
     PreferencesService mPreferencesService;
 
     @Inject
+    HardwareService mHardwareService;
+
+    @Inject
     AccountService mAccountService;
 
     @Inject
@@ -314,7 +319,7 @@
         }
         if (fContent instanceof SmartListFragment) {
             Bundle bundle = new Bundle();
-            bundle.putString("conversationID", intent.getStringExtra("conversationID"));
+            bundle.putString(ConversationFragment.KEY_CONVERSATION_ID, intent.getStringExtra(ConversationFragment.KEY_CONVERSATION_ID));
             startConversationTablet(bundle);
         }
     }
@@ -462,8 +467,10 @@
                         case Manifest.permission.CAMERA:
                             sharedPref.edit().putBoolean(getString(R.string.pref_systemCamera_key), grantResults[i] == PackageManager.PERMISSION_GRANTED).apply();
                             // permissions have changed, video params should be reset
-                            ((RingApplication) getApplication()).restartVideo();
-                            break;
+                            final boolean isVideoAllowed = mDeviceRuntimeService.hasVideoPermission();
+                            if (isVideoAllowed) {
+                                mHardwareService.initVideo();
+                            }
                     }
                 }
 
@@ -822,7 +829,7 @@
                     Intent intent = new Intent(Intent.ACTION_VIEW)
                             .setClass(HomeActivity.this, ConversationActivity.class)
                             .setData(Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, c.getIds().get(0)))
-                            .putExtra("number", selected);
+                            .putExtra(CallFragment.KEY_NUMBER, selected);
                     startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
                 }
             });
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
index 82ebee0..4a71a61 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
@@ -23,14 +23,16 @@
 
 import cx.ring.about.AboutFragment;
 import cx.ring.about.AboutPresenter;
+import cx.ring.account.RegisterNameDialog;
 import cx.ring.account.RingAccountSummaryFragment;
 import cx.ring.account.RingAccountSummaryPresenter;
 import cx.ring.application.RingApplication;
 import cx.ring.client.AccountEditionActivity;
 import cx.ring.client.AccountWizard;
-import cx.ring.client.CallActivity;
 import cx.ring.client.HomeActivity;
 import cx.ring.contactrequests.BlackListFragment;
+import cx.ring.contactrequests.PendingContactRequestsFragment;
+import cx.ring.facades.ConversationFacade;
 import cx.ring.fragments.AccountMigrationFragment;
 import cx.ring.fragments.AccountsManagementFragment;
 import cx.ring.fragments.AdvancedAccountFragment;
@@ -38,7 +40,6 @@
 import cx.ring.fragments.ConversationFragment;
 import cx.ring.fragments.MediaPreferenceFragment;
 import cx.ring.fragments.ProfileCreationFragment;
-import cx.ring.account.RegisterNameDialog;
 import cx.ring.fragments.RingAccountCreationFragment;
 import cx.ring.fragments.SIPAccountCreationFragment;
 import cx.ring.fragments.SecurityAccountFragment;
@@ -48,12 +49,10 @@
 import cx.ring.service.BootReceiver;
 import cx.ring.service.DRingService;
 import cx.ring.service.LocalService;
-import cx.ring.service.VideoManagerCallback;
 import cx.ring.services.AccountService;
 import cx.ring.services.CallService;
 import cx.ring.services.ConferenceService;
 import cx.ring.services.ContactServiceImpl;
-import cx.ring.facades.ConversationFacade;
 import cx.ring.services.DaemonService;
 import cx.ring.services.DeviceRuntimeServiceImpl;
 import cx.ring.services.HardwareService;
@@ -65,7 +64,6 @@
 import cx.ring.settings.SettingsPresenter;
 import cx.ring.share.ShareFragment;
 import cx.ring.share.SharePresenter;
-import cx.ring.contactrequests.PendingContactRequestsFragment;
 import dagger.Component;
 
 @Singleton
@@ -75,8 +73,6 @@
 
     void inject(RingNavigationFragment view);
 
-    void inject(CallActivity activity);
-
     void inject(HomeActivity activity);
 
     void inject(AccountWizard activity);
@@ -121,8 +117,6 @@
 
     void inject(DRingService service);
 
-    void inject(VideoManagerCallback callback);
-
     void inject(DeviceRuntimeServiceImpl service);
 
     void inject(DaemonService service);
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
index c37500c..4c392cb 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
@@ -39,6 +39,7 @@
 import cx.ring.services.DeviceRuntimeService;
 import cx.ring.services.DeviceRuntimeServiceImpl;
 import cx.ring.services.HardwareService;
+import cx.ring.services.HardwareServiceImpl;
 import cx.ring.services.HistoryService;
 import cx.ring.services.HistoryServiceImpl;
 import cx.ring.services.LogService;
@@ -139,8 +140,8 @@
 
     @Provides
     @Singleton
-    HardwareService provideHardwareService(DaemonService daemonService) {
-        HardwareService hardwareService = new HardwareService();
+    HardwareService provideHardwareService(Context context) {
+        HardwareServiceImpl hardwareService = new HardwareServiceImpl(context);
         mRingApplication.getRingInjectionComponent().inject(hardwareService);
         return hardwareService;
     }
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 b140d66..1ca65ab 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
@@ -1,8 +1,7 @@
 /*
- *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
  *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Hadrien De Sousa <hadrien.desousa@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
@@ -18,36 +17,21 @@
  *  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.app.Activity;
-import android.app.Fragment;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
-import android.media.AudioManager;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v7.app.ActionBar;
-import android.text.Editable;
 import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -58,16 +42,13 @@
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
-import android.widget.ViewSwitcher;
 
+import com.bumptech.glide.Glide;
 import com.skyfishjy.library.RippleBackground;
 
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
 import java.util.Locale;
 
 import javax.inject.Inject;
@@ -76,92 +57,72 @@
 import butterknife.ButterKnife;
 import butterknife.OnClick;
 import cx.ring.R;
-import cx.ring.adapters.ContactDetailsTask;
 import cx.ring.application.RingApplication;
+import cx.ring.call.CallPresenter;
+import cx.ring.call.CallView;
 import cx.ring.client.ConversationActivity;
 import cx.ring.client.HomeActivity;
-import cx.ring.facades.ConversationFacade;
-import cx.ring.interfaces.CallInterface;
 import cx.ring.model.CallContact;
-import cx.ring.model.Conference;
-import cx.ring.model.SecureSipCall;
-import cx.ring.model.ServiceEvent;
 import cx.ring.model.SipCall;
-import cx.ring.service.CallManagerCallBack;
-import cx.ring.service.IDRingService;
+import cx.ring.model.Uri;
+import cx.ring.mvp.BaseFragment;
 import cx.ring.service.LocalService;
-import cx.ring.services.AccountService;
-import cx.ring.services.NotificationService;
 import cx.ring.utils.ActionHelper;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.utils.BlockchainInputHandler;
+import cx.ring.utils.CircleTransform;
 import cx.ring.utils.ContentUriHandler;
 import cx.ring.utils.KeyboardVisibilityManager;
-import cx.ring.utils.Observable;
-import cx.ring.utils.Observer;
-import cx.ring.utils.VCardUtils;
-import ezvcard.VCard;
-import ezvcard.property.Photo;
 
-public class CallFragment extends Fragment implements CallInterface, ContactDetailsTask.DetailsLoadedCallback, Observer<ServiceEvent> {
+public class CallFragment extends BaseFragment<CallPresenter> implements CallView {
 
-    static final private String TAG = CallFragment.class.getSimpleName();
+    public static final String TAG = CallFragment.class.getSimpleName();
 
-    public static final int REQUEST_TRANSFER = 10;
+    public static final String ACTION_PLACE_CALL = "PLACE_CALL";
+    public static final String ACTION_GET_CALL = "GET_CALL";
 
-    //~ Regular expression to match DTMF supported characters : 0 to 9, A, B, C, D, * and #
-    public static final String DTMF_SUPPORTED_CHARS_REGEX = "^[a-dA-D0-9#*]*$";
-
-    // Screen wake lock for incoming call
-    private WakeLock mScreenWakeLock;
+    public static final String KEY_ACTION = "action";
+    public static final String KEY_ACCOUNT_ID = "accountId";
+    public static final String KEY_CONF_ID = "confId";
+    public static final String KEY_NUMBER = "number";
+    public static final String KEY_HAS_VIDEO = "hasVideo";
 
     @Inject
-    AccountService mAccountService;
-
-    @Inject
-    ConversationFacade mConversationFacade;
-
-    @Inject
-    NotificationService mNotificationService;
+    protected CallPresenter callPresenter;
 
     @BindView(R.id.contact_bubble_layout)
-    View contactBubbleLayout;
+    protected View contactBubbleLayout;
 
     @BindView(R.id.contact_bubble)
-    ImageView contactBubbleView;
+    protected ImageView contactBubbleView;
 
     @BindView(R.id.contact_bubble_txt)
-    TextView contactBubbleTxt;
+    protected TextView contactBubbleTxt;
 
     @BindView(R.id.contact_bubble_num_txt)
-    TextView contactBubbleNumTxt;
+    protected TextView contactBubbleNumTxt;
 
     @BindView(R.id.call_accept_btn)
-    View acceptButton;
+    protected View acceptButton;
 
     @BindView(R.id.call_refuse_btn)
-    View refuseButton;
+    protected View refuseButton;
 
     @BindView(R.id.call_hangup_btn)
-    View hangupButton;
+    protected View hangupButton;
 
     @BindView(R.id.call_status_txt)
-    TextView mCallStatusTxt;
-
-    @BindView(R.id.security_indicator)
-    View securityIndicator;
-
-    @BindView(R.id.security_switcher)
-    ViewSwitcher mSecuritySwitch;
+    protected TextView mCallStatusTxt;
 
     @BindView(R.id.dialpad_edit_text)
-    EditText mNumeralDialEditText;
+    protected EditText mNumeralDialEditText;
 
     @BindView(R.id.ripple_animation)
-    RippleBackground mPulseAnimation;
+    protected RippleBackground mPulseAnimation;
 
     @BindView(R.id.video_preview_surface)
-    SurfaceView mVideoSurface = null;
+    protected SurfaceView mVideoSurface = null;
+
+    @BindView(R.id.camera_preview_surface)
+    protected SurfaceView mVideoPreview = null;
 
     private MenuItem speakerPhoneBtn = null;
     private MenuItem addContactBtn = null;
@@ -169,65 +130,60 @@
     private MenuItem dialPadBtn = null;
     private MenuItem changeScreenOrientationBtn = null;
 
-    @BindView(R.id.camera_preview_surface)
-    SurfaceView videoPreview = null;
-
-    public ConversationCallbacks mCallbacks = sDummyCallbacks;
-
-    private AudioManager audioManager;
-    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 boolean ongoingCall = false;
-
-    private BlockchainInputHandler mBlockchainInputHandler;
-
+    // Screen wake lock for incoming call
+    private PowerManager.WakeLock mScreenWakeLock;
     private DisplayManager.DisplayListener displayListener;
 
-    @Override
-    public void onAttach(Activity activity) {
-        Log.i(TAG, "onAttach");
-        super.onAttach(activity);
+    public static CallFragment newInstance(@NonNull String action, @Nullable String accountID, @Nullable Uri number, boolean hasVideo) {
+        Bundle bundle = new Bundle();
+        bundle.putString(KEY_ACTION, action);
+        bundle.putString(KEY_ACCOUNT_ID, accountID);
+        bundle.putSerializable(KEY_NUMBER, number);
+        bundle.putBoolean(KEY_HAS_VIDEO, hasVideo);
+        CallFragment countDownFragment = new CallFragment();
+        countDownFragment.setArguments(bundle);
+        return countDownFragment;
+    }
 
-        if (!(activity instanceof ConversationCallbacks)) {
-            throw new IllegalStateException("Activity must implement fragment's callbacks.");
+    public static CallFragment newInstance(@NonNull String action, @Nullable String confId) {
+        Bundle bundle = new Bundle();
+        bundle.putString(KEY_ACTION, action);
+        bundle.putString(KEY_CONF_ID, confId);
+        CallFragment countDownFragment = new CallFragment();
+        countDownFragment.setArguments(bundle);
+        return countDownFragment;
+    }
+
+    @Override
+    protected CallPresenter createPresenter() {
+        return callPresenter;
+    }
+
+    @Override
+    protected void initPresenter(CallPresenter presenter) {
+        super.initPresenter(presenter);
+
+        String action = getArguments().getString(KEY_ACTION);
+        if (action.equals(ACTION_PLACE_CALL)) {
+            callPresenter.initOutGoing(getArguments().getString(KEY_ACCOUNT_ID),
+                    (Uri) getArguments().getSerializable(KEY_NUMBER),
+                    getArguments().getBoolean(KEY_HAS_VIDEO));
+        } else if (action.equals(ACTION_GET_CALL)) {
+            callPresenter.initIncoming(getArguments().getString(KEY_CONF_ID));
         }
-
-        mCallbacks = (ConversationCallbacks) activity;
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CallManagerCallBack.RECORD_STATE_CHANGED);
-        intentFilter.addAction(CallManagerCallBack.RTCP_REPORT_RECEIVED);
-        intentFilter.addAction(CallManagerCallBack.VCARD_COMPLETED);
-
-        intentFilter.addAction(RingApplication.VIDEO_EVENT);
-
-        getActivity().registerReceiver(mReceiver, intentFilter);
     }
 
+    @Nullable
     @Override
-    public void onDetach() {
-        Log.i(TAG, "onDetach");
-        getActivity().unregisterReceiver(mReceiver);
-        mCallbacks = sDummyCallbacks;
-        super.onDetach();
-    }
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+        setHasOptionsMenu(true);
+        View inflatedView = inflater.inflate(R.layout.frag_call, container, false);
 
-    @Override
-    public void onCreate(Bundle savedBundle) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedBundle);
+        ButterKnife.bind(this, inflatedView);
 
         // dependency injection
         ((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
 
-        audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-
-        setHasOptionsMenu(true);
         PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
         mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "cx.ring.onIncomingCall");
         mScreenWakeLock.setReferenceCounted(false);
@@ -251,158 +207,84 @@
                     getActivity().runOnUiThread(new Runnable() {
                         @Override
                         public void run() {
-                            try {
-                                mCallbacks.getRemoteService().switchInput(getConference().getId(), lastVideoSource);
-                            } catch (RemoteException e) {
-                                e.printStackTrace();
-                            }
+                            callPresenter.displayChanged();
                         }
                     });
                 }
             };
         }
+
+        mVideoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
+        mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                presenter.videoSurfaceCreated(holder);
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                presenter.videoSurfaceDestroyed();
+            }
+        });
+        inflatedView.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) {
+                callPresenter.layoutChanged();
+            }
+        });
+        inflatedView.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;
+                callPresenter.uiVisibilityChanged(ui);
+            }
+        });
+
+        mVideoPreview.getHolder().setFormat(PixelFormat.RGBA_8888);
+        mVideoPreview.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                presenter.previewVideoSurfaceCreated(holder);
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                presenter.previewVideoSurfaceDestroyed();
+            }
+        });
+        mVideoPreview.setZOrderMediaOverlay(true);
+
+        return inflatedView;
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
-        Log.i(TAG, "onDestroy");
+    public void onStop() {
+        super.onStop();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
+            displayManager.unregisterDisplayListener(displayListener);
+        }
+
         if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
             mScreenWakeLock.release();
         }
     }
 
-    /**
-     * The Activity calling this fragment has to implement this interface
-     */
-    public interface ConversationCallbacks extends LocalService.Callbacks {
-        void startTimer();
-
-        void terminateCall();
-
-        Conference getDisplayedConference();
-
-        void updateDisplayedConference(Conference c);
-
-        ActionBar getSupportActionBar();
-    }
-
-    private static final ConversationCallbacks sDummyCallbacks = new ConversationCallbacks() {
-        @Override
-        public void startTimer() {
-            //Dummy implementation
-        }
-
-        @Override
-        public void terminateCall() {
-            //Dummy implementation
-        }
-
-        @Override
-        public Conference getDisplayedConference() {
-            //Dummy implementation
-            return null;
-        }
-
-        @Override
-        public void updateDisplayedConference(Conference c) {
-            //Dummy implementation
-        }
-
-        @Override
-        public ActionBar getSupportActionBar() {
-            //Dummy implementation
-            return null;
-        }
-
-        @Override
-        public IDRingService getRemoteService() {
-            //Dummy implementation
-            return null;
-        }
-
-        @Override
-        public LocalService getService() {
-            //Dummy implementation
-            return null;
-        }
-    };
-
-    public class CallReceiver extends BroadcastReceiver {
-        private final String TAG = CallReceiver.class.getSimpleName();
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.contentEquals(LocalService.ACTION_CONF_UPDATE)) {
-                confUpdate();
-            } else if (action.contentEquals(RingApplication.VIDEO_EVENT)) {
-                if (mVideoSurface == null)
-                    return;
-                Conference conf = getConference();
-                if (intent.hasExtra("start")) {
-                    mVideoSurface.setVisibility(View.VISIBLE);
-                    videoPreview.setVisibility(View.VISIBLE);
-                } else if (intent.hasExtra("camera")) {
-                    previewWidth = intent.getIntExtra("width", 0);
-                    previewHeight = intent.getIntExtra("height", 0);
-                } else if (conf != null && conf.getId().equals(intent.getStringExtra("call"))) {
-                    if (mVideoSurface != null) {
-                        haveVideo = intent.getBooleanExtra("started", false);
-                        if (haveVideo) {
-                            mVideoSurface.setVisibility(View.VISIBLE);
-                            videoPreview.setVisibility(View.VISIBLE);
-
-                            videoWidth = intent.getIntExtra("width", 0);
-                            videoHeight = intent.getIntExtra("height", 0);
-                        } else {
-                            mVideoSurface.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.RTCP_REPORT_RECEIVED)) {
-                rtcpReportReceived(null, null); // FIXME
-            } else if (action.contentEquals(CallManagerCallBack.VCARD_COMPLETED)) {
-                updateContactBubble();
-            } else {
-                Log.e(TAG, "Unknown action: " + intent.getAction());
-            }
-        }
-    }
-
-    private final CallReceiver mReceiver = new CallReceiver();
-
-    public void refreshState() {
-        Conference conf = getConference();
-
-        if (conf == null) {
-            contactBubbleView.setImageBitmap(null);
-            contactBubbleTxt.setText("");
-            contactBubbleNumTxt.setText("");
-            acceptButton.setVisibility(View.GONE);
-            refuseButton.setVisibility(View.GONE);
-            hangupButton.setVisibility(View.GONE);
-        } else if (conf.getParticipants().size() == 1) {
-            SipCall call = conf.getParticipants().get(0);
-            if (call.isIncoming() && call.isRinging()) {
-                Log.w(TAG, "CallFragment refreshState INCOMING " + call.getCallId());
-                initIncomingCallDisplay();
-            } else if (conf.getParticipants().get(0).isRinging()) {
-                Log.w(TAG, "CallFragment refreshState RINGING " + call.getCallId());
-                initOutGoingCallDisplay();
-            } else if (call.isOngoing()) {
-                initNormalStateDisplay();
-            }
-        } else if (conf.getParticipants().size() > 1) {
-            initNormalStateDisplay();
-        }
-
-        getActivity().invalidateOptionsMenu();
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        presenter.configurationChanged();
     }
 
     @Override
@@ -419,21 +301,197 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
+        callPresenter.prepareOptionMenu();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (item.getItemId()) {
+            case android.R.id.home:
+            case R.id.menuitem_chat:
+                callPresenter.chatClick();
+                break;
+            case R.id.menuitem_addcontact:
+                callPresenter.acceptCall();
+                break;
+            case R.id.menuitem_speaker:
+                callPresenter.speakerClick();
+                getActivity().invalidateOptionsMenu();
+                break;
+            case R.id.menuitem_camera_flip:
+                callPresenter.switchVideoInputClick();
+                break;
+            case R.id.menuitem_dialpad:
+                callPresenter.dialpadClick();
+                break;
+            case R.id.menuitem_change_screen_orientation:
+                callPresenter.screenRotationClick();
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    public void blockScreenRotation() {
+        int currentOrientation = getResources().getConfiguration().orientation;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+            return;
+        }
+        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        } else {
+            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+    }
+
+    @Override
+    public void displayContactBubble(final boolean display) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                contactBubbleLayout.setVisibility(display ? View.VISIBLE : View.GONE);
+            }
+        });
+    }
+
+    @Override
+    public void displayVideoSurface(final boolean display) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVideoSurface.setVisibility(display ? View.VISIBLE : View.GONE);
+                mVideoPreview.setVisibility(display ? View.VISIBLE : View.GONE);
+            }
+        });
+    }
+
+    @Override
+    public void displayHangupButton(boolean display) {
+        hangupButton.setVisibility(display ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void displayDialPadKeyboard() {
+        KeyboardVisibilityManager.showKeyboard(getActivity(),
+                mNumeralDialEditText,
+                InputMethodManager.SHOW_IMPLICIT);
+    }
+
+    @Override
+    public void switchCameraIcon(boolean isFront) {
+        flipCameraBtn.setIcon(isFront ? R.drawable.ic_camera_front_white : R.drawable.ic_camera_rear_white);
+    }
+
+    @Override
+    public void changeScreenRotation() {
+        int currentOrientation = getResources().getConfiguration().orientation;
+        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        } else {
+            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        }
+    }
+
+    @Override
+    public void updateTime(final long duration) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mCallStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
+            }
+        });
+    }
+
+    @Override
+    public void updateContactBubble(final String contactName) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (contactName.isEmpty()) {
+                    return;
+                }
+                if (contactBubbleTxt.getText().toString().contains(CallContact.PREFIX_RING)) {
+                    contactBubbleNumTxt.setVisibility(View.VISIBLE);
+                    contactBubbleNumTxt.setText(contactBubbleTxt.getText());
+                    contactBubbleTxt.setText(contactName);
+                } else {
+                    contactBubbleNumTxt.setVisibility(View.VISIBLE);
+                    contactBubbleNumTxt.setText(contactName);
+                }
+            }
+        });
+    }
+
+    /**
+     * Updates the bubble contact image with the vcard image, the contact image or by default the
+     * contact picture drawable.
+     */
+    @Override
+    public void updateContactBubbleWithVCard(final String contactName, final byte[] photo) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (photo != null && photo.length > 0) {
+                    Glide.with(getActivity())
+                            .load(photo)
+                            .transform(new CircleTransform(getActivity()))
+                            .error(R.drawable.ic_contact_picture)
+                            .into(contactBubbleView);
+                } else {
+                    Glide.with(getActivity())
+                            .load(R.drawable.ic_contact_picture)
+                            .into(contactBubbleView);
+                }
+
+                if (TextUtils.isEmpty(contactName) || contactName.contains(CallContact.PREFIX_RING)) {
+                    return;
+                }
+                if (contactBubbleTxt.getText().toString().contains(CallContact.PREFIX_RING)) {
+                    contactBubbleNumTxt.setVisibility(View.VISIBLE);
+                    contactBubbleNumTxt.setText(contactBubbleTxt.getText());
+                }
+                contactBubbleTxt.setText(contactName);
+            }
+        });
+    }
+
+    @Override
+    public void updateCallStatus(final int callState) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                switch (callState) {
+                    case SipCall.State.NONE:
+                        mCallStatusTxt.setText("");
+                        break;
+                    default:
+                        mCallStatusTxt.setText(callStateToHumanState(callState));
+                        break;
+                }
+
+            }
+        });
+    }
+
+    @Override
+    public void initMenu(boolean isSpeakerOn, boolean hasContact, boolean displayFlip, boolean canDial, boolean onGoingCall) {
         if (speakerPhoneBtn != null) {
-            boolean speakerPhone = audioManager.isSpeakerphoneOn();
-            if (speakerPhoneBtn.getIcon() != null)
-                speakerPhoneBtn.getIcon().setAlpha(speakerPhone ? 255 : 128);
-            speakerPhoneBtn.setChecked(speakerPhone);
+            speakerPhoneBtn.setVisible(onGoingCall);
+            if (speakerPhoneBtn.getIcon() != null) {
+                speakerPhoneBtn.getIcon().setAlpha(isSpeakerOn ? 255 : 128);
+            }
+            speakerPhoneBtn.setChecked(isSpeakerOn);
         }
         if (addContactBtn != null) {
-            SipCall call = (getConference() != null && !getConference().getParticipants().isEmpty()) ? getFirstParticipant() : null;
-            addContactBtn.setVisible(call != null && null != call.getContact() && call.getContact().isUnknown());
+            addContactBtn.setVisible(hasContact);
         }
-
-        flipCameraBtn.setVisible(haveVideo);
-
+        if (flipCameraBtn != null) {
+            flipCameraBtn.setVisible(displayFlip);
+        }
         if (dialPadBtn != null) {
-            dialPadBtn.setVisible(ongoingCall && getConference() != null && !getConference().isIncoming());
+            dialPadBtn.setVisible(canDial);
         }
         if (changeScreenOrientationBtn != null) {
             changeScreenOrientationBtn.setVisible(mVideoSurface.getVisibility() == View.VISIBLE);
@@ -441,196 +499,133 @@
     }
 
     @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        super.onOptionsItemSelected(item);
-        SipCall firstParticipant = getFirstParticipant();
-        switch (item.getItemId()) {
-            case android.R.id.home:
-                if (firstParticipant != null) {
-                    startConversationActivity(firstParticipant.getContact());
-                }
-                break;
-            case R.id.menuitem_chat:
-                if (firstParticipant == null
-                        || firstParticipant.getContact() == null
-                        || firstParticipant.getContact().getIds() == null
-                        || firstParticipant.getContact().getIds().isEmpty()) {
-                    break;
-                }
-                startConversationActivity(firstParticipant.getContact());
-                break;
-            case R.id.menuitem_addcontact:
-                if (firstParticipant == null || firstParticipant.getContact() == null) {
-                    break;
-                }
-                startActivityForResult(ActionHelper.getAddNumberIntentForContact(firstParticipant.getContact()),
-                        ConversationFragment.REQ_ADD_CONTACT);
-                break;
-            case R.id.menuitem_speaker:
-                audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
+    public void initNormalStateDisplay(final boolean hasVideo) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                acceptButton.setVisibility(View.GONE);
+                refuseButton.setVisibility(View.GONE);
+                hangupButton.setVisibility(View.VISIBLE);
+
+                contactBubbleLayout.setVisibility(hasVideo ? View.INVISIBLE : View.VISIBLE);
+
                 getActivity().invalidateOptionsMenu();
-                break;
-            case R.id.menuitem_camera_flip:
-                lastVideoSource = !lastVideoSource;
-                try {
-                    mCallbacks.getRemoteService().switchInput(getConference().getId(), lastVideoSource);
-                } catch (RemoteException e) {
-                    e.printStackTrace();
+            }
+        });
+    }
+
+    @Override
+    public void initIncomingCallDisplay() {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                acceptButton.setVisibility(View.VISIBLE);
+                refuseButton.setVisibility(View.VISIBLE);
+                hangupButton.setVisibility(View.GONE);
+            }
+        });
+    }
+
+    @Override
+    public void initOutGoingCallDisplay() {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                acceptButton.setVisibility(View.GONE);
+                refuseButton.setVisibility(View.VISIBLE);
+                hangupButton.setVisibility(View.GONE);
+            }
+        });
+    }
+
+    @Override
+    public void initContactDisplay(final SipCall call) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                CallContact contact = call.getContact();
+                if (contact == null) {
+                    return;
                 }
-                item.setIcon(lastVideoSource ? R.drawable.ic_camera_front_white : R.drawable.ic_camera_rear_white);
-                break;
-            case R.id.menuitem_dialpad:
-                KeyboardVisibilityManager.showKeyboard(getActivity(),
-                        mNumeralDialEditText,
-                        InputMethodManager.SHOW_IMPLICIT);
-                break;
-            case R.id.menuitem_change_screen_orientation:
-                changeScreenOrientation();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-
-        Conference c = getConference();
-        Log.w(TAG, "onStop() haveVideo=" + haveVideo);
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
-            displayManager.unregisterDisplayListener(displayListener);
-        }
-
-        RingApplication application = (RingApplication) getActivity().getApplication();
-
-        application.videoSurfaces.remove(c.getId());
-        application.mCameraPreviewSurface.clear();
-        try {
-            IDRingService service = mCallbacks.getRemoteService();
-            if (service != null) {
-                service.videoSurfaceRemoved(c.getId());
-                service.videoPreviewSurfaceRemoved();
+                final String name = contact.getDisplayName();
+                contactBubbleTxt.setText(name);
+                if (!name.contains(CallContact.PREFIX_RING) && contactBubbleNumTxt.getText().toString().isEmpty()) {
+                    contactBubbleNumTxt.setVisibility(View.VISIBLE);
+                    contactBubbleNumTxt.setText(call.getNumber());
+                }
+                mPulseAnimation.startRippleAnimation();
             }
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
+        });
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
+    public void resetVideoSize(final int videoWidth, final int videoHeight, final int previewWidth, final int previewHeight) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewGroup rootView = (ViewGroup) getView();
+                if (rootView == null)
+                    return;
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            DisplayManager displayManager = (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
-            displayManager.registerDisplayListener(displayListener, null);
-        }
+                double videoRatio = videoWidth / (double) videoHeight;
+                double screenRatio = getView().getWidth() / (double) getView().getHeight();
 
-        Conference c = getConference();
-        if (c != null && mVideoSurface != null && c.shouldResumeVideo()) {
-            Log.i(TAG, "Resuming video");
-            haveVideo = true;
-            mVideoSurface.setVisibility(View.VISIBLE);
-            videoPreview.setVisibility(View.VISIBLE);
+                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mVideoSurface.getLayoutParams();
+                int oldW = params.width;
+                int oldH = params.height;
+                if (videoRatio >= screenRatio) {
+                    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);
+                }
 
-            c.setResumeVideo(false);
-        }
-    }
+                if (oldW != params.width || oldH != params.height) {
+                    mVideoSurface.setLayoutParams(params);
+                }
 
-    @Override
-    public void onResume() {
-        super.onResume();
-        Log.i(TAG, "onResume()");
-
-        mAccountService.addObserver(this);
-        mConversationFacade.addObserver(this);
-
-        Conference conference = getConference();
-
-        confUpdate();
-
-        if (getActivity() != null) {
-            getActivity().invalidateOptionsMenu();
-        }
-
-        if (conference != null) {
-            conference.setVisible(true);
-            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity());
-            notificationManager.cancel(conference.getUuid());
-            if (conference.shouldResumeVideo()) {
-                Log.w(TAG, "Resuming video");
-                haveVideo = true;
-                mVideoSurface.setVisibility(View.VISIBLE);
-                videoPreview.setVisibility(View.VISIBLE);
-
-                conference.setResumeVideo(false);
+                DisplayMetrics metrics = getResources().getDisplayMetrics();
+                RelativeLayout.LayoutParams paramsPreview = (RelativeLayout.LayoutParams) mVideoPreview.getLayoutParams();
+                oldW = paramsPreview.width;
+                oldH = paramsPreview.height;
+                double previewMaxDim = Math.max(previewWidth, previewHeight);
+                double previewRatio = metrics.density * 160. / previewMaxDim;
+                paramsPreview.width = (int) (previewWidth * previewRatio);
+                paramsPreview.height = (int) (previewHeight * previewRatio);
+                if (oldW != paramsPreview.width || oldH != paramsPreview.height) {
+                    mVideoPreview.setLayoutParams(paramsPreview);
+                }
             }
-        }
-
-        refreshState();
+        });
     }
 
     @Override
-    public void onPause() {
-        Log.w(TAG, "onPause() haveVideo=" + haveVideo);
-        super.onPause();
-
-        mAccountService.removeObserver(this);
-        mConversationFacade.removeObserver(this);
-
-        Conference conference = getConference();
-        if (conference != null) {
-            conference.setVisible(false);
-            conference.setResumeVideo(haveVideo);
-            mNotificationService.showCallNotification(conference);
-        }
-    }
-
-    public void confUpdate() {
-        LocalService service = mCallbacks.getService();
-        if (service == null)
-            return;
-
-        Conference c = mConversationFacade.getConference(getConference().getId());
-        mCallbacks.updateDisplayedConference(c);
-        if (c == null || c.getParticipants().isEmpty()) {
-            mCallbacks.terminateCall();
-            return;
-        }
-
-        int newState = getHumanState(c);
-
-        Log.w(TAG, "confUpdate() " + getString(newState));
-
-        String newStateString = (newState == R.string.call_human_state_none ||
-                newState == R.string.conference_human_state_default)
-                ? "" :
-                getString(newState);
-        if (c.isOnGoing()) {
-            ongoingCall = true;
-            initNormalStateDisplay();
-        } else if (c.isRinging()) {
-            ongoingCall = false;
-            mCallStatusTxt.setText(newStateString);
-
-            if (c.isIncoming()) {
-                initIncomingCallDisplay();
-            } else
-                initOutGoingCallDisplay();
+    public void goToConversation(String conversationId) {
+        Intent intent = new Intent();
+        if (ConversationFragment.isTabletMode(getActivity())) {
+            intent.setClass(getActivity(), HomeActivity.class)
+                    .setAction(LocalService.ACTION_CONV_ACCEPT)
+                    .putExtra(ConversationFragment.KEY_CONVERSATION_ID, conversationId);
+            startActivity(intent);
         } else {
-            NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity());
-            notificationManager.cancel(c.getUuid());
-            mCallStatusTxt.setText(newStateString);
-            mCallbacks.terminateCall();
+            intent.setClass(getActivity(), ConversationActivity.class)
+                    .setAction(Intent.ACTION_VIEW)
+                    .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
+                    .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, conversationId));
+            startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
         }
     }
 
-    public int getHumanState(Conference conference) {
-        if (conference.getParticipants().size() == 1) {
-            return callStateToHumanState(conference.getParticipants().get(0).getCallState());
-        }
-        return getConferenceHumanState(conference.getState());
+    @Override
+    public void goToAddContact(CallContact callContact) {
+        startActivityForResult(ActionHelper.getAddNumberIntentForContact(callContact),
+                ConversationFragment.REQ_ADD_CONTACT);
+    }
+
+    @Override
+    public void finish() {
+        getActivity().finish();
     }
 
     public static int callStateToHumanState(final int state) {
@@ -661,604 +656,18 @@
         }
     }
 
-    public int getConferenceHumanState(final int state) {
-        switch (state) {
-            case Conference.state.ACTIVE_ATTACHED:
-                return R.string.conference_human_state_active_attached;
-            case Conference.state.ACTIVE_DETACHED:
-                return R.string.conference_human_state_active_detached;
-            case Conference.state.ACTIVE_ATTACHED_REC:
-                return R.string.conference_human_state_active_attached_rec;
-            case Conference.state.ACTIVE_DETACHED_REC:
-                return R.string.conference_human_state_active_detached_rec;
-            case Conference.state.HOLD:
-                return R.string.conference_human_state_hold;
-            case Conference.state.HOLD_REC:
-                return R.string.conference_human_state_hold_rec;
-            default:
-                return R.string.conference_human_state_default;
-        }
+    @OnClick({R.id.call_hangup_btn})
+    public void hangUpClicked() {
+        callPresenter.hangupCall();
     }
 
-    @Override
-    public void recordingChanged(Conference c, String callID, String filename) {
-
-    }
-
-    @Override
-    public void rtcpReportReceived(Conference c, HashMap<String, Integer> stats) {
-        // No implementation yet
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        SipCall transfer;
-        if (requestCode == REQUEST_TRANSFER) {
-            switch (resultCode) {
-                case TransferDFragment.RESULT_TRANSFER_CONF:
-                    Conference c = data.getParcelableExtra("target");
-                    transfer = data.getParcelableExtra("transfer");
-                    try {
-                        mCallbacks.getRemoteService().attendedTransfer(transfer.getCallId(), c.getParticipants().get(0).getCallId());
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-
-                case TransferDFragment.RESULT_TRANSFER_NUMBER:
-                    String to = data.getStringExtra("to_number");
-                    transfer = data.getParcelableExtra("transfer");
-                    try {
-                        mCallbacks.getRemoteService().transfer(transfer.getCallId(), to);
-                        mCallbacks.getRemoteService().hangUp(transfer.getCallId());
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-                case Activity.RESULT_CANCELED:
-                default:
-                    confUpdate();
-                    break;
-            }
-        }
-    }
-
-    void resetVideoSizes() {
-        ViewGroup rootView = (ViewGroup) getView();
-        if (rootView == null)
-            return;
-
-        double videoRatio = videoWidth / (double) videoHeight;
-        double screenRatio = getView().getWidth() / (double) getView().getHeight();
-
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mVideoSurface.getLayoutParams();
-        int oldW = params.width;
-        int oldH = params.height;
-        if (videoRatio >= screenRatio) {
-            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);
-            mVideoSurface.setLayoutParams(params);
-        }
-
-        DisplayMetrics metrics = getResources().getDisplayMetrics();
-        FrameLayout.LayoutParams paramsPreview = (FrameLayout.LayoutParams) videoPreview.getLayoutParams();
-        oldW = paramsPreview.width;
-        oldH = paramsPreview.height;
-        double previewMaxDim = Math.max(previewWidth, previewHeight);
-        double previewRatio = metrics.density * 160. / previewMaxDim;
-        paramsPreview.width = (int) (previewWidth * previewRatio);
-        paramsPreview.height = (int) (previewHeight * previewRatio);
-        if (oldW != paramsPreview.width || oldH != paramsPreview.height) {
-            Log.i(TAG, "onLayoutChange " + paramsPreview.width + " x " + paramsPreview.height);
-            videoPreview.setLayoutParams(paramsPreview);
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        updatePreview();
-    }
-
-    private void updatePreview() {
-        if (videoPreview.getVisibility() == View.VISIBLE) {
-            try {
-                mCallbacks.getRemoteService().setPreviewSettings();
-                mCallbacks.getRemoteService().videoPreviewSurfaceAdded();
-            } catch (RemoteException e) {
-                Log.e(TAG, "service not found ", e);
-            }
-        }
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        Log.i(TAG, "onCreateView");
-        View rootView = inflater.inflate(R.layout.frag_call, container, false);
-
-        ButterKnife.bind(this, rootView);
-
-        mNumeralDialEditText.requestFocus();
-        mNumeralDialEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                //~ Empty
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                String editTextString = s.toString();
-                String lastChar = editTextString.substring(start, start + count);
-                if (lastChar.matches(DTMF_SUPPORTED_CHARS_REGEX)) {
-                    try {
-                        Log.d(TAG, "Sending DTMF: " + lastChar.toUpperCase());
-                        mCallbacks.getRemoteService().playDtmf(lastChar.toUpperCase());
-                    } catch (RemoteException exc) {
-                        exc.printStackTrace();
-                    }
-                }
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                //~ Empty
-            }
-        });
-
-
-        mVideoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
-        mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                RingApplication application = (RingApplication) getActivity().getApplication();
-                contactBubbleLayout.setVisibility(View.GONE);
-                Conference c = getConference();
-                application.videoSurfaces.put(c.getId(), new WeakReference<>(holder));
-                blockSensorScreenRotation();
-                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) {
-                Conference c = getConference();
-                RingApplication application = (RingApplication) getActivity().getApplication();
-                application.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.getHolder().setFormat(PixelFormat.RGBA_8888);
-        videoPreview.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                RingApplication application = (RingApplication) getActivity().getApplication();
-                application.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) {
-                RingApplication application = (RingApplication) getActivity().getApplication();
-                if (videoPreview != null && application.mCameraPreviewSurface.get() == holder) {
-                    application.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() {
-        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();
-        contactBubbleTxt.setText(name);
-        if (call.getNumber().contentEquals(name)) {
-            contactBubbleNumTxt.setVisibility(View.GONE);
-        } else {
-            contactBubbleNumTxt.setVisibility(View.VISIBLE);
-            contactBubbleNumTxt.setText(call.getNumber());
-            getUsername(call);
-        }
-
-        mPulseAnimation.startRippleAnimation();
-
-        updateContactBubble();
-    }
-
-    private void getUsername(SipCall call) {
-        Log.d(TAG, "blockchain with " + call.getNumber());
-
-        if (mBlockchainInputHandler == null || !mBlockchainInputHandler.isAlive()) {
-            mBlockchainInputHandler = new BlockchainInputHandler(new WeakReference<>(mAccountService));
-        }
-
-        String[] split = call.getNumber().split(":");
-        if (split.length > 0) {
-            mBlockchainInputHandler.enqueueNextLookup(split[1]);
-        }
-    }
-
-    private void initNormalStateDisplay() {
-        Log.i(TAG, "Start normal display");
-        mCallbacks.startTimer();
-        acceptButton.setVisibility(View.GONE);
-        refuseButton.setVisibility(View.GONE);
-        hangupButton.setVisibility(View.VISIBLE);
-
-        final SipCall call = getFirstParticipant();
-        initContactDisplay(call);
-
-        if (getActivity() != null) {
-            getActivity().invalidateOptionsMenu();
-        }
-
-        contactBubbleLayout.setVisibility(haveVideo ? View.GONE : View.VISIBLE);
-        updateSecurityDisplay();
-
-        updatePreview();
-    }
-
-    private void updateSecurityDisplay() {
-        //First we check if all participants use a security layer.
-        boolean secureCall = !getConference().getParticipants().isEmpty();
-        for (SipCall c : getConference().getParticipants())
-            secureCall &= c instanceof SecureSipCall && ((SecureSipCall) c).isSecure();
-
-        securityIndicator.setVisibility(secureCall ? View.VISIBLE : View.GONE);
-        if (!secureCall)
-            return;
-
-        Log.i(TAG, "Enable security display");
-        if (getConference().hasMultipleParticipants()) {
-            //TODO What layout should we put?
-        } else {
-            final SecureSipCall secured = (SecureSipCall) getFirstParticipant();
-            switch (secured.displayModule()) {
-                case SecureSipCall.DISPLAY_GREEN_LOCK:
-                    Log.i(TAG, "DISPLAY_GREEN_LOCK");
-                    showLock(R.drawable.green_lock);
-                    break;
-                case SecureSipCall.DISPLAY_RED_LOCK:
-                    Log.i(TAG, "DISPLAY_RED_LOCK");
-                    showLock(R.drawable.red_lock);
-                    break;
-                case SecureSipCall.DISPLAY_NONE:
-                    break;
-            }
-        }
-    }
-
-    private void showLock(int resId) {
-        ImageView lock = (ImageView) mSecuritySwitch.findViewById(R.id.lock_image);
-        lock.setImageDrawable(ResourcesCompat.getDrawable(getResources(), resId, null));
-        mSecuritySwitch.setDisplayedChild(1);
-        mSecuritySwitch.setVisibility(View.VISIBLE);
-    }
-
-    private void initIncomingCallDisplay() {
-        Log.i(TAG, "Start incoming display");
-        final SipCall call = getFirstParticipant();
-        if (mAccountService.getAccount(call.getAccount()).isAutoanswerEnabled()) {
-            try {
-                mCallbacks.getRemoteService().accept(call.getCallId());
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        } else {
-            initContactDisplay(call);
-            acceptButton.setVisibility(View.VISIBLE);
-            refuseButton.setVisibility(View.VISIBLE);
-            hangupButton.setVisibility(View.GONE);
-        }
-    }
-
-    private void initOutGoingCallDisplay() {
-        Log.i(TAG, "Start outgoing display");
-
-        final SipCall call = getFirstParticipant();
-        initContactDisplay(call);
-
-        acceptButton.setVisibility(View.GONE);
-        refuseButton.setVisibility(View.VISIBLE);
-        hangupButton.setVisibility(View.GONE);
-    }
-
-    public void updateTime() {
-        if (getConference() != null && !getConference().getParticipants().isEmpty()) {
-            long duration = System.currentTimeMillis() - getFirstParticipant().getTimestampStart();
-            duration = duration / 1000;
-            if (getConference().isOnGoing())
-                mCallStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
-        }
-    }
-
-    /**
-     * Updates the bubble contact image with the vcard image, the contact image or by default the
-     * contact picture drawable.
-     */
-    private void updateContactBubble() {
-        Conference conference = this.getConference();
-        Context context = getActivity();
-        if (conference == null || context == null) {
-            return;
-        }
-
-        SipCall participant = getFirstParticipant();
-        if (participant == null) {
-            return;
-        }
-
-        VCard vcard = null;
-        String username = participant.getNumberUri().getUsername();
-        if (username != null) {
-            Log.d(TAG, "username " + username);
-            vcard = VCardUtils.loadPeerProfileFromDisk(context.getFilesDir(), username + ".vcf");
-        }
-        if (vcard == null) {
-            Log.d(TAG, "No vcard.");
-            setDefaultPhoto();
-            return;
-        } else {
-            Log.d(TAG, "VCard found: " + vcard);
-        }
-
-        if (!vcard.getPhotos().isEmpty()) {
-            Photo tmp = vcard.getPhotos().get(0);
-            if (tmp.getData() != null) {
-                contactBubbleView.setImageBitmap(BitmapUtils.cropImageToCircle(tmp.getData()));
-            } else {
-                setDefaultPhoto();
-            }
-        } else {
-            setDefaultPhoto();
-        }
-
-        if (vcard.getFormattedName() == null || TextUtils.isEmpty(vcard.getFormattedName().getValue())) {
-            return;
-        }
-        contactBubbleTxt.setText(vcard.getFormattedName().getValue());
-        ActionBar ab = mCallbacks.getSupportActionBar();
-        ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
-        ab.setTitle(vcard.getFormattedName().getValue());
-
-        if (participant.getNumber().contentEquals(vcard.getFormattedName().getValue())) {
-            contactBubbleNumTxt.setVisibility(View.GONE);
-        } else {
-            contactBubbleNumTxt.setVisibility(View.VISIBLE);
-            contactBubbleNumTxt.setText(participant.getNumber());
-            getUsername(participant);
-        }
-    }
-
-
-    @OnClick({R.id.call_hangup_btn, R.id.call_refuse_btn})
-    public void hangUpClicked(View view) {
-        try {
-            final SipCall call = getFirstParticipant();
-            if (call == null) {
-                return;
-            }
-            final String callId = call.getCallId();
-            startConversationActivity(call.getContact());
-            if (view.getId() == R.id.call_hangup_btn) {
-                mCallbacks.getRemoteService().hangUp(callId);
-            } else {
-                mCallbacks.getRemoteService().refuse(callId);
-            }
-            mCallbacks.terminateCall();
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
+    @OnClick(R.id.call_refuse_btn)
+    public void refuseClicked() {
+        callPresenter.refuseCall();
     }
 
     @OnClick(R.id.call_accept_btn)
     public void acceptClicked() {
-        final SipCall call = getFirstParticipant();
-        if (call == null) {
-            return;
-        }
-        try {
-            mCallbacks.getRemoteService().accept(call.getCallId());
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
+        callPresenter.acceptCall();
     }
-
-    public void changeScreenOrientation() {
-        int currentOrientation = getResources().getConfiguration().orientation;
-        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        } else {
-            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-        }
-    }
-
-    private void blockSensorScreenRotation() {
-        changeScreenOrientationBtn.setVisible(true);
-        int currentOrientation = getResources().getConfiguration().orientation;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-            return;
-        }
-        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
-            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-        } else {
-            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        }
-    }
-
-    /**
-     * Helper accessor that check nullity or emptiness of components to access first call participant
-     *
-     * @return the first participant or null
-     */
-    @Nullable
-    private SipCall getFirstParticipant() {
-        if (getConference() == null || getConference().getParticipants() == null || getConference().getParticipants().isEmpty()) {
-            return null;
-        }
-        return getConference().getParticipants().get(0);
-    }
-
-    public void onBackPressed() {
-        SipCall call = getFirstParticipant();
-        if (call != null) {
-            startConversationActivity(call.getContact());
-        }
-    }
-
-    private void startConversationActivity(CallContact contact) {
-        if (contact == null || contact.getIds().isEmpty()) {
-            return;
-        }
-        Intent intent = new Intent();
-        if (ConversationFragment.isTabletMode(getActivity())) {
-            intent.setClass(getActivity(), HomeActivity.class)
-                    .setAction(LocalService.ACTION_CONV_ACCEPT)
-                    .putExtra("conversationID", contact.getIds().get(0));
-            startActivity(intent);
-        } else {
-            intent.setClass(getActivity(), ConversationActivity.class)
-                    .setAction(Intent.ACTION_VIEW)
-                    .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
-                    .setData(Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, contact.getIds().get(0)));
-            startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
-        }
-    }
-
-    private void setDefaultPhoto() {
-        if (getConference() != null
-                && getConference().getParticipants() != null
-                && !getConference().getParticipants().isEmpty()) {
-            final SipCall call = getConference().getParticipants().get(0);
-            final CallContact contact = call.getContact();
-            if (contact != null) {
-                new ContactDetailsTask(getActivity(), contact, this).run();
-            }
-        } else {
-            contactBubbleView.setImageDrawable(
-                    ResourcesCompat.getDrawable(getResources(), R.drawable.ic_contact_picture, null));
-        }
-    }
-
-    @Override
-    public void onDetailsLoaded(Bitmap bmp, String formattedName) {
-        if (bmp != null) {
-            contactBubbleView.setImageBitmap(bmp);
-        }
-
-        if (formattedName != null) {
-            contactBubbleTxt.setText(formattedName);
-            ActionBar ab = mCallbacks.getSupportActionBar();
-            ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
-            ab.setTitle(formattedName);
-        }
-    }
-
-    @Override
-    public void update(Observable observable, ServiceEvent event) {
-        if (event == null) {
-            return;
-        }
-
-        if (observable instanceof ConversationFacade) {
-            switch (event.getEventType()) {
-                case HISTORY_LOADED:
-                case CONVERSATIONS_CHANGED:
-                case CALL_STATE_CHANGED:
-                    RingApplication.uiHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            confUpdate();
-                        }
-                    });
-                    break;
-                default:
-                    Log.d(TAG, "This event type is not handled here " + event.getEventType());
-                    break;
-            }
-        } else {
-            switch (event.getEventType()) {
-                case REGISTERED_NAME_FOUND:
-                    final String name = event.getEventInput(ServiceEvent.EventInput.NAME, String.class);
-                    RingApplication.uiHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            contactBubbleNumTxt.setText(name);
-                        }
-                    });
-                    break;
-                default:
-                    Log.d(TAG, "This event type is not handled here " + event.getEventType());
-                    break;
-            }
-        }
-
-    }
-}
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
index 2d299f8..48b2878 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
@@ -47,6 +47,7 @@
 import cx.ring.model.Phone;
 import cx.ring.model.Uri;
 import cx.ring.mvp.BaseFragment;
+import cx.ring.services.NotificationService;
 import cx.ring.utils.ActionHelper;
 import cx.ring.utils.BitmapUtils;
 import cx.ring.utils.ClipboardHelper;
@@ -64,12 +65,13 @@
         ContactDetailsTask.DetailsLoadedCallback,
         ConversationView {
 
+    public static final int REQ_ADD_CONTACT = 42;
+    public static final String KEY_CONVERSATION_ID = "CONVERSATION_ID";
+
     private static final String TAG = ConversationFragment.class.getSimpleName();
     private static final String CONVERSATION_DELETE = "CONVERSATION_DELETE";
     private static final int MIN_SIZE_TABLET = 960;
 
-    public static final int REQ_ADD_CONTACT = 42;
-
     @Inject
     protected ConversationPresenter conversationPresenter;
 
@@ -77,7 +79,7 @@
     protected EditText mMsgEditTxt;
 
     @BindView(R.id.ongoingcall_pane)
-    protected ViewGroup mBottomPane;
+    protected ViewGroup mTopPane;
 
     @BindView(R.id.hist_list)
     protected RecyclerView mHistList;
@@ -140,8 +142,8 @@
         // Dependency injection
         ((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
 
-        if (mBottomPane != null) {
-            mBottomPane.setVisibility(View.GONE);
+        if (mTopPane != null) {
+            mTopPane.setVisibility(View.GONE);
         }
 
         LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
@@ -341,8 +343,8 @@
     @Override
     protected void initPresenter(ConversationPresenter presenter) {
         super.initPresenter(presenter);
-        String conversationId = getArguments().getString("conversationID");
-        Uri number = new Uri(getArguments().getString("number"));
+        String conversationId = getArguments().getString(KEY_CONVERSATION_ID);
+        Uri number = new Uri(getArguments().getString(CallFragment.KEY_NUMBER));
         conversationPresenter.init(conversationId, number);
     }
 
@@ -386,7 +388,7 @@
         getActivity().runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mBottomPane.setVisibility(display ? View.GONE : View.VISIBLE);
+                mTopPane.setVisibility(display ? View.GONE : View.VISIBLE);
             }
         });
     }
@@ -478,7 +480,7 @@
     public void goToCallActivity(String conferenceId) {
         startActivity(new Intent(Intent.ACTION_VIEW)
                 .setClass(getActivity().getApplicationContext(), CallActivity.class)
-                .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI, conferenceId)));
+                .putExtra(NotificationService.KEY_CALL_ID, conferenceId));
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
index ba88736..3612a6e 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
@@ -531,7 +531,7 @@
             startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
         } else {
             Bundle bundle = new Bundle();
-            bundle.putString("conversationID", callContact.getIds().get(0));
+            bundle.putString(ConversationFragment.KEY_CONVERSATION_ID, callContact.getIds().get(0));
             ((HomeActivity) getActivity()).startConversationTablet(bundle);
         }
     }
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
index 5c6df74..3767c76 100644
--- a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
@@ -23,8 +23,12 @@
 import android.os.Bundle;
 import android.view.View;
 
+import cx.ring.utils.Log;
+
 public abstract class BaseFragment<T extends RootPresenter> extends Fragment {
 
+    protected static final String TAG = BaseFragment.class.getSimpleName();
+
     protected T presenter;
 
     @Override
@@ -39,6 +43,7 @@
 
     @Override
     public void onDestroyView() {
+        Log.d(TAG, "onDestroyView");
         super.onDestroyView();
         presenter.unbindView();
     }
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 f42a03c..60fd660 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
@@ -35,7 +35,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.SurfaceHolder;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -49,7 +48,7 @@
 
 import cx.ring.BuildConfig;
 import cx.ring.application.RingApplication;
-import cx.ring.daemon.StringMap;
+import cx.ring.client.CallActivity;
 import cx.ring.model.Codec;
 import cx.ring.services.AccountService;
 import cx.ring.services.CallService;
@@ -69,6 +68,11 @@
     public static final String ACTION_TRUST_REQUEST_REFUSE = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_REFUSE";
     public static final String ACTION_TRUST_REQUEST_BLOCK = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_BLOCK";
 
+    static public final String ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT";
+    static public final String ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE";
+    static public final String ACTION_CALL_END = BuildConfig.APPLICATION_ID + ".action.CALL_END";
+    static public final String ACTION_CALL_VIEW = BuildConfig.APPLICATION_ID + ".action.CALL_VIEW";
+
     private static final String TAG = DRingService.class.getName();
 
     @Inject
@@ -441,7 +445,7 @@
 
         @Override
         public void playDtmf(final String key) throws RemoteException {
-            mCallService.playDtmf(key);
+
         }
 
         @Override
@@ -480,51 +484,38 @@
         }
 
         @Override
+        @Deprecated
         public void videoSurfaceAdded(String id) {
-            Log.d(TAG, "DRingService.videoSurfaceAdded() " + id);
-            RingApplication application = (RingApplication) getApplication();
-            RingApplication.Shm shm = ((RingApplication) getApplication()).videoInputs.get(id);
-            SurfaceHolder holder = application.videoSurfaces.get(id).get();
-            if (shm != null && holder != null && shm.window == 0) {
-                application.startVideo(shm, holder);
-            }
+
         }
 
         @Override
+        @Deprecated
         public void videoSurfaceRemoved(String id) {
-            Log.d(TAG, "DRingService.videoSurfaceRemoved() " + id);
-            RingApplication application = (RingApplication) getApplication();
-            RingApplication.Shm shm = application.videoInputs.get(id);
-            if (shm != null) {
-                application.stopVideo(shm);
-            }
+
         }
 
         @Override
+        @Deprecated
         public void videoPreviewSurfaceAdded() {
-            Log.i(TAG, "DRingService.videoPreviewSurfaceChanged()");
-            ((RingApplication) getApplication()).startCapture(((RingApplication) getApplication()).previewParams);
+
         }
 
         @Override
+        @Deprecated
         public void videoPreviewSurfaceRemoved() {
-            Log.i(TAG, "DRingService.videoPreviewSurfaceChanged()");
-            ((RingApplication) getApplication()).stopCapture();
+
         }
 
         @Override
+        @Deprecated
         public void switchInput(final String id, final boolean front) {
-            RingApplication application = (RingApplication) getApplication();
-            final int camId = (front ? application.mVideoManagerCallback.cameraFront : application.mVideoManagerCallback.cameraBack);
-            final String uri = "camera://" + camId;
-            final cx.ring.daemon.StringMap map = application.mVideoManagerCallback.getNativeParams(camId).toMap(getResources().getConfiguration().orientation);
-            mHardwareService.switchInput(id, uri, map);
         }
 
         @Override
+        @Deprecated
         public void setPreviewSettings() {
-            Map<String, StringMap> camSettings = mDeviceRuntimeService.retrieveAvailablePreviewSettings();
-            mHardwareService.setPreviewSettings(camSettings);
+
         }
 
         @Override
@@ -559,16 +550,25 @@
     };
 
     private void parseIntent(Intent intent) {
+        Bundle extras;
         switch (intent.getAction()) {
             case ACTION_TRUST_REQUEST_ACCEPT:
             case ACTION_TRUST_REQUEST_REFUSE:
-            case ACTION_TRUST_REQUEST_BLOCK: {
-                Bundle extras = intent.getExtras();
+            case ACTION_TRUST_REQUEST_BLOCK:
+                extras = intent.getExtras();
                 if (extras != null) {
                     handleTrustRequestAction(intent.getAction(), extras);
                 }
                 break;
-            }
+            case ACTION_CALL_ACCEPT:
+            case ACTION_CALL_REFUSE:
+            case ACTION_CALL_END:
+            case ACTION_CALL_VIEW:
+                extras = intent.getExtras();
+                if (extras != null) {
+                    handleCallAction(intent.getAction(), extras);
+                }
+                break;
             default:
                 break;
         }
@@ -580,20 +580,52 @@
         if (account != null && from != null) {
             mNotificationService.cancelTrustRequestNotification(account);
             switch (action) {
-                case ACTION_TRUST_REQUEST_ACCEPT: {
+                case ACTION_TRUST_REQUEST_ACCEPT:
                     mAccountService.acceptTrustRequest(account, from);
                     break;
-                }
-                case ACTION_TRUST_REQUEST_REFUSE: {
+                case ACTION_TRUST_REQUEST_REFUSE:
                     mAccountService.discardTrustRequest(account, from);
                     break;
-                }
-                case ACTION_TRUST_REQUEST_BLOCK: {
+                case ACTION_TRUST_REQUEST_BLOCK:
                     mAccountService.discardTrustRequest(account, from);
                     mContactService.removeContact(account, from);
                     break;
-                }
             }
         }
     }
+
+    private void handleCallAction(String action, Bundle extras) {
+        String callId = extras.getString(NotificationServiceImpl.KEY_CALL_ID);
+
+        if (callId == null || callId.isEmpty()) {
+            return;
+        }
+
+        switch (action) {
+            case ACTION_CALL_ACCEPT:
+                mCallService.accept(callId);
+                mNotificationService.cancelCallNotification(callId.hashCode());
+                startActivity(new Intent(Intent.ACTION_VIEW)
+                        .putExtras(extras)
+                        .setClass(getApplicationContext(), CallActivity.class)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
+                break;
+            case ACTION_CALL_REFUSE:
+                mCallService.refuse(callId);
+                mDeviceRuntimeService.closeAudioState();
+                mNotificationService.cancelCallNotification(callId.hashCode());
+                break;
+            case ACTION_CALL_END:
+                mCallService.hangUp(callId);
+                mDeviceRuntimeService.closeAudioState();
+                mNotificationService.cancelCallNotification(callId.hashCode());
+                break;
+            case ACTION_CALL_VIEW:
+                startActivity(new Intent(Intent.ACTION_VIEW)
+                        .putExtras(extras)
+                        .setClass(getApplicationContext(), CallActivity.class)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
+                break;
+        }
+    }
 }
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 90f465a..5fdb8f5 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
@@ -67,9 +67,6 @@
     static public final String ACTION_CONV_READ = BuildConfig.APPLICATION_ID + ".action.CONV_READ";
 
     // Receiving commands
-    static public final String ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT";
-    static public final String ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE";
-    static public final String ACTION_CALL_END = BuildConfig.APPLICATION_ID + ".action.CALL_END";
     static public final String ACTION_CONV_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CONV_ACCEPT";
 
     @Inject
@@ -277,41 +274,6 @@
                     sendBroadcast(new Intent(ACTION_CONF_UPDATE).setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONVERSATION_CONTENT_URI, convId)));
                     break;
                 }
-                case ACTION_CALL_ACCEPT: {
-                    String callId = intent.getData().getLastPathSegment();
-                    try {
-                        mService.accept(callId);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "ACTION_CALL_ACCEPT", e);
-                    }
-                    mDeviceRuntimeService.updateAudioState(mConversationFacade.getCurrentCallingConf());
-                    Conference conf = mConversationFacade.getConference(callId);
-                    if (conf != null && !conf.isVisible()) {
-                        startActivity(ActionHelper.getViewIntent(LocalService.this, conf).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                    }
-                    break;
-                }
-                case ACTION_CALL_REFUSE: {
-                    String call_id = intent.getData().getLastPathSegment();
-                    try {
-                        mService.refuse(call_id);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "ACTION_CALL_REFUSE", e);
-                    }
-                    mDeviceRuntimeService.updateAudioState(mConversationFacade.getCurrentCallingConf());
-                    break;
-                }
-                case ACTION_CALL_END: {
-                    String call_id = intent.getData().getLastPathSegment();
-                    try {
-                        mService.hangUp(call_id);
-                        mService.hangUpConference(call_id);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "ACTION_CALL_END", e);
-                    }
-                    mDeviceRuntimeService.updateAudioState(mConversationFacade.getCurrentCallingConf());
-                    break;
-                }
                 default:
                     break;
             }
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
deleted file mode 100644
index 6ef3e92..0000000
--- a/ring-android/app/src/main/java/cx/ring/service/VideoManagerCallback.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- *  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.content.res.Configuration;
-import android.graphics.Point;
-import android.hardware.Camera;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import java.util.HashMap;
-
-import javax.inject.Inject;
-
-import cx.ring.application.RingApplication;
-import cx.ring.daemon.IntVect;
-import cx.ring.daemon.StringMap;
-import cx.ring.daemon.UintVect;
-import cx.ring.model.ServiceEvent;
-import cx.ring.services.HardwareService;
-import cx.ring.utils.Observable;
-import cx.ring.utils.Observer;
-
-
-public class VideoManagerCallback implements Observer<ServiceEvent> {
-    private static final String TAG = VideoManagerCallback.class.getSimpleName();
-
-    @Inject
-    HardwareService mHardwareService;
-
-    private final RingApplication mRingApplication;
-    private final LongSparseArray<DeviceParams> mNativeParams = new LongSparseArray<>();
-    private final HashMap<String, RingApplication.VideoParams> mParams = new HashMap<>();
-
-    @Override
-    public void update(Observable o, ServiceEvent event) {
-        if (event == null) {
-            return;
-        }
-
-        switch (event.getEventType()) {
-            case DECODING_STARTED:
-                decodingStarted(
-                        event.getEventInput(ServiceEvent.EventInput.ID, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.PATHS, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.WIDTH, Integer.class),
-                        event.getEventInput(ServiceEvent.EventInput.HEIGHT, Integer.class),
-                        event.getEventInput(ServiceEvent.EventInput.IS_MIXER, Boolean.class)
-                );
-                break;
-            case DECODING_STOPPED:
-                decodingStopped(
-                        event.getEventInput(ServiceEvent.EventInput.ID, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.PATHS, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.IS_MIXER, Boolean.class)
-                );
-                break;
-            case GET_CAMERA_INFO:
-                getCameraInfo(
-                        event.getEventInput(ServiceEvent.EventInput.CAMERA_ID, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.FORMATS, IntVect.class),
-                        event.getEventInput(ServiceEvent.EventInput.SIZES, UintVect.class),
-                        event.getEventInput(ServiceEvent.EventInput.RATES, UintVect.class)
-                );
-                break;
-            case SET_PARAMETERS:
-                setParameters(
-                        event.getEventInput(ServiceEvent.EventInput.CAMERA_ID, String.class),
-                        event.getEventInput(ServiceEvent.EventInput.FORMATS, Integer.class),
-                        event.getEventInput(ServiceEvent.EventInput.WIDTH, Integer.class),
-                        event.getEventInput(ServiceEvent.EventInput.HEIGHT, Integer.class),
-                        event.getEventInput(ServiceEvent.EventInput.RATES, Integer.class)
-                );
-                break;
-            case START_CAPTURE:
-                startCapture(
-                        event.getEventInput(ServiceEvent.EventInput.CAMERA_ID, String.class)
-                );
-                break;
-            case STOP_CAPTURE:
-                stopCapture();
-                break;
-            default:
-                Log.i(TAG, "Unknown daemon event");
-                break;
-        }
-    }
-
-    static public class DeviceParams {
-        Point size;
-        long rate;
-        Camera.CameraInfo infos;
-
-        public StringMap toMap(int orientation) {
-            StringMap map = new StringMap();
-            boolean rotated = (size.x > size.y) == (orientation == Configuration.ORIENTATION_PORTRAIT);
-            map.set("size", Integer.toString(rotated ? size.y : size.x) + "x" + Integer.toString(rotated ? size.x : size.y));
-            map.set("rate", Long.toString(rate));
-            return map;
-        }
-    }
-
-    public int cameraFront = 0;
-    public int cameraBack = 0;
-
-    public VideoManagerCallback(RingApplication app) {
-        mRingApplication = app;
-        mRingApplication.getRingInjectionComponent().inject(this);
-    }
-
-    public void init() {
-        mNativeParams.clear();
-        int number_cameras = getNumberOfCameras();
-        Camera.CameraInfo camInfo = new Camera.CameraInfo();
-        for (int i = 0; i < number_cameras; i++) {
-            mHardwareService.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);
-        }
-        mHardwareService.setDefaultVideoDevice(Integer.toString(cameraFront));
-    }
-
-    public DeviceParams getNativeParams(int i) {
-        return mNativeParams.get(i);
-    }
-
-    private void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
-        mRingApplication.decodingStarted(id, shmPath, width, height, isMixer);
-    }
-
-    private void decodingStopped(String id, String shmPath, boolean isMixer) {
-        mRingApplication.decodingStopped(id);
-    }
-
-    private void setParameters(String camId, int format, int width, int height, int rate) {
-        int id = Integer.valueOf(camId);
-        DeviceParams p = mNativeParams.get(id);
-        RingApplication.VideoParams newParams = new RingApplication.VideoParams(id, format, p.size.x, p.size.y, rate);
-        newParams.rotWidth = width;
-        newParams.rotHeight = height;
-        mRingApplication.setVideoRotation(newParams, p.infos);
-        mParams.put(camId, newParams);
-    }
-
-    private void startCapture(String camId) {
-        RingApplication.VideoParams params = mParams.get(camId);
-        if (params == null) {
-            return;
-        }
-
-        mRingApplication.startCapture(params);
-    }
-
-    private void stopCapture() {
-        mRingApplication.stopCapture();
-    }
-
-    private 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.Parameters param = cam.getParameters();
-        cam.release();
-
-        getFormats(param, formats);
-
-        DeviceParams p = new DeviceParams();
-        p.size = getSizeToUse(param);
-        sizes.add(p.size.x);
-        sizes.add(p.size.y);
-        sizes.add(p.size.y);
-        sizes.add(p.size.x);
-
-        getRates(param, rates);
-        p.rate = rates.get(0);
-
-        p.infos = new Camera.CameraInfo();
-        Camera.getCameraInfo(id, p.infos);
-
-        mNativeParams.put(id, p);
-    }
-
-    private int getNumberOfCameras() {
-        return Camera.getNumberOfCameras();
-    }
-
-    private void getFormats(Camera.Parameters param, IntVect formats) {
-        for (int fmt : param.getSupportedPreviewFormats()) {
-            formats.add(fmt);
-        }
-    }
-
-    private Point getSizeToUse(Camera.Parameters param) {
-        final int MIN_WIDTH = 320;
-        final Point size = new Point(0, 0);
-        /** {@link Camera.Parameters#getSupportedPreviewSizes} :
-         * "This method will always return a list with at least one element."
-         * Attempt to find the size with width closest (but above) MIN_WIDTH. */
-        for (Camera.Size s : param.getSupportedPreviewSizes()) {
-            if (s.width < s.height)
-                continue;
-            if (size.x < MIN_WIDTH ? s.width > size.x : (s.width >= MIN_WIDTH && s.width < size.x)) {
-                size.x = s.width;
-                size.y = s.height;
-            }
-        }
-        Log.d(TAG, "Size to use: " + size.x + " x " + size.y);
-        return size;
-    }
-
-    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/services/DeviceRuntimeServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
index ea2cd77..960a860 100644
--- a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
@@ -22,7 +22,6 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.hardware.Camera;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
@@ -30,8 +29,6 @@
 import android.support.v4.content.ContextCompat;
 
 import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -39,9 +36,6 @@
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import cx.ring.application.RingApplication;
-import cx.ring.daemon.StringMap;
-import cx.ring.model.Conference;
 import cx.ring.utils.Log;
 import cx.ring.utils.MediaManager;
 import cx.ring.utils.NetworkUtils;
@@ -87,31 +81,31 @@
     }
 
     @Override
-    public void updateAudioState(final Conference conf) {
+    public void updateAudioState(final boolean isRinging) {
         Handler mainHandler = new Handler(mContext.getMainLooper());
 
         mainHandler.post(new Runnable() {
             @Override
             public void run() {
-                if (conf != null) {
-                    boolean incomingAndRinging = conf.isIncoming() && conf.isRinging();
-                    mediaManager.obtainAudioFocus(incomingAndRinging);
-                    if (incomingAndRinging) {
-                        mediaManager.audioManager.setMode(AudioManager.MODE_RINGTONE);
-                        mediaManager.startRing(null);
-                    } else {
-                        mediaManager.stopRing();
-                        mediaManager.audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
-                    }
+                mediaManager.obtainAudioFocus(isRinging);
+                if (isRinging) {
+                    mediaManager.audioManager.setMode(AudioManager.MODE_RINGTONE);
+                    mediaManager.startRing(null);
                 } else {
                     mediaManager.stopRing();
-                    mediaManager.abandonAudioFocus();
+                    mediaManager.audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
                 }
             }
         });
     }
 
     @Override
+    public void closeAudioState() {
+        mediaManager.stopRing();
+        mediaManager.abandonAudioFocus();
+    }
+
+    @Override
     public File provideFilesDir() {
         return mContext.getFilesDir();
     }
@@ -163,18 +157,6 @@
         return checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
     }
 
-    @Override
-    public Map<String, StringMap> retrieveAvailablePreviewSettings() {
-        RingApplication application = (RingApplication) mContext.getApplicationContext();
-        Map<String, StringMap> camSettings = new HashMap<>();
-        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
-            if (application.mVideoManagerCallback.getNativeParams(i) != null) {
-                camSettings.put(Integer.toString(i), application.mVideoManagerCallback.getNativeParams(i).toMap(mContext.getResources().getConfiguration().orientation));
-            }
-        }
-        return camSettings;
-    }
-
     private boolean checkPermission(String permission) {
         return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_GRANTED;
     }
diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
new file mode 100644
index 0000000..9f533e2
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
@@ -0,0 +1,565 @@
+/*
+ *  Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@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.services;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.media.AudioManager;
+import android.support.annotation.Nullable;
+import android.util.LongSparseArray;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import cx.ring.daemon.IntVect;
+import cx.ring.daemon.StringMap;
+import cx.ring.daemon.UintVect;
+import cx.ring.model.ServiceEvent;
+import cx.ring.utils.Log;
+
+public class HardwareServiceImpl extends HardwareService {
+
+    public static final String TAG = HardwareServiceImpl.class.getName();
+
+    private Context mContext;
+
+    private int cameraFront = 0;
+    private int cameraBack = 0;
+
+    private int currentCamera = -1;
+
+    private final Map<String, Shm> videoInputs = new HashMap<>();
+    private static WeakReference<SurfaceHolder> mCameraPreviewSurface = new WeakReference<>(null);
+    private static Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<String, WeakReference<SurfaceHolder>>());
+    private VideoParams previewParams = null;
+    private Camera previewCamera = null;
+    private final HashMap<String, VideoParams> mParams = new HashMap<>();
+    private final LongSparseArray<DeviceParams> mNativeParams = new LongSparseArray<>();
+
+    public HardwareServiceImpl(Context mContext) {
+        this.mContext = mContext;
+    }
+
+    public void initVideo() {
+        Log.i(TAG, "initVideo()");
+        mNativeParams.clear();
+        int numberCameras = Camera.getNumberOfCameras();
+        Camera.CameraInfo camInfo = new Camera.CameraInfo();
+        for (int i = 0; i < numberCameras; i++) {
+            addVideoDevice(Integer.toString(i));
+            Camera.getCameraInfo(i, camInfo);
+            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                cameraFront = i;
+            } else {
+                cameraBack = i;
+            }
+        }
+        currentCamera = cameraFront;
+        setDefaultVideoDevice(Integer.toString(cameraFront));
+    }
+
+    @Override
+    public boolean isSpeakerPhoneOn() {
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        return audioManager.isSpeakerphoneOn();
+    }
+
+    @Override
+    public void switchSpeakerPhone() {
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
+    }
+
+    public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
+        Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height);
+        Shm shm = new Shm();
+        shm.id = id;
+        shm.path = shmPath;
+        shm.w = width;
+        shm.h = height;
+        shm.mixer = isMixer;
+        videoInputs.put(id, shm);
+        WeakReference<SurfaceHolder> weakSurfaceHolder = videoSurfaces.get(id);
+        if (weakSurfaceHolder != null) {
+            SurfaceHolder holder = weakSurfaceHolder.get();
+            if (holder != null) {
+                shm.window = startVideo(id, holder.getSurface(), width, height);
+
+                if (shm.window == 0) {
+                    Log.i(TAG, "DRingService.decodingStarted() no window !");
+
+                    ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+                    event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
+                    setChanged();
+                    notifyObservers(event);
+                    return;
+                }
+
+                ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+                event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
+                event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
+                event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, shm.w);
+                event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, shm.h);
+                setChanged();
+                notifyObservers(event);
+            }
+        }
+    }
+
+    @Override
+    public void decodingStopped(String id, String shmPath, boolean isMixer) {
+        Log.i(TAG, "decodingStopped() " + id);
+        Shm shm = videoInputs.remove(id);
+        if (shm != null) {
+            stopVideo(shm.id, shm.window);
+        }
+    }
+
+    @Override
+    public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates) {
+        Log.d(TAG, "getCameraInfo: " + camId + ", " + formats + ", " + sizes + ", " + rates);
+        int id = Integer.valueOf(camId);
+
+        if (id < 0 || id >= Camera.getNumberOfCameras()) {
+            return;
+        }
+
+        Camera cam;
+        try {
+            cam = Camera.open(id);
+        } catch (Exception e) {
+            Log.d(TAG, e.getMessage());
+            return;
+        }
+
+        Camera.Parameters param = cam.getParameters();
+        cam.release();
+
+        for (int fmt : param.getSupportedPreviewFormats()) {
+            formats.add(fmt);
+        }
+
+        DeviceParams p = new DeviceParams();
+
+        int MIN_WIDTH = 320;
+        Point size = new Point(0, 0);
+        /** {@link Camera.Parameters#getSupportedPreviewSizes} :
+         * "This method will always return a list with at least one element."
+         * Attempt to find the size with width closest (but above) MIN_WIDTH. */
+        for (Camera.Size s : param.getSupportedPreviewSizes()) {
+            if (s.width < s.height) {
+                continue;
+            }
+            if (size.x < MIN_WIDTH ? s.width > size.x : (s.width >= MIN_WIDTH && s.width < size.x)) {
+                size.x = s.width;
+                size.y = s.height;
+            }
+        }
+
+        p.size = size;
+
+        sizes.add(p.size.x);
+        sizes.add(p.size.y);
+        sizes.add(p.size.y);
+        sizes.add(p.size.x);
+
+        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);
+        }
+        p.rate = rates.get(0);
+
+        p.infos = new Camera.CameraInfo();
+        Camera.getCameraInfo(id, p.infos);
+
+        mNativeParams.put(id, p);
+    }
+
+    @Override
+    public void setParameters(String camId, int format, int width, int height, int rate) {
+        Log.d(TAG, "setParameters: " + camId + ", " + format + ", " + width + ", " + height + ", " + rate);
+        int id = Integer.valueOf(camId);
+        DeviceParams deviceParams = mNativeParams.get(id);
+        VideoParams newParams = new VideoParams(id, format, deviceParams.size.x, deviceParams.size.y, rate);
+        newParams.rotWidth = width;
+        newParams.rotHeight = height;
+        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
+        if (deviceParams.infos.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            newParams.rotation = (deviceParams.infos.orientation + rotation + 360) % 360;
+        } else {
+            newParams.rotation = (deviceParams.infos.orientation - rotation + 360) % 360;
+        }
+        mParams.put(camId, newParams);
+    }
+
+    @Override
+    public void startCapture(@Nullable String camId) {
+        VideoParams videoParams;
+
+        if (camId == null && previewParams != null) {
+            videoParams = previewParams;
+        } else if (camId != null) {
+            videoParams = mParams.get(camId);
+        } else if (mParams.size() == 2) {
+            currentCamera = cameraFront;
+            videoParams = mParams.get(cameraFront);
+        } else {
+            currentCamera = cameraBack;
+            videoParams = mParams.get(cameraBack);
+        }
+
+        SurfaceHolder surface = mCameraPreviewSurface.get();
+        if (surface == null) {
+            Log.w(TAG, "Can't start capture: no surface registered.");
+            previewParams = videoParams;
+
+            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
+            setChanged();
+            notifyObservers(event);
+            return;
+        }
+
+        if (videoParams == null) {
+            Log.w(TAG, "startCapture: no video parameters ");
+            return;
+        }
+        Log.d(TAG, "startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation);
+
+        final Camera preview;
+        try {
+            if (previewCamera != null) {
+                previewCamera.release();
+                previewCamera = null;
+            }
+            preview = Camera.open(videoParams.id);
+            setCameraDisplayOrientation(videoParams.id, preview);
+        } catch (Exception e) {
+            Log.e(TAG, "Camera.open: " + e.getMessage());
+            return;
+        }
+
+        try {
+            surface.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+            preview.setPreviewDisplay(surface);
+        } catch (IOException e) {
+            Log.e(TAG, "setPreviewDisplay: " + e.getMessage());
+            return;
+        }
+
+        Camera.Parameters parameters = preview.getParameters();
+        parameters.setPreviewFormat(videoParams.format);
+        parameters.setPreviewSize(videoParams.width, videoParams.height);
+        parameters.setRotation(0);
+
+        for (int[] fps : parameters.getSupportedPreviewFpsRange()) {
+            if (videoParams.rate >= fps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
+                    videoParams.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, "Error while settings preview parameters", e);
+        }
+
+        final int videoWidth = videoParams.width;
+        final int heigth = videoParams.height;
+        final int rotation = videoParams.rotation;
+
+        preview.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
+            @Override
+            public void onPreviewFrame(byte[] data, Camera camera) {
+                setVideoFrame(data, videoWidth, heigth, rotation);
+                preview.addCallbackBuffer(data);
+            }
+        });
+
+        // enqueue first buffer
+        int bufferSize = parameters.getPreviewSize().width * parameters.getPreviewSize().height * ImageFormat.getBitsPerPixel(parameters.getPreviewFormat()) / 8;
+        preview.addCallbackBuffer(new byte[bufferSize]);
+
+        preview.setErrorCallback(new Camera.ErrorCallback() {
+            @Override
+            public void onError(int error, Camera cam) {
+                Log.w(TAG, "Camera onError " + error);
+                if (preview == cam) {
+                    stopCapture();
+                }
+            }
+        });
+        try {
+            preview.startPreview();
+        } catch (RuntimeException e) {
+            Log.e(TAG, "startPreview: " + e.getMessage());
+            return;
+        }
+
+        previewCamera = preview;
+        previewParams = videoParams;
+
+        ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_CAMERA, videoParams.id == 1);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, videoParams.rotWidth);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, videoParams.rotHeight);
+        setChanged();
+        notifyObservers(event);
+    }
+
+    @Override
+    public void stopCapture() {
+        Log.d(TAG, "stopCapture " + previewCamera);
+        if (previewCamera != null) {
+            final Camera preview = previewCamera;
+            final VideoParams params = previewParams;
+            previewCamera = null;
+            try {
+                preview.setPreviewCallback(null);
+                preview.setErrorCallback(null);
+                preview.stopPreview();
+                preview.release();
+            } catch (Exception e) {
+                Log.e(TAG, "stopCapture error" + e);
+            }
+
+            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_CAMERA, params.id == 1);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, false);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, params.width);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, params.height);
+            setChanged();
+            notifyObservers(event);
+        }
+    }
+
+    @Override
+    public void addVideoSurface(String id, Object holder) {
+        if (!(holder instanceof SurfaceHolder)) {
+            return;
+        }
+
+        Log.w(TAG, "addVideoSurface " + id + holder.hashCode());
+
+        Shm shm = videoInputs.get(id);
+        WeakReference<SurfaceHolder> surfaceHolder = new WeakReference<>((SurfaceHolder) holder);
+        videoSurfaces.put(id, surfaceHolder);
+        if (shm != null && shm.window == 0) {
+            shm.window = startVideo(shm.id, surfaceHolder.get().getSurface(), shm.w, shm.h);
+        }
+
+        if (shm == null || shm.window == 0) {
+            Log.i(TAG, "DRingService.addVideoSurface() no window !");
+
+            ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+            event.addEventInput(ServiceEvent.EventInput.VIDEO_START, true);
+            setChanged();
+            notifyObservers(event);
+            return;
+        }
+
+        ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, true);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_WIDTH, shm.w);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_HEIGHT, shm.h);
+        setChanged();
+        notifyObservers(event);
+
+    }
+
+    @Override
+    public void addPreviewVideoSurface(Object holder) {
+        if (!(holder instanceof SurfaceHolder)) {
+            return;
+        }
+
+        Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode());
+
+        mCameraPreviewSurface = new WeakReference<>((SurfaceHolder) holder);
+    }
+
+    @Override
+    public void removeVideoSurface(String id) {
+        Log.i(TAG, "removeVideoSurface " + id);
+        Shm shm = videoInputs.get(id);
+        if (shm == null) {
+            return;
+        }
+        if (shm.window != 0) {
+            try {
+                stopVideo(shm.id, shm.window);
+            } catch (Exception e) {
+                Log.e(TAG, "removeVideoSurface error" + e);
+            }
+
+            shm.window = 0;
+        }
+
+        ServiceEvent event = new ServiceEvent(ServiceEvent.EventType.VIDEO_EVENT);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_CALL, shm.id);
+        event.addEventInput(ServiceEvent.EventInput.VIDEO_STARTED, false);
+        setChanged();
+        notifyObservers(event);
+    }
+
+    @Override
+    public void removePreviewVideoSurface() {
+        Log.w(TAG, "removePreviewVideoSurface");
+        mCameraPreviewSurface.clear();
+    }
+
+    @Override
+    public void switchInput(String id) {
+        Log.w(TAG, "switchInput " + id);
+
+        final int camId;
+        if (currentCamera == cameraBack) {
+            camId = cameraFront;
+        } else {
+            camId = cameraBack;
+        }
+        currentCamera = camId;
+
+        final String uri = "camera://" + camId;
+        final StringMap map = mNativeParams.get(camId).toMap(mContext.getResources().getConfiguration().orientation);
+        this.switchInput(id, uri, map);
+    }
+
+    @Override
+    public void restartCamera(String id) {
+        stopCapture();
+        setPreviewSettings();
+        final String uri = "camera://" + currentCamera;
+        final StringMap map = mNativeParams.get(currentCamera).toMap(mContext.getResources().getConfiguration().orientation);
+        this.switchInput(id, uri, map);
+    }
+
+    @Override
+    public void setPreviewSettings() {
+        Map<String, StringMap> camSettings = new HashMap<>();
+        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
+            if (mNativeParams.get(i) != null) {
+                camSettings.put(Integer.toString(i), mNativeParams.get(i).toMap(mContext.getResources().getConfiguration().orientation));
+                Log.w(TAG, "setPreviewSettings camera:" + Integer.toString(i));
+            }
+        }
+        this.setPreviewSettings(camSettings);
+    }
+
+    @Override
+    public int getCameraCount() {
+        return Camera.getNumberOfCameras();
+    }
+
+    @Override
+    public boolean isPreviewFromFrontCamera() {
+        return Camera.getNumberOfCameras() == 1 || currentCamera == cameraFront;
+    }
+
+    private void setCameraDisplayOrientation(int camId, Camera camera) {
+        Camera.CameraInfo info = new Camera.CameraInfo();
+        Camera.getCameraInfo(camId, info);
+        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        int rotation = rotationToDegrees(windowManager.getDefaultDisplay().getRotation());
+        int result;
+        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (info.orientation + rotation) % 360;
+            result = (360 - result) % 360;  // compensate the mirror
+        } else {  // back-facing
+            result = (info.orientation - rotation + 360) % 360;
+        }
+        camera.setDisplayOrientation(result);
+        Log.w(TAG, "setCameraDisplayOrientation " + Integer.toString(rotation) + " " + Integer.toString(result));
+    }
+
+    private int rotationToDegrees(int rotation) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                return 0;
+            case Surface.ROTATION_90:
+                return 90;
+            case Surface.ROTATION_180:
+                return 180;
+            case Surface.ROTATION_270:
+                return 270;
+        }
+        return 0;
+    }
+
+    private static class Shm {
+        String id;
+        String path;
+        int w, h;
+        boolean mixer;
+        long window = 0;
+    }
+
+    private static 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;
+
+        // size as captured by Android
+        public int width;
+        public int height;
+
+        //size, rotated, as seen by the daemon
+        public int rotWidth;
+        public int rotHeight;
+
+        public int rate;
+        public int rotation;
+    }
+
+    private static class DeviceParams {
+        Point size;
+        long rate;
+        Camera.CameraInfo infos;
+
+        public StringMap toMap(int orientation) {
+            StringMap map = new StringMap();
+            boolean rotated = (size.x > size.y) == (orientation == Configuration.ORIENTATION_PORTRAIT);
+            map.set("size", Integer.toString(rotated ? size.y : size.x) + "x" + Integer.toString(rotated ? size.x : size.y));
+            map.set("rate", Long.toString(rate));
+            return map;
+        }
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
index 3b5489f..dd925f4 100644
--- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
@@ -42,6 +42,7 @@
 
 import cx.ring.BuildConfig;
 import cx.ring.R;
+import cx.ring.client.CallActivity;
 import cx.ring.client.ConversationActivity;
 import cx.ring.client.HomeActivity;
 import cx.ring.contactrequests.PendingContactRequestsFragment;
@@ -66,9 +67,6 @@
 
     private static final String TAG = NotificationServiceImpl.class.getName();
 
-    public static final String TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID = "trustRequestNotificationAccountId";
-    public static final String TRUST_REQUEST_NOTIFICATION_FROM = "trustRequestNotificationFrom";
-
     private static final String NOTIF_CALL = "CALL";
     private static final String NOTIF_MSG = "MESSAGE";
     private static final String NOTIF_TRUST_REQUEST = "TRUST REQUEST";
@@ -109,12 +107,14 @@
 
         SipCall call = conference.getParticipants().get(0);
         CallContact contact = call.getContact();
-        final int notificationId = getCallNotificationId(call);
+        final int notificationId = call.getCallId().hashCode();
         notificationManager.cancel(notificationId);
 
-        final Uri callUri = Uri.withAppendedPath(ContentUriHandler.CALL_CONTENT_URI, call.getCallId());
-        PendingIntent gotoIntent = PendingIntent.getActivity(mContext, new Random().nextInt(),
-                ActionHelper.getViewIntent(mContext, conference), PendingIntent.FLAG_ONE_SHOT);
+        PendingIntent gotoIntent = PendingIntent.getService(mContext,
+                new Random().nextInt(),
+                new Intent(DRingService.ACTION_CALL_VIEW)
+                        .setClass(mContext, DRingService.class)
+                        .putExtra(KEY_CALL_ID, call.getCallId()), 0);
         NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext);
 
         if (conference.isOnGoing()) {
@@ -123,9 +123,9 @@
                     .setContentIntent(gotoIntent)
                     .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_hangup),
                             PendingIntent.getService(mContext, new Random().nextInt(),
-                                    new Intent(LocalService.ACTION_CALL_END)
-                                            .setClass(mContext, LocalService.class)
-                                            .setData(callUri),
+                                    new Intent(DRingService.ACTION_CALL_END)
+                                            .setClass(mContext, DRingService.class)
+                                            .putExtra(KEY_CALL_ID, call.getCallId()),
                                     PendingIntent.FLAG_ONE_SHOT));
         } else if (conference.isRinging()) {
             if (conference.isIncoming()) {
@@ -138,15 +138,15 @@
                         .setFullScreenIntent(gotoIntent, true)
                         .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_decline),
                                 PendingIntent.getService(mContext, new Random().nextInt(),
-                                        new Intent(LocalService.ACTION_CALL_REFUSE)
-                                                .setClass(mContext, LocalService.class)
-                                                .setData(callUri),
+                                        new Intent(DRingService.ACTION_CALL_REFUSE)
+                                                .setClass(mContext, DRingService.class)
+                                                .putExtra(KEY_CALL_ID, call.getCallId()),
                                         PendingIntent.FLAG_ONE_SHOT))
                         .addAction(R.drawable.ic_action_accept, mContext.getText(R.string.action_call_accept),
                                 PendingIntent.getService(mContext, new Random().nextInt(),
-                                        new Intent(LocalService.ACTION_CALL_ACCEPT)
-                                                .setClass(mContext, LocalService.class)
-                                                .setData(callUri),
+                                        new Intent(DRingService.ACTION_CALL_ACCEPT)
+                                                .setClass(mContext, DRingService.class)
+                                                .putExtra(KEY_CALL_ID, call.getCallId()),
                                         PendingIntent.FLAG_ONE_SHOT))
                         .addExtras(extras);
             } else {
@@ -155,12 +155,11 @@
                         .setContentIntent(gotoIntent)
                         .addAction(R.drawable.ic_call_end_white, mContext.getText(R.string.action_call_hangup),
                                 PendingIntent.getService(mContext, new Random().nextInt(),
-                                        new Intent(LocalService.ACTION_CALL_END)
-                                                .setClass(mContext, LocalService.class)
-                                                .setData(callUri),
+                                        new Intent(DRingService.ACTION_CALL_END)
+                                                .setClass(mContext, DRingService.class)
+                                                .putExtra(KEY_CALL_ID, call.getCallId()),
                                         PendingIntent.FLAG_ONE_SHOT));
             }
-
         } else {
             return;
         }
@@ -202,7 +201,7 @@
         if (ConversationFragment.isTabletMode(mContext)) {
             intentConversation = new Intent(LocalService.ACTION_CONV_ACCEPT)
                     .setClass(mContext, HomeActivity.class)
-                    .putExtra("conversationID", contact.getIds().get(0));
+                    .putExtra(ConversationFragment.KEY_CONVERSATION_ID, contact.getIds().get(0));
         } else {
             intentConversation = new Intent(Intent.ACTION_VIEW)
                     .setClass(mContext, ConversationActivity.class)
@@ -353,11 +352,7 @@
     }
 
     @Override
-    public void cancelCallNotification(SipCall call) {
-        if (call == null) {
-            return;
-        }
-        int notificationId = getCallNotificationId(call);
+    public void cancelCallNotification(int notificationId) {
         notificationManager.cancel(notificationId);
         mNotificationBuilders.remove(notificationId);
     }
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java b/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java
index 0e663e1..08842f0 100644
--- a/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java
+++ b/ring-android/app/src/main/java/cx/ring/utils/ActionHelper.java
@@ -195,11 +195,6 @@
         }
     }
 
-    public static Intent getViewIntent(Context context, Conference conference) {
-        final android.net.Uri confUri = android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI, conference.getId());
-        return new Intent(Intent.ACTION_VIEW).setData(confUri).setClass(context, CallActivity.class);
-    }
-
     public static String getShortenedNumber(String number) {
         if (number != null && !number.isEmpty() && number.length() > 18) {
             int size = number.length();
diff --git a/ring-android/app/src/main/res/layout-land/frag_call.xml b/ring-android/app/src/main/res/layout-land/frag_call.xml
index 277abde..8bbabda 100644
--- a/ring-android/app/src/main/res/layout-land/frag_call.xml
+++ b/ring-android/app/src/main/res/layout-land/frag_call.xml
@@ -16,7 +16,7 @@
 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"
+<RelativeLayout 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"
@@ -27,184 +27,144 @@
         android:id="@+id/video_preview_surface"
         android:layout_width="match_parent"
         android:layout_height="32dp"
+        android:layout_centerInParent="true"
         android:layout_gravity="center"
-        android:visibility="gone" />
+        android:visibility="gone"
+        tools:visibility="visible" />
 
-    <FrameLayout
-        android:id="@+id/inner_layout"
+    <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:visibility="gone"
+        tools:visibility="visible" />
+
+    <LinearLayout
+        android:id="@+id/contact_bubble_layout"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:fitsSystemWindows="true">
+        android:layout_marginBottom="16dp"
+        android:orientation="horizontal"
+        android:weightSum="100">
 
-        <SurfaceView
-            android:id="@+id/camera_preview_surface"
-            android:layout_width="160dp"
-            android:layout_height="120dp"
-            android:layout_gravity="bottom|end"
-            android:layout_margin="8dp"
-            android:visibility="gone" />
+        <com.skyfishjy.library.RippleBackground
+            android:id="@+id/ripple_animation"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="50"
+            app:rb_color="@color/white"
+            app:rb_duration="5000"
+            app:rb_radius="20dp"
+            app:rb_rippleAmount="3"
+            app:rb_scale="6">
+
+            <ImageView
+                android:id="@+id/contact_bubble"
+                android:layout_width="160dp"
+                android:layout_height="160dp"
+                android:layout_centerInParent="true"
+                tools:src="@drawable/ic_contact_picture" />
+        </com.skyfishjy.library.RippleBackground>
 
         <LinearLayout
-            android:id="@+id/contact_bubble_layout"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="match_parent"
-            android:weightSum="100"
-            android:orientation="horizontal"
-            android:layout_marginBottom="16dp">
+            android:layout_margin="10dp"
+            android:layout_weight="50"
+            android:gravity="center"
+            android:orientation="vertical">
 
-            <com.skyfishjy.library.RippleBackground
-                android:id="@+id/ripple_animation"
-                android:layout_width="0dp"
-                android:layout_weight="50"
-                android:layout_height="match_parent"
-                app:rb_color="@color/white"
-                app:rb_duration="5000"
-                app:rb_radius="20dp"
-                app:rb_rippleAmount="3"
-                app:rb_scale="6">
+            <TextView
+                android:id="@+id/contact_bubble_txt"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:ellipsize="middle"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:textColor="@color/text_color_primary_dark"
+                tools:text="Contact Name" />
 
-                <ImageView
-                    android:id="@+id/contact_bubble"
-                    android:layout_width="160dp"
-                    android:layout_height="160dp"
-                    android:layout_centerInParent="true"
-                    tools:src="@drawable/ic_contact_picture" />
-            </com.skyfishjy.library.RippleBackground>
+            <TextView
+                android:id="@+id/contact_bubble_num_txt"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:ellipsize="middle"
+                android:paddingEnd="32dp"
+                android:paddingStart="32dp"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="@color/text_color_secondary_dark"
+                tools:text="ring:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
+
+            <TextView
+                android:id="@+id/call_status_txt"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:textColor="@color/text_color_primary_dark"
+                android:textSize="16sp"
+                tools:text="Connecting" />
 
             <LinearLayout
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:gravity="center"
-                android:layout_weight="50"
-                android:layout_margin="10dp"
-                android:orientation="vertical">
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="12dp"
+                android:orientation="horizontal">
 
-                <TextView
-                    android:id="@+id/contact_bubble_txt"
+                <android.support.design.widget.FloatingActionButton
+                    android:id="@+id/call_refuse_btn"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:singleLine="true"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
-                    android:textColor="@color/text_color_primary_dark"
-                    tools:text="Contact Name" />
+                    android:layout_margin="16dp"
+                    android:contentDescription="@string/action_call_decline"
+                    android:src="@drawable/ic_call_end_white"
+                    app:backgroundTint="@color/error_red"
+                    app:elevation="6dp"
+                    app:pressedTranslationZ="12dp"
+                    app:rippleColor="@android:color/white" />
 
-                <TextView
-                    android:id="@+id/contact_bubble_num_txt"
+                <android.support.design.widget.FloatingActionButton
+                    android:id="@+id/call_accept_btn"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:ellipsize="end"
-                    android:paddingEnd="32dp"
-                    android:paddingStart="32dp"
-                    android:singleLine="true"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@color/text_color_secondary_dark"
-                    tools:text="ring:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
-
-                <TextView
-                    android:id="@+id/call_status_txt"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:textColor="@color/text_color_primary_dark"
-                    android:textSize="16sp"
-                    tools:text="Connecting" />
-
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_margin="12dp"
-                    android:orientation="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"
-                        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"
-                        app:backgroundTint="#4caf50"
-                        app:elevation="6dp"
-                        app:pressedTranslationZ="12dp"
-                        app:rippleColor="@android:color/white" />
-                </LinearLayout>
-
-                <ImageView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/imageView"
-                    android:layout_gravity="center_horizontal" />
+                    android:layout_margin="16dp"
+                    android:contentDescription="@string/action_call_accept"
+                    android:src="@drawable/ic_call_white"
+                    app:backgroundTint="#4caf50"
+                    app:elevation="6dp"
+                    app:pressedTranslationZ="12dp"
+                    app:rippleColor="@android:color/white" />
 
 
             </LinearLayout>
         </LinearLayout>
+    </LinearLayout>
 
-        <RelativeLayout
-            android:id="@+id/call_status_bar"
-            android:layout_width="match_parent"
-            android:layout_height="?android:attr/actionBarSize"
-            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_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="48dp"
+        android:src="@drawable/ic_call_end_white"
+        app:backgroundTint="@color/error_red"
+        app:elevation="6dp"
+        app:pressedTranslationZ="12dp"
+        app:rippleColor="@android:color/white" />
 
-            <ViewSwitcher
-                android:id="@+id/security_switcher"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
-                android:visibility="gone">
+    <EditText
+        android:id="@+id/dialpad_edit_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ems="10"
+        android:inputType="phone"
+        android:visibility="visible" />
 
-                <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/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"
-                android:tint="#4caf50"
-                android:visibility="gone" />
-
-        </RelativeLayout>
-
-        <android.support.design.widget.FloatingActionButton
-            android:id="@+id/call_hangup_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|center_horizontal"
-            android:layout_margin="28dp"
-            android:src="@drawable/ic_call_end_white"
-            app:backgroundTint="@color/error_red"
-            app:elevation="6dp"
-            app:pressedTranslationZ="12dp"
-            app:rippleColor="@android:color/white" />
-
-        <EditText
-            android:id="@+id/dialpad_edit_text"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:ems="10"
-            android:inputType="phone"
-            android:visibility="visible" />
-
-    </FrameLayout>
-</FrameLayout>
+</RelativeLayout>
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 18f4f13..b671ba3 100644
--- a/ring-android/app/src/main/res/layout/frag_call.xml
+++ b/ring-android/app/src/main/res/layout/frag_call.xml
@@ -16,7 +16,7 @@
 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"
+<RelativeLayout 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"
@@ -27,180 +27,142 @@
         android:id="@+id/video_preview_surface"
         android:layout_width="match_parent"
         android:layout_height="32dp"
+        android:layout_centerInParent="true"
         android:layout_gravity="center"
-        android:visibility="gone" />
+        android:visibility="gone"
+        tools:visibility="visible" />
 
-    <FrameLayout
-        android:id="@+id/inner_layout"
+    <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="12dp"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
+    <LinearLayout
+        android:id="@+id/contact_bubble_layout"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:fitsSystemWindows="true">
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:gravity="center"
+        android:orientation="vertical">
 
-        <SurfaceView
-            android:id="@+id/camera_preview_surface"
-            android:layout_width="160dp"
-            android:layout_height="120dp"
-            android:layout_gravity="bottom|end"
-            android:layout_margin="8dp"
-            android:visibility="gone" />
+        <com.skyfishjy.library.RippleBackground
+            android:id="@+id/ripple_animation"
+            android:layout_width="230dp"
+            android:layout_height="230dp"
+            app:rb_color="@color/white"
+            app:rb_duration="5000"
+            app:rb_radius="20dp"
+            app:rb_rippleAmount="3"
+            app:rb_scale="6">
 
-        <RelativeLayout
-            android:id="@+id/contact_bubble_layout"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_gravity="center"
-            android:layout_marginBottom="16dp">
-
-            <com.skyfishjy.library.RippleBackground
-                android:id="@+id/ripple_animation"
-                android:layout_width="230dp"
-                android:layout_height="230dp"
+            <ImageView
+                android:id="@+id/contact_bubble"
+                android:layout_width="160dp"
+                android:layout_height="160dp"
                 android:layout_centerInParent="true"
-                app:rb_color="@color/white"
-                app:rb_duration="5000"
-                app:rb_radius="20dp"
-                app:rb_rippleAmount="3"
-                app:rb_scale="6">
+                tools:src="@drawable/ic_contact_picture" />
+        </com.skyfishjy.library.RippleBackground>
 
-                <ImageView
-                    android:id="@+id/contact_bubble"
-                    android:layout_width="160dp"
-                    android:layout_height="160dp"
-                    android:layout_centerInParent="true"
-                    tools:src="@drawable/ic_contact_picture" />
-            </com.skyfishjy.library.RippleBackground>
-
-            <LinearLayout
-                android:layout_width="300dp"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/ripple_animation"
-                android:layout_centerHorizontal="true"
-                android:layout_margin="10dp"
-                android:orientation="vertical">
-
-                <TextView
-                    android:id="@+id/contact_bubble_txt"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:singleLine="true"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
-                    android:textColor="@color/text_color_primary_dark"
-                    tools:text="Contact Name" />
-
-                <TextView
-                    android:id="@+id/contact_bubble_num_txt"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:ellipsize="end"
-                    android:paddingEnd="32dp"
-                    android:paddingStart="32dp"
-                    android:singleLine="true"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@color/text_color_secondary_dark"
-                    tools:text="ring:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
-
-                <TextView
-                    android:id="@+id/call_status_txt"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:textColor="@color/text_color_primary_dark"
-                    android:textSize="16sp"
-                    tools:text="Connecting" />
-
-            </LinearLayout>
-
-
-        </RelativeLayout>
-
-        <LinearLayout
-            android:layout_width="wrap_content"
+        <TextView
+            android:id="@+id/contact_bubble_txt"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="bottom|center_horizontal"
-            android:layout_margin="12dp"
-            android:orientation="horizontal">
+            android:gravity="center_horizontal"
+            android:ellipsize="middle"
+            android:paddingEnd="32dp"
+            android:paddingStart="32dp"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textColor="@color/text_color_primary_dark"
+            tools:text="Contact Name" />
 
-            <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"
-                app:backgroundTint="@color/error_red"
-                app:elevation="6dp"
-                app:pressedTranslationZ="12dp"
-                app:rippleColor="@android:color/white" />
+        <TextView
+            android:id="@+id/contact_bubble_num_txt"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="middle"
+            android:gravity="center_horizontal"
+            android:paddingEnd="32dp"
+            android:paddingStart="32dp"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/text_color_secondary_dark"
+            android:visibility="gone"
+            tools:text="ring:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            tools:visibility="visible" />
 
-            <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"
-                app:backgroundTint="#4caf50"
-                app:elevation="6dp"
-                app:pressedTranslationZ="12dp"
-                app:rippleColor="@android:color/white" />
-        </LinearLayout>
+        <TextView
+            android:id="@+id/call_status_txt"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:paddingEnd="32dp"
+            android:paddingStart="32dp"
+            android:textColor="@color/text_color_primary_dark"
+            android:textSize="16sp"
+            tools:text="Connecting" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/contact_bubble_layout"
+        android:layout_centerHorizontal="true"
+        android:orientation="horizontal">
 
         <android.support.design.widget.FloatingActionButton
-            android:id="@+id/call_hangup_btn"
+            android:id="@+id/call_refuse_btn"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="bottom|center_horizontal"
-            android:layout_margin="28dp"
+            android:layout_margin="16dp"
+            android:contentDescription="@string/action_call_decline"
             android:src="@drawable/ic_call_end_white"
-            android:visibility="gone"
             app:backgroundTint="@color/error_red"
             app:elevation="6dp"
             app:pressedTranslationZ="12dp"
             app:rippleColor="@android:color/white" />
 
-        <RelativeLayout
-            android:id="@+id/call_status_bar"
-            android:layout_width="match_parent"
-            android:layout_height="?android:attr/actionBarSize"
-            android:visibility="visible">
-
-            <ViewSwitcher
-                android:id="@+id/security_switcher"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
-                android:visibility="gone">
-
-                <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/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"
-                android:tint="#4caf50"
-                android:visibility="gone" />
-
-        </RelativeLayout>
-
-        <EditText
-            android:id="@+id/dialpad_edit_text"
-            android:layout_width="0dp"
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/call_accept_btn"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:ems="10"
-            android:inputType="phone"
-            android:visibility="visible" />
+            android:layout_margin="16dp"
+            android:contentDescription="@string/action_call_accept"
+            android:src="@drawable/ic_call_white"
+            app:backgroundTint="#4caf50"
+            app:elevation="6dp"
+            app:pressedTranslationZ="12dp"
+            app:rippleColor="@android:color/white" />
 
-    </FrameLayout>
-</FrameLayout>
+    </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_centerInParent="true"
+        android:layout_marginBottom="64dp"
+        android:src="@drawable/ic_call_end_white"
+        android:visibility="gone"
+        app:backgroundTint="@color/error_red"
+        app:elevation="6dp"
+        app:pressedTranslationZ="12dp"
+        app:rippleColor="@android:color/white"
+        tools:visibility="visible" />
+
+    <EditText
+        android:id="@+id/dialpad_edit_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ems="10"
+        android:inputType="phone"
+        android:visibility="visible" />
+
+</RelativeLayout>
diff --git a/ring-android/app/src/main/res/values/colors.xml b/ring-android/app/src/main/res/values/colors.xml
index d87852c..f9fae11 100644
--- a/ring-android/app/src/main/res/values/colors.xml
+++ b/ring-android/app/src/main/res/values/colors.xml
@@ -44,6 +44,7 @@
     <color name="holo_blue_bright">#ff00ddff</color>
 
     <color name="transparent_grey">#AACCCCCC</color>
+    <color name="transparent_dark_grey">#88424242</color>
 
     <color name="text_color_primary">@color/abc_primary_text_material_light</color>
     <color name="text_color_secondary">@color/abc_secondary_text_material_light</color>
diff --git a/ring-android/app/src/main/res/values/strings_call.xml b/ring-android/app/src/main/res/values/strings_call.xml
index b591eb0..e16a75a 100644
--- a/ring-android/app/src/main/res/values/strings_call.xml
+++ b/ring-android/app/src/main/res/values/strings_call.xml
@@ -22,9 +22,9 @@
 -->
 <resources>
     <!-- SipCalls -->
-    <string name="call_human_state_incoming">Ringing</string>
+    <string name="call_human_state_incoming">Incoming</string>
     <string name="call_human_state_connecting">Connecting</string>
-    <string name="call_human_state_ringing">Calling</string>
+    <string name="call_human_state_ringing">Ringing</string>
     <string name="call_human_state_current">Talking</string>
     <string name="call_human_state_hungup">Over</string>
     <string name="call_human_state_busy">Busy</string>
diff --git a/ring-android/app/src/main/res/values/styles.xml b/ring-android/app/src/main/res/values/styles.xml
index 183a86e..56a56c5 100644
--- a/ring-android/app/src/main/res/values/styles.xml
+++ b/ring-android/app/src/main/res/values/styles.xml
@@ -15,9 +15,9 @@
     </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:background">@color/transparent_dark_grey</item>
         <item name="android:height">@dimen/abc_action_bar_default_height_material</item>
-        <item name="background">@android:color/transparent</item>
+        <item name="background">@color/transparent_dark_grey</item>
     </style>
 
     <style name="ToolbarTitle" parent="@style/TextAppearance.Widget.AppCompat.Toolbar.Title">