From 37fb52e3c1ba5ccb53e9843ef760935546d03bd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Fri, 2 Oct 2020 19:24:31 -0400
Subject: [PATCH] prefs: display only available resolutions

Change-Id: I7eff9dfd8ff78b132cdf38be120f43ad3e8c3fcb
Gitlab: #747
---
 .../cx/ring/application/JamiApplication.java  |  11 +-
 .../fragments/GeneralAccountFragment.java     |  11 +-
 .../fragments/GeneralAccountPresenter.java    |  24 ++-
 .../cx/ring/fragments/GeneralAccountView.java |   3 +
 .../java/cx/ring/services/CameraService.java  | 147 ++++++++++++------
 .../cx/ring/services/HardwareServiceImpl.java |   9 +-
 .../SharedPreferencesServiceImpl.java         |   6 +-
 .../cx/ring/settings/SettingsFragment.java    |  54 ++-----
 .../ring/settings/VideoSettingsFragment.java  |  80 ++++------
 .../ring/tv/account/TVSettingsFragment.java   |  38 ++++-
 .../main/res/xml/tv_account_general_pref.xml  |   4 +-
 .../account/ProfileCreationPresenter.java     |   6 +-
 .../main/java/cx/ring/call/CallPresenter.java |   4 +-
 .../conversation/ConversationPresenter.java   |   6 +-
 .../navigation/HomeNavigationPresenter.java   |   4 +-
 .../cx/ring/services/HardwareService.java     |   2 +
 16 files changed, 241 insertions(+), 168 deletions(-)

diff --git a/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java b/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
index cb766c934..270307fd5 100644
--- a/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
+++ b/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
@@ -34,6 +34,8 @@ import android.os.IBinder;
 import android.system.Os;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.bumptech.glide.Glide;
 
 import java.io.File;
@@ -45,9 +47,6 @@ import java.util.concurrent.ScheduledExecutorService;
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import androidx.annotation.RequiresApi;
-import androidx.core.content.ContextCompat;
-
 import cx.ring.BuildConfig;
 import cx.ring.R;
 import cx.ring.contacts.AvatarFactory;
