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