@@ -155,10 +154,12 @@ public abstract class JamiApplication extends Application {
                 mDaemonService.startDaemon();
 
                 // Check if the camera hardware feature is available.
-                if (mDeviceRuntimeService.hasVideoPermission() && mHardwareService.isVideoAvailable()) {
+                if (mDeviceRuntimeService.hasVideoPermission()) {
                     //initVideo is called here to give time to the application to initialize hardware cameras
                     Log.d(TAG, "bootstrapDaemon: At least one camera available. Initializing video...");
-                    mHardwareService.initVideo().subscribe();
+                    mHardwareService.initVideo()
+                            .onErrorComplete()
+                            .subscribe();
                 } else {
                     Log.d(TAG, "bootstrapDaemon: No camera available");
                 }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
index 354d6a3a6..33bf6a2eb 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
@@ -22,6 +22,9 @@ package cx.ring.fragments;
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
+import android.text.format.Formatter;
+import android.view.inputmethod.EditorInfo;
+
 import androidx.annotation.NonNull;
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.EditTextPreference;
@@ -31,9 +34,6 @@ import androidx.preference.SeekBarPreference;
 import androidx.preference.SwitchPreference;
 import androidx.preference.TwoStatePreference;
 
-import android.text.format.Formatter;
-import android.view.inputmethod.EditorInfo;
-
 import cx.ring.R;
 import cx.ring.account.AccountEditionFragment;
 import cx.ring.application.JamiApplication;
@@ -43,6 +43,7 @@ import cx.ring.model.ConfigKey;
 import cx.ring.mvp.BasePreferenceFragment;
 import cx.ring.services.SharedPreferencesServiceImpl;
 import cx.ring.utils.Log;
+import cx.ring.utils.Tuple;
 import cx.ring.views.EditTextIntegerPreference;
 import cx.ring.views.EditTextPreferenceDialog;
 import cx.ring.views.PasswordPreference;
@@ -131,6 +132,10 @@ public class GeneralAccountFragment extends BasePreferenceFragment<GeneralAccoun
             activity.onBackPressed();
     }
 
+    @Override
+    public void updateResolutions(Tuple<Integer, Integer> maxResolution, int currentResolution) {
+    }
+
     private CharSequence getFileSizeSummary(int size, int maxSize) {
         if (size == 0)  {
             return getText(R.string.account_accept_files_never);
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
index c3da20bdb..0872ba0a3 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
@@ -29,6 +29,8 @@ import cx.ring.model.Account;
 import cx.ring.model.ConfigKey;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.services.AccountService;
+import cx.ring.services.HardwareService;
+import cx.ring.services.PreferencesService;
 import io.reactivex.Scheduler;
 
 public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> {
@@ -37,14 +39,20 @@ public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> {
 
     protected AccountService mAccountService;
 
+    protected HardwareService mHardwareService;
+
+    protected PreferencesService mPreferenceService;
+
     private Account mAccount;
     @Inject
     @Named("UiScheduler")
     protected Scheduler mUiScheduler;
 
     @Inject
-    GeneralAccountPresenter(AccountService accountService) {
+    GeneralAccountPresenter(AccountService accountService, HardwareService hardwareService, PreferencesService preferencesService) {
         this.mAccountService = accountService;
+        this.mHardwareService = hardwareService;
+        this.mPreferenceService = preferencesService;
     }
 
     // Init with current account
@@ -57,7 +65,9 @@ public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> {
     }
 
     private void init(Account account) {
+        mCompositeDisposable.clear();
         mAccount = account;
+
         if (account != null) {
             if (account.isJami()) {
                 getView().addJamiPreferences(account.getAccountID());
@@ -65,10 +75,20 @@ public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> {
                 getView().addSipPreferences();
             }
             getView().accountChanged(account);
-            mCompositeDisposable.clear();
             mCompositeDisposable.add(mAccountService.getObservableAccount(account.getAccountID())
                     .observeOn(mUiScheduler)
                     .subscribe(acc -> getView().accountChanged(acc)));
+
+            mCompositeDisposable.add(mHardwareService.getMaxResolutions()
+                    .observeOn(mUiScheduler)
+                    .subscribe(res -> {
+                        if (res.first == null) {
+                            getView().updateResolutions(null, mPreferenceService.getResolution());
+                        } else {
+                            getView().updateResolutions(res, mPreferenceService.getResolution());
+                        }
+                    },
+                    e -> getView().updateResolutions(null, mPreferenceService.getResolution())));
         } else {
             Log.e(TAG, "init: No currentAccount available");
             getView().finish();
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java
index f0825873f..62fd54e6f 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java
@@ -22,6 +22,7 @@ package cx.ring.fragments;
 import androidx.annotation.NonNull;
 
 import cx.ring.model.Account;
+import cx.ring.utils.Tuple;
 
 public interface GeneralAccountView {
 
@@ -32,4 +33,6 @@ public interface GeneralAccountView {
     void accountChanged(@NonNull Account account);
 
     void finish();
+
+    void updateResolutions(Tuple<Integer, Integer> maxResolution, int currentResolution);
 }
diff --git a/ring-android/app/src/main/java/cx/ring/services/CameraService.java b/ring-android/app/src/main/java/cx/ring/services/CameraService.java
index 538c6a4d5..210f6c1d5 100644
--- a/ring-android/app/src/main/java/cx/ring/services/CameraService.java
+++ b/ring-android/app/src/main/java/cx/ring/services/CameraService.java
@@ -23,7 +23,6 @@ import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -46,6 +45,7 @@ import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
@@ -58,7 +58,6 @@ import androidx.annotation.RequiresApi;
 import androidx.core.util.Pair;
 
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -73,13 +72,15 @@ import cx.ring.daemon.Ringservice;
 import cx.ring.daemon.RingserviceJNI;
 import cx.ring.daemon.StringMap;
 import cx.ring.daemon.UintVect;
-import cx.ring.utils.Log;
+import cx.ring.utils.Tuple;
 import cx.ring.views.AutoFitTextureView;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Observable;
 import io.reactivex.Single;
 import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.subjects.BehaviorSubject;
+import io.reactivex.subjects.Subject;
 
 public class CameraService {
     private static final String TAG = CameraService.class.getSimpleName();
@@ -98,22 +99,40 @@ public class CameraService {
     private MediaCodec currentCodec;
     // SPS and PPS NALs (Config Data).
     private ByteBuffer codecData = null;
+    private static final Tuple<Integer, Integer> RESOLUTION_NONE = new Tuple<>(null, null);
+    private final Subject<Tuple<Integer, Integer>> maxResolutionSubject = BehaviorSubject.createDefault(RESOLUTION_NONE);
 
-    /*private final CameraManager.AvailabilityCallback availabilityCallback = new CameraManager.AvailabilityCallback() {
+    protected VideoDevices devices = null;
+    private VideoParams previewParams = null;
+
+    private final CameraManager.AvailabilityCallback availabilityCallback = new CameraManager.AvailabilityCallback() {
         @Override
         public void onCameraAvailable(@NonNull String cameraId) {
-            init()
-                .onErrorComplete()
-                .subscribe();
+            Log.w(TAG, "onCameraAvailable " + cameraId);
+            filterCompatibleCamera(Observable.just(cameraId), manager).blockingSubscribe(camera -> {
+                synchronized (addedDevices) {
+                    if (addedDevices.add(camera.first)) {
+                        if (!devices.cameras.contains(camera.first))
+                            devices.cameras.add(camera.first);
+                        RingserviceJNI.addVideoDevice(camera.first);
+                    }
+                }
+            });
         }
 
         @Override
         public void onCameraUnavailable(@NonNull String cameraId) {
-            init()
-                .onErrorComplete()
-                .subscribe();
+            if (devices == null || devices.getCurrentId() == null || !devices.getCurrentId().equals(cameraId)) {
+                synchronized (addedDevices) {
+                    if (addedDevices.remove(cameraId)) {
+                        Log.w(TAG, "onCameraUnavailable " + cameraId + " current:" + previewCamera);
+                        devices.cameras.remove(cameraId);
+                        RingserviceJNI.removeVideoDevice(cameraId);
+                    }
+                }
+            }
         }
-    };*/
+    };
 
     CameraService(@NonNull Context c) {
         manager = (CameraManager) c.getSystemService(Context.CAMERA_SERVICE);
@@ -130,6 +149,10 @@ public class CameraService {
         return videoHandler;
     }
 
+    public Observable<Tuple<Integer, Integer>> getMaxResolutions() {
+        return maxResolutionSubject;
+    }
+
     static class VideoDevices {
         final List<String> cameras = new ArrayList<>();
         String currentId;
@@ -148,11 +171,11 @@ public class CameraService {
             }
             return currentId;
         }
-    }
-
-    protected VideoDevices devices = null;
-    private VideoParams previewParams = null;
 
+        public String getCurrentId() {
+            return currentId;
+        }
+    }
     public String switchInput(boolean setDefaultCamera) {
         if (devices == null)
             return null;
@@ -160,11 +183,14 @@ public class CameraService {
     }
 
     public VideoParams getParams(String camId) {
+        Log.w(TAG, "getParams()" + camId);
         if (camId != null) {
             return mParams.get(camId);
         } else if (previewParams != null) {
+            Log.w(TAG, "getParams() previewParams");
             return previewParams;
         } else if (devices != null && !devices.cameras.isEmpty()) {
+            Log.w(TAG, "getParams() fallback");
             devices.currentId = devices.cameras.get(0);
             return mParams.get(devices.currentId);
         }
@@ -176,13 +202,16 @@ public class CameraService {
     }
 
     public void setParameters(String camId, int format, int width, int height, int rate, int rotation) {
+        Log.w(TAG, "setParameters() " + camId + " " + format + " " + width + " " + height + " " + rate + " " + rotation);
         DeviceParams deviceParams = mNativeParams.get(camId);
-        if (deviceParams == null)
+        if (deviceParams == null) {
+            Log.w(TAG, "setParameters() can't find device");
             return;
+        }
 
-        CameraService.VideoParams params = mParams.get(camId);
+        VideoParams params = mParams.get(camId);
         if (params == null) {
-            params = new CameraService.VideoParams(camId, format, deviceParams.size.x, deviceParams.size.y, rate);
+            params = new VideoParams(camId, format, deviceParams.size.x, deviceParams.size.y, rate);
             mParams.put(camId, params);
         } else {
             params.id = camId;
@@ -191,25 +220,25 @@ public class CameraService {
             params.height = deviceParams.size.y;
             params.rate = rate;
         }
-        if (deviceParams.infos != null) {
-            params.rotation = getCameraDisplayRotation(deviceParams, rotation);
-        }
+        params.rotation = getCameraDisplayRotation(deviceParams, rotation);
         int r = params.rotation;
-        getVideoHandler().post(() -> Ringservice.setDeviceOrientation(camId, r));
+        videoHandler.post(() -> Ringservice.setDeviceOrientation(camId, r));
     }
 
     public void setOrientation(int rotation) {
+        Log.w(TAG, "setOrientation() " + rotation);
         for (String id : getCameraIds())
             setDeviceOrientation(id, rotation);
     }
 
     private void setDeviceOrientation(String camId, int screenRotation) {
+        Log.w(TAG, "setDeviceOrientation() " + camId + " " + screenRotation);
         DeviceParams deviceParams = mNativeParams.get(camId);
         int rotation = 0;
-        if (deviceParams != null && deviceParams.infos != null) {
+        if (deviceParams != null) {
             rotation = getCameraDisplayRotation(deviceParams, screenRotation);
         }
-        CameraService.VideoParams params = mParams.get(camId);
+        VideoParams params = mParams.get(camId);
         if (params != null) {
             params.rotation = rotation;
         }
@@ -217,12 +246,12 @@ public class CameraService {
     }
 
     private static int getCameraDisplayRotation(DeviceParams device, int screenRotation) {
-        return getCameraDisplayRotation(device.infos.orientation, rotationToDegrees(screenRotation), device.infos.facing);
+        return getCameraDisplayRotation(device.orientation, rotationToDegrees(screenRotation), device.facing);
     }
 
     private static int getCameraDisplayRotation(int sensorOrientation, int screenOrientation, int cameraFacing) {
         int rotation = 0;
-        if (cameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+        if (cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {
             rotation = (sensorOrientation + screenOrientation + 360) % 360;
         } else {
             rotation = (sensorOrientation - screenOrientation + 360) % 360;
@@ -233,18 +262,14 @@ public class CameraService {
 
     public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates, Point minVideoSize) {
         Log.d(TAG, "getCameraInfo: " + camId + " min. size: " + minVideoSize);
-        DeviceParams p = new CameraService.DeviceParams();
+        DeviceParams p = new DeviceParams();
         p.size = new Point(0, 0);
-        p.infos = new Camera.CameraInfo();
-
+        p.maxSize = new Point(0, 0);
         rates.clear();
-
         fillCameraInfo(p, camId, formats, sizes, rates, minVideoSize);
         sizes.add((long) p.size.x);
         sizes.add((long) p.size.y);
-        sizes.add((long) p.size.y);
-        sizes.add((long) p.size.x);
-
+        Log.d(TAG, "getCameraInfo: " + camId + " max. size: " + p.maxSize + " size:" + p.size);
         mNativeParams.put(camId, p);
     }
 
@@ -252,6 +277,15 @@ public class CameraService {
         return mNativeParams.get(camId);
     }
 
+    private Point getMaxResolution() {
+        Point max = null;
+        for (DeviceParams deviceParams : mNativeParams.values()) {
+            if (max == null || max.x * max.y < deviceParams.maxSize.x * deviceParams.maxSize.y)
+                max = deviceParams.maxSize;
+        }
+        return max;
+    }
+
     public boolean isPreviewFromFrontCamera() {
         return mNativeParams.size() == 1 || (devices != null && devices.currentId != null && devices.currentId.equals(devices.cameraFront));
     }
@@ -305,8 +339,10 @@ public class CameraService {
 
     static class DeviceParams {
         Point size;
+        Point maxSize;
         long rate;
-        Camera.CameraInfo infos;
+        int facing;
+        int orientation;
 
         StringMap toMap() {
             StringMap map = new StringMap();
@@ -316,9 +352,8 @@ public class CameraService {
         }
     }
 
-    static private Observable<Pair<String, CameraCharacteristics>> filterCompatibleCamera(String[] cameraIds, CameraManager cameraManager) {
-        return Observable.fromArray(cameraIds)
-                .map(id -> new Pair<>(id, cameraManager.getCameraCharacteristics(id)))
+    static private Observable<Pair<String, CameraCharacteristics>> filterCompatibleCamera(Observable<String> cameras, CameraManager cameraManager) {
+        return cameras.map(id -> new Pair<>(id, cameraManager.getCameraCharacteristics(id)))
                 .filter(camera -> {
                     try {
                         for (int c : camera.second.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
@@ -340,7 +375,7 @@ public class CameraService {
     private Single<VideoDevices> loadDevices(CameraManager manager) {
         return Single.fromCallable(() -> {
             VideoDevices devices = new VideoDevices();
-            List<Pair<String, CameraCharacteristics>> cameras = filterCompatibleCamera(manager.getCameraIdList(), manager).toList().blockingGet();
+            List<Pair<String, CameraCharacteristics>> cameras = filterCompatibleCamera(Observable.fromArray(manager.getCameraIdList()), manager).toList().blockingGet();
             Maybe<String> backCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_BACK).firstElement();
             Maybe<String> frontCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_FRONT).firstElement();
             Observable<String> externalCameras;
@@ -362,6 +397,7 @@ public class CameraService {
     }
 
     Completable init() {
+        boolean resetCamera = false;
         if (manager == null)
             return Completable.error(new IllegalStateException("Video manager not available"));
         return loadDevices(manager)
@@ -372,7 +408,7 @@ public class CameraService {
                         // Removed devices
                         if (old != null) {
                             for (String oldId : old.cameras) {
-                                if (!devs.cameras.contains(oldId)) {
+                                if (!devs.cameras.contains(oldId) || resetCamera) {
                                     if (addedDevices.remove(oldId))
                                         RingserviceJNI.removeVideoDevice(oldId);
                                 }
@@ -380,6 +416,7 @@ public class CameraService {
                         }
                         // Added devices
                         for (String camera : devs.cameras) {
+                            Log.w(TAG, "RingserviceJNI.addVideoDevice init " + camera);
                             if (addedDevices.add(camera))
                                 RingserviceJNI.addVideoDevice(camera);
                         }
@@ -391,8 +428,16 @@ public class CameraService {
                     return devs;
                 })
                 .ignoreElement()
-                .doOnError(e -> Log.e(TAG, "Error initializing video device", e))
-                //.doOnComplete(() -> manager.registerAvailabilityCallback(availabilityCallback, getVideoHandler()))
+                .doOnError(e -> {
+                    Log.e(TAG, "Error initializing video device", e);
+                    maxResolutionSubject.onNext(RESOLUTION_NONE);
+                })
+                .doOnComplete(() -> {
+                    Point max = getMaxResolution();
+                    Log.w(TAG, "Found max resolution: " + max);
+                    maxResolutionSubject.onNext(max == null ? RESOLUTION_NONE : new Tuple<>(max.x, max.y));
+                    manager.registerAvailabilityCallback(availabilityCallback, getVideoHandler());
+                })
                 .onErrorComplete();
     }
 
@@ -641,7 +686,7 @@ public class CameraService {
         } else if (notBigEnough.size() > 0) {
             return Collections.max(notBigEnough, new CompareSizesByArea());
         } else {
-            android.util.Log.e(TAG, "Couldn't find any suitable preview size");
+            Log.e(TAG, "Couldn't find any suitable preview size");
             return choices[0];
         }
     }
@@ -676,7 +721,7 @@ public class CameraService {
 
         Pair<MediaCodec, Surface> r = null;
         while (screenWidth >= 320) {
-            CameraService.VideoParams params = new CameraService.VideoParams(null, 0, screenWidth, screenHeight, 24);
+            VideoParams params = new VideoParams(null, 0, screenWidth, screenHeight, 24);
             r = openEncoder(params, MediaFormat.MIMETYPE_VIDEO_AVC, handler, 720, 0);
             if (r.first == null) {
                 screenWidth /= 2;
@@ -684,6 +729,8 @@ public class CameraService {
             } else
                 break;
         }
+        if (r == null)
+            return null;
 
         final Surface surface = r.second;
         final MediaCodec codec = r.first;
@@ -945,6 +992,10 @@ public class CameraService {
                             && newSize.getWidth() < minVideoSize.x
                                     ? s.getWidth() > newSize.getWidth()
                                     : (s.getWidth() >= minVideoSize.x && s.getWidth() < newSize.getWidth()))) {
+                    if (s.getWidth() * s.getHeight() > p.maxSize.x * p.maxSize.y) {
+                        p.maxSize.x = s.getWidth();
+                        p.maxSize.y = s.getHeight();
+                    }
                     newSize = s;
                 }
             }
@@ -956,10 +1007,8 @@ public class CameraService {
             long fps = (long) maxfps;
             rates.add(fps);
             p.rate = fps;
-
-            int facing = cc.get(CameraCharacteristics.LENS_FACING);
-            p.infos.orientation = cc.get(CameraCharacteristics.SENSOR_ORIENTATION);
-            p.infos.facing = facing == CameraCharacteristics.LENS_FACING_FRONT ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
+            p.orientation = cc.get(CameraCharacteristics.SENSOR_ORIENTATION);
+            p.facing = cc.get(CameraCharacteristics.LENS_FACING);
         } catch (Exception e) {
             Log.e(TAG, "An error occurred getting camera info", e);
         }
@@ -992,7 +1041,7 @@ public class CameraService {
     }
 
     public void unregisterCameraDetectionCallback() {
-        /*if (manager != null && availabilityCallback != null)
-            manager.unregisterAvailabilityCallback(availabilityCallback);*/
+        if (manager != null)
+            manager.unregisterAvailabilityCallback(availabilityCallback);
     }
 }
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
index a7d879331..b734c1df9 100644
--- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
@@ -24,7 +24,6 @@ import android.bluetooth.BluetoothHeadset;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
-import android.hardware.camera2.CameraManager;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaRecorder;
@@ -34,7 +33,6 @@ import android.view.SurfaceHolder;
 import android.view.TextureView;
 import android.view.WindowManager;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.media.AudioAttributesCompat;
 import androidx.media.AudioFocusRequestCompat;
@@ -57,7 +55,9 @@ import cx.ring.model.SipCall.CallStatus;
 import cx.ring.utils.BluetoothWrapper;
 import cx.ring.utils.Log;
 import cx.ring.utils.Ringer;
+import cx.ring.utils.Tuple;
 import io.reactivex.Completable;
+import io.reactivex.Observable;
 
 import static cx.ring.daemon.Ringservice.getCallMediaHandlerStatus;
 import static cx.ring.daemon.RingserviceJNI.toggleCallMediaHandler;
@@ -107,6 +107,10 @@ public class HardwareServiceImpl extends HardwareService implements AudioManager
         return cameraService.init();
     }
 
+    public Observable<Tuple<Integer, Integer>> getMaxResolutions() {
+        return cameraService.getMaxResolutions();
+    }
+
     public boolean isVideoAvailable() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) || cameraService.hasCamera();
     }
@@ -426,7 +430,6 @@ public class HardwareServiceImpl extends HardwareService implements AudioManager
             minVideoSize = parseResolution(mPreferenceService.getResolution());
         else
             minVideoSize = VIDEO_SIZE_LOW;
-
         cameraService.getCameraInfo(camId, formats, sizes, rates, minVideoSize);
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
index db1230792..5e9187fb8 100644
--- a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
@@ -22,13 +22,13 @@ package cx.ring.services;
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.os.Build;
+import android.text.TextUtils;
+
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatDelegate;
 import androidx.preference.PreferenceManager;
 
-import android.os.Build;
-import android.text.TextUtils;
-
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
diff --git a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
index a2cdaf76e..6be87a204 100644
--- a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
@@ -20,18 +20,13 @@
 package cx.ring.settings;
 
 import android.app.Activity;
-import android.graphics.Typeface;
 import android.os.Bundle;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.appcompat.widget.Toolbar;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
 
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import com.google.android.material.snackbar.Snackbar;
 
-import android.os.Bundle;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -40,32 +35,17 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-import androidx.preference.PreferenceManager;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.snackbar.Snackbar;
-
 import cx.ring.R;
 import cx.ring.application.JamiApplication;
 import cx.ring.client.HomeActivity;
+import cx.ring.daemon.Ringservice;
 import cx.ring.databinding.FragSettingsBinding;
 import cx.ring.model.Settings;
 import cx.ring.mvp.BaseSupportFragment;
 import cx.ring.mvp.GenericView;
-import cx.ring.utils.DeviceUtils;
-
-import static cx.ring.daemon.Ringservice.getPluginsEnabled;
-import static cx.ring.daemon.Ringservice.setPluginsEnabled;
 
-/**
- * TODO: improvements : handle multiples permissions for feature.
- */
 public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> implements GenericView<Settings>, ViewTreeObserver.OnScrollChangedListener {
 
     private static final int SCROLL_DIRECTION_UP = -1;
@@ -79,7 +59,7 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         binding = FragSettingsBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
+        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
@@ -94,17 +74,17 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
         setHasOptionsMenu(true);
         super.onViewCreated(view, savedInstanceState);
         binding.settingsDarkTheme.setChecked(presenter.getDarkMode());
-        binding.settingsPluginsSwitch.setChecked(getPluginsEnabled());
+        binding.settingsPluginsSwitch.setChecked(Ringservice.getPluginsEnabled());
         if (TextUtils.isEmpty(JamiApplication.getInstance().getPushToken())) {
             binding.settingsPushNotificationsLayout.setVisibility(View.GONE);
         }
         // loading preferences
         presenter.loadSettings();
-        ((HomeActivity) getActivity()).setToolbarTitle(R.string.menu_item_settings);
+        ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_settings);
 
         binding.scrollview.getViewTreeObserver().addOnScrollChangedListener(this);
         binding.settingsDarkTheme.setOnCheckedChangeListener((buttonView, isChecked) -> presenter.setDarkMode(isChecked));
-        binding.settingsPluginsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> setPluginsEnabled(isChecked));
+        binding.settingsPluginsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> Ringservice.setPluginsEnabled(isChecked));
 
         CompoundButton.OnCheckedChangeListener save = (buttonView, isChecked) -> {
             if (!mIsRefreshingViewFromPresenter)
@@ -136,7 +116,7 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
                 .show());
         binding.settingsPluginsLayout.setOnClickListener(v -> {
             HomeActivity activity = (HomeActivity) getActivity();
-            if (activity != null && getPluginsEnabled()){
+            if (activity != null && Ringservice.getPluginsEnabled()){
                 activity.goToPluginsListSettings();
             }
         });
@@ -145,7 +125,7 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
     @Override
     public void onResume() {
         super.onResume();
-        ((HomeActivity) getActivity()).setToolbarTitle(R.string.menu_item_settings);
+        ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_settings);
     }
 
     @Override
@@ -168,28 +148,12 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
         presenter.saveSettings(newSettings);
     }
 
-    /**
-     * Presents a Toast explaining why the Read Contacts permission is required to display the devi-
-     * ces contacts in Ring.
-     */
     private void presentReadContactPermissionExplanationToast() {
-        Activity activity = getActivity();
-        if (null != activity) {
-            String toastMessage = getString(R.string.permission_dialog_read_contacts_message);
-            Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show();
-        }
+        Toast.makeText(requireContext(), getString(R.string.permission_dialog_read_contacts_message), Toast.LENGTH_LONG).show();
     }
 
-    /**
-     * Presents a Toast explaining why the Write Call Log permission is required to enable the cor-
-     * responding feature.
-     */
     private void presentWriteCallLogPermissionExplanationToast() {
-        Activity activity = getActivity();
-        if (null != activity) {
-            String toastMessage = getString(R.string.permission_dialog_write_call_log_message);
-            Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show();
-        }
+        Toast.makeText(requireContext(), getString(R.string.permission_dialog_write_call_log_message), Toast.LENGTH_LONG).show();
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/settings/VideoSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/VideoSettingsFragment.java
index cd1f2fc5f..0278563ac 100644
--- a/ring-android/app/src/main/java/cx/ring/settings/VideoSettingsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/settings/VideoSettingsFragment.java
@@ -1,19 +1,25 @@
+/*
+ *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
 package cx.ring.settings;
 
 import android.content.Context;
-import android.graphics.Typeface;
 import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.Toolbar;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
@@ -21,11 +27,8 @@ import androidx.preference.PreferenceManager;
 import cx.ring.R;
 import cx.ring.client.HomeActivity;
 import cx.ring.services.SharedPreferencesServiceImpl;
-import cx.ring.utils.DeviceUtils;
 
 public class VideoSettingsFragment extends PreferenceFragmentCompat {
-    private static final String TAG = VideoSettingsFragment.class.getName();
-
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         PreferenceManager pm = getPreferenceManager();
@@ -34,40 +37,25 @@ public class VideoSettingsFragment extends PreferenceFragmentCompat {
 
         setPreferencesFromResource(R.xml.video_prefs, rootKey);
         Preference resolutionPref = findPreference(SharedPreferencesServiceImpl.PREF_RESOLUTION);
-        handleResolutionIcon(pm.getSharedPreferences().getString(SharedPreferencesServiceImpl.PREF_RESOLUTION, "720"));
-        resolutionPref.setOnPreferenceChangeListener((preference, newValue) -> {
-            Log.w(TAG, "onPreferenceChange " + preference.getKey() + " " + newValue);
-            handleResolutionIcon((String) newValue);
-            return true;
-        });
-    }
-
-    private void handleResolutionIcon(String resolution) {
-        if (resolution.equals("480"))
-            getPreferenceScreen().findPreference(SharedPreferencesServiceImpl.PREF_RESOLUTION).setIcon(R.drawable.baseline_videocam_24);
-        else
-            getPreferenceScreen().findPreference(SharedPreferencesServiceImpl.PREF_RESOLUTION).setIcon(R.drawable.baseline_hd_24);
+        if (resolutionPref != null) {
+            handleResolutionIcon(resolutionPref, pm.getSharedPreferences().getString(SharedPreferencesServiceImpl.PREF_RESOLUTION, "720"));
+            resolutionPref.setOnPreferenceChangeListener((preference, newValue) -> {
+                handleResolutionIcon(preference, (String) newValue);
+                return true;
+            });
+        }
     }
 
     @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        if (DeviceUtils.isTablet(getContext())) {
-            Toolbar toolbar = getActivity().findViewById(R.id.main_toolbar);
-            TextView title = toolbar.findViewById(R.id.contact_title);
-            ImageView logo = toolbar.findViewById(R.id.contact_image);
-
-            logo.setVisibility(View.GONE);
-            title.setText(R.string.menu_item_settings);
-            title.setTextSize(19);
-            title.setTypeface(null, Typeface.BOLD);
+    public void onResume() {
+        super.onResume();
+        ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_settings);
+    }
 
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
-            params.removeRule(RelativeLayout.ALIGN_TOP);
-            params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
-            title.setLayoutParams(params);
-        } else {
-            ((HomeActivity) requireActivity()).setToolbarState(R.string.menu_item_settings);
-        }
-        return super.onCreateView(inflater, container, savedInstanceState);
+    private static void handleResolutionIcon(Preference resolutionPref, String resolution) {
+        if (resolution == null || resolution.equals("480"))
+            resolutionPref.setIcon(R.drawable.baseline_videocam_24);
+        else
+            resolutionPref.setIcon(R.drawable.baseline_hd_24);
     }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java
index 757775067..70a7d862d 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java
@@ -20,18 +20,22 @@
 package cx.ring.tv.account;
 
 import android.content.Context;
+
 import android.os.Bundle;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 import androidx.leanback.preference.LeanbackSettingsFragmentCompat;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
-import androidx.preference.SwitchPreference;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
 
-import android.view.View;
+import java.util.ArrayList;
+import java.util.Arrays;
 
 import cx.ring.R;
 import cx.ring.application.JamiApplication;
@@ -40,6 +44,7 @@ import cx.ring.fragments.GeneralAccountView;
 import cx.ring.model.Account;
 import cx.ring.model.ConfigKey;
 import cx.ring.services.SharedPreferencesServiceImpl;
+import cx.ring.utils.Tuple;
 
 public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
 
@@ -108,6 +113,18 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
             requireActivity().onBackPressed();
         }
 
+        @Override
+        public void updateResolutions(Tuple<Integer, Integer> maxResolution, int currentResolution) {
+            String[] videoResolutionsNames = getResources().getStringArray(R.array.video_resolutionStrings);
+            String[] videoResolutionsValues = filterResolutions(getResources().getStringArray(R.array.video_resolutions), currentResolution, maxResolution);
+
+            ListPreference lpVideoResolution = findPreference(SharedPreferencesServiceImpl.PREF_RESOLUTION);
+            if (lpVideoResolution != null) {
+                lpVideoResolution.setEntries(Arrays.copyOfRange(videoResolutionsNames, 0, videoResolutionsValues.length));
+                lpVideoResolution.setEntryValues(videoResolutionsValues);
+            }
+        }
+
         @Override
         public void onCreatePreferences(Bundle bundle, String rootKey) {
             PreferenceManager pm = getPreferenceManager();
@@ -116,6 +133,21 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
             setPreferencesFromResource(R.xml.tv_account_general_pref, rootKey);
         }
 
+        private String[] filterResolutions(String[] videoResolutionsValues, int currentResolution, Tuple<Integer, Integer> maxResolution) {
+            if (maxResolution == null) return videoResolutionsValues;
+            if (currentResolution > maxResolution.second) return videoResolutionsValues;
+
+            ArrayList<String> resolutions = new ArrayList<>();
+            for (String videoResolutionsValue : videoResolutionsValues) {
+                int resolutionValueInt = Integer.parseInt(videoResolutionsValue);
+                if (resolutionValueInt <= maxResolution.second) {
+                    resolutions.add(videoResolutionsValue);
+                }
+            }
+
+            return resolutions.toArray(new String[0]);
+        }
+
         @Override
         public boolean onPreferenceTreeClick(Preference preference) {
             if (preference.getKey().equals(ConfigKey.ACCOUNT_AUTOANSWER.key())) {
diff --git a/ring-android/app/src/main/res/xml/tv_account_general_pref.xml b/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
index 9f46c4b9f..4b084a2d6 100644
--- a/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
+++ b/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
@@ -37,10 +37,10 @@
 
         <ListPreference
             android:defaultValue="@string/video_resolution_default_tv"
-            android:entries="@array/video_resolutionStrings"
-            android:entryValues="@array/video_resolutions"
             android:icon="@drawable/baseline_hd_24"
             android:key="video_resolution"
+            android:entries="@array/video_resolutionStrings"
+            android:entryValues="@array/video_resolutions"
             android:summary="@string/pref_videoResolution_summary"
             android:title="@string/pref_videoResolution_title" />
     </PreferenceCategory>
diff --git a/ring-android/libringclient/src/main/java/cx/ring/account/ProfileCreationPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/account/ProfileCreationPresenter.java
index 555f4e3ae..2396a9f25 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/account/ProfileCreationPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/account/ProfileCreationPresenter.java
@@ -21,13 +21,11 @@ package cx.ring.account;
 
 import javax.inject.Inject;
 
-import cx.ring.model.Account;
 import cx.ring.mvp.AccountCreationModel;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.services.DeviceRuntimeService;
 import cx.ring.services.HardwareService;
 import cx.ring.utils.Log;
-import io.reactivex.Observable;
 import io.reactivex.Scheduler;
 import io.reactivex.Single;
 
@@ -101,7 +99,9 @@ public class ProfileCreationPresenter extends RootPresenter<ProfileCreationView>
 
     public void cameraPermissionChanged(boolean isGranted) {
         if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo().subscribe();
+            mHardwareService.initVideo()
+                    .onErrorComplete()
+                    .subscribe();
         }
     }
 
diff --git a/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
index 5fc46996d..f67d997c0 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
@@ -103,7 +103,9 @@ public class CallPresenter extends RootPresenter<CallView> {
 
     public void cameraPermissionChanged(boolean isGranted) {
         if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo().blockingAwait();
+            mHardwareService.initVideo()
+                    .onErrorComplete()
+                    .blockingAwait();
             permissionChanged = true;
         }
     }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
index 8a6bd3b28..53aa2a2d6 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/conversation/ConversationPresenter.java
@@ -32,8 +32,8 @@ import cx.ring.model.CallContact;
 import cx.ring.model.Conference;
 import cx.ring.model.Conversation;
 import cx.ring.model.DataTransfer;
-import cx.ring.model.Interaction;
 import cx.ring.model.Error;
+import cx.ring.model.Interaction;
 import cx.ring.model.SipCall;
 import cx.ring.model.TrustRequest;
 import cx.ring.model.Uri;
@@ -402,7 +402,9 @@ public class ConversationPresenter extends RootPresenter<ConversationView> {
 
     public void cameraPermissionChanged(boolean isGranted) {
         if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo().subscribe();
+            mHardwareService.initVideo()
+                    .onErrorComplete()
+                    .subscribe();
         }
     }
 
diff --git a/ring-android/libringclient/src/main/java/cx/ring/navigation/HomeNavigationPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/navigation/HomeNavigationPresenter.java
index 160dc61db..d8deffe45 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/navigation/HomeNavigationPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/navigation/HomeNavigationPresenter.java
@@ -213,7 +213,9 @@ public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> {
 
     public void cameraPermissionChanged(boolean isGranted) {
         if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo().subscribe();
+            mHardwareService.initVideo()
+                    .onErrorComplete()
+                    .subscribe();
         }
     }
 }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/HardwareService.java b/ring-android/libringclient/src/main/java/cx/ring/services/HardwareService.java
index 722facc76..0fa27cb0a 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/HardwareService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/HardwareService.java
@@ -35,6 +35,7 @@ import cx.ring.daemon.UintVect;
 import cx.ring.model.Conference;
 import cx.ring.model.SipCall;
 import cx.ring.utils.Log;
+import cx.ring.utils.Tuple;
 import io.reactivex.Completable;
 import io.reactivex.Observable;
 import io.reactivex.Scheduler;
@@ -156,6 +157,7 @@ public abstract class HardwareService {
 
     public abstract boolean hasCamera();
     public abstract int getCameraCount();
+    public abstract Observable<Tuple<Integer, Integer>> getMaxResolutions();
 
     public abstract boolean isPreviewFromFrontCamera();
 
-- 
GitLab