diff --git a/.gitignore b/.gitignore index 46ed4410cb88a4b3c28cce54185fa88737340f95..775172ba3e9e2b286aa0f357b00726ecbe482b87 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ _saved/ build/ app/release/ +app/version.properties gfx/ testdata/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..8e2f31462cc1846f193f597d55388d36cf1a2ec9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,77 @@ +image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest" + +stages: + - update-from-upstream + - build + +before_script: + - apt update && apt-get install libncurses5 -y + - export GRADLE_USER_HOME=$(pwd)/.gradle + - chmod +x ./gradlew + +cache: + key: ${CI_PROJECT_ID} + paths: + - .gradle/ + +build: + stage: build + script: + - ./gradlew build -x test + artifacts: + paths: + - app/build/outputs/apk + +.update-from-upstream: + image: registry.gitlab.e.foundation/e/tools/docker-tools:latest + stage: update-from-upstream + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME == $LOCAL_BRANCH' + variables: + CI_PROJECT_SSH_URL: git@gitlab.e.foundation:$CI_PROJECT_PATH + GIT_STRATEGY: none + before_script: + - 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )' + - eval $(ssh-agent -s) + - echo "${SSH_E_ROBOT_PRIVATE_KEY}" | tr -d '\r' | ssh-add - + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo "${SSH_KNOWN_HOSTS}" > ~/.ssh/known_hosts + - echo "${SSH_KNOWN_HOSTS}" + - chmod 644 ~/.ssh/known_hosts + - git config --global user.email $GITLAB_USER_EMAIL + - git config --global user.name "$GITLAB_USER_NAME" + - cd $CI_BUILD_DIR + - rm -rf $CI_PROJECT_DIR + - git clone $CI_PROJECT_SSH_URL $CI_PROJECT_DIR + - cd $CI_PROJECT_DIR + script: + - git config http.sslverify false + # update $UPSTREAM_BRANCH & tags + - git fetch origin + - git checkout $UPSTREAM_BRANCH + - git remote add upstream $UPSTREAM_URL + - git fetch upstream + - git pull upstream $UPSTREAM_DEFAULT_BRANCH + - git push origin $UPSTREAM_BRANCH + - git push origin --tags + # checkout to latest tag commit to $TEMP_LATEST_TAG_BRANCH + - git checkout $(git describe --tags --abbrev=0) + - git checkout -b $TEMP_LATEST_TAG_BRANCH + # merge $LOCAL_BRANCH with $TEMP_LATEST_TAG_BRANCH & push + - git checkout $LOCAL_BRANCH + - git merge $TEMP_LATEST_TAG_BRANCH + - git push origin $LOCAL_BRANCH + # remove unwanted local branch & remote + - git branch -D $TEMP_LATEST_TAG_BRANCH + - git remote remove upstream + +update-default-branch: + extends: .update-from-upstream + variables: + LOCAL_BRANCH: master + UPSTREAM_BRANCH: upstream/master + UPSTREAM_DEFAULT_BRANCH: master + UPSTREAM_URL: https://git.code.sf.net/p/opencamera/code + TEMP_LATEST_TAG_BRANCH: latest_upstream_tag_branch + allow_failure: true diff --git a/app/build.gradle b/app/build.gradle index c00a8b38cdcbb8b07b9a737cbafdaa8b7ffadb41..61d228d0378f82397b1a179e73b8580eb62eeeaa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,85 @@ apply plugin: 'com.android.application' +apply plugin: 'org.jetbrains.kotlin.android' android { compileSdk 34 compileOptions.encoding = 'UTF-8' + def versionPropsFile = file('version.properties') + Properties versionProps = new Properties() + + if (!versionPropsFile.exists()) { + versionProps['VERSION_CHANGE'] = '0' + versionProps['VERSION_MAJOR'] = '1' + versionProps['VERSION_MINOR'] = '53' + versionProps['VERSION_PATCH'] = '1' + versionProps['VERSION_CODE'] = '90' + versionProps.store(versionPropsFile.newWriter(), null) + } + + def getVersionCode = { -> + if (versionPropsFile.canRead()) { + versionProps.load(new FileInputStream(versionPropsFile)) + def versionChange = versionProps['VERSION_CHANGE'].toInteger() + 1 + def versionMinor = versionProps['VERSION_MINOR'].toInteger() + def versionMajor = versionProps['VERSION_MAJOR'].toInteger() + def versionPatch = versionProps['VERSION_PATCH'].toInteger() + // Up version on each 100 cycles of builds + if (versionChange >= 100) { + versionPatch = versionProps['VERSION_PATCH'].toInteger() + 1 + versionChange = 0 + } + if (versionPatch == 9) { + versionMinor = versionProps['VERSION_MINOR'].toInteger() + 1 + versionPatch = 0 + } + if (versionMinor == 9) { + versionMajor = versionProps['VERSION_MAJOR'].toInteger() + 1 + versionMinor = 0 + } + def versionCode = versionProps['VERSION_CODE'].toInteger() + + versionProps['VERSION_CHANGE'] = versionChange.toString() + versionProps['VERSION_PATCH'] = versionPatch.toString() + versionProps['VERSION_MINOR'] = versionMinor.toString() + versionProps['VERSION_MAJOR'] = versionMajor.toString() + versionProps['VERSION_CODE'] = (versionCode.toInteger() + 1).toString() + versionProps.store(versionPropsFile.newWriter(), null) + return versionCode + } + } + + def getVersionName = { -> + if (versionPropsFile.canRead()) { + versionProps.load(new FileInputStream(versionPropsFile)) + + def versionMajor = versionProps['VERSION_MAJOR'] + def versionMinor = versionProps['VERSION_MINOR'] + def versionPatch = versionProps['VERSION_PATCH'] + + return "${versionMajor}.${versionMinor}.${versionPatch}" + } + } + + signingConfigs { + // These are public keys provided by AOSP, Use different passcode protected keys for production + debug { + storeFile file('keys/platform.jks') + storePassword 'platform' + keyAlias 'platform' + keyPassword 'platform' + } + } + defaultConfig { - applicationId "net.sourceforge.opencamera" - minSdkVersion 15 + applicationId "foundation.e.camera" + minSdkVersion 26 targetSdkVersion 34 //compileSdkVersion 31 // needed to support appcompat:1.4.0 (which we need for emoji policy support, and not yet ready to target SDK 30) + versionCode getVersionCode() + versionName getVersionName() + renderscriptTargetApi 21 //renderscriptSupportModeEnabled true // don't use support library as it bloats the APK, and we don't need pre-4.4 support // need build tools at least 20 at least to support ScriptIntrinsicHistogram @@ -25,8 +95,11 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } - } + debug { + signingConfig signingConfigs.debug + } + } // needed to use android.test package (ActivityInstrumentationTestCase2 etc) when targetting sdk 28 (Android 9) - // see https://developer.android.com/training/testing/set-up-project @@ -39,20 +112,29 @@ android { namespace 'net.sourceforge.opencamera' buildFeatures { renderScript true + buildConfig true } //useLibrary 'android.test.mock' } dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' + implementation 'androidx.core:core-ktx:1.8.0' // appcompat version must be 1.4.0 or later to satisfy emoji policy! implementation 'androidx.appcompat:appcompat:1.6.1' + implementation "org.greenrobot:eventbus:3.3.1" + + implementation 'foundation.e:elib:0.0.1-alpha11' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.exifinterface:exifinterface:1.3.7' + implementation 'androidx.camera:camera-core:1.2.3' + implementation 'com.google.zxing:core:3.5.2' + testImplementation 'junit:junit:4.13.2' // newer AndroidJUnit4 InstrumentedTest diff --git a/app/keys/platform.jks b/app/keys/platform.jks new file mode 100644 index 0000000000000000000000000000000000000000..b778840542e79c048bcf570aa960243eeb9b9d53 Binary files /dev/null and b/app/keys/platform.jks differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a25745d04d7da3afb726eae7e6df800369e5e8df..525732197f52e86e59d111ecd786257c7fb8aba0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ android:maxSdkVersion="28" tools:ignore="ScopedStorage" /> + @@ -38,11 +39,14 @@ android:name=".OpenCameraApplication" android:theme="@style/AppTheme" android:largeHeap="true" + android:hardwareAccelerated="true" + android:requestLegacyExternalStorage="true" > @@ -89,13 +93,25 @@ + + + + @@ -128,7 +144,7 @@ @@ -139,7 +155,7 @@ @@ -150,7 +166,7 @@ diff --git a/app/src/main/java/net/sourceforge/opencamera/ContinuousBurstImageRunningAction.java b/app/src/main/java/net/sourceforge/opencamera/ContinuousBurstImageRunningAction.java new file mode 100644 index 0000000000000000000000000000000000000000..b5cf989e784a373eb2d74dd1dfe46fc74fc2ec3f --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ContinuousBurstImageRunningAction.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * 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 . + */ + +package net.sourceforge.opencamera; + +public class ContinuousBurstImageRunningAction { } diff --git a/app/src/main/java/net/sourceforge/opencamera/DeviceSettings.java b/app/src/main/java/net/sourceforge/opencamera/DeviceSettings.java new file mode 100644 index 0000000000000000000000000000000000000000..b76e9fcb3174c548151be18c11c90c79409ef041 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/DeviceSettings.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * 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 . + */ + +package net.sourceforge.opencamera; + +import android.os.Build; + +import java.util.Locale; + +public class DeviceSettings { + + /** Returns whether the the device uses fake flash in Camera API2. + * Some older device have issues in low light. + */ + public static boolean deviceUsingFakeFlash() { + final boolean is_xiaomi = Build.MANUFACTURER.toLowerCase(Locale.US).contains("xiaomi"); + final boolean is_fairphone = Build.MANUFACTURER.toLowerCase(Locale.US).contains("fairphone"); + final boolean is_GS290 = Build.DEVICE != null && Build.DEVICE.equals("GS290"); + return is_fairphone || is_xiaomi || is_GS290; + } + + public static boolean isMurenaOne() { + final boolean isMurena = Build.MANUFACTURER.equals("Secure_Phone") + || Build.MANUFACTURER.equals("Murena"); + final boolean isOne = Build.DEVICE.equals("one") || Build.DEVICE.equals("X2"); + return isMurena && isOne; + } + + public static boolean isMurenaTwo() { + return Build.DEVICE.toLowerCase(Locale.US).contains("two"); + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ImageSaver.java b/app/src/main/java/net/sourceforge/opencamera/ImageSaver.java index 878c39e2530f229d5900877d02acf37e14cb802f..9bf105cb4ae83664dd457079b9b86f43cb1485d6 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ImageSaver.java +++ b/app/src/main/java/net/sourceforge/opencamera/ImageSaver.java @@ -3952,9 +3952,10 @@ public class ImageSaver extends Thread { } setCustomExif(exif, custom_tag_artist, custom_tag_copyright); - if( store_location && ( !exif.hasAttribute(ExifInterface.TAG_GPS_LATITUDE) || !exif.hasAttribute(ExifInterface.TAG_GPS_LATITUDE) ) ) { + if( store_location && ( !exif.hasAttribute(ExifInterface.TAG_GPS_LATITUDE) || !exif.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE) || exif.getLatLong()[1] == 0.0d) ) { // We need this when using camera extensions (since Camera API doesn't support location for camera extensions). // But some devices (e.g., Pixel 6 Pro with Camera2 API) seem to not store location data, so we always check if we need to add it. + // Also for FP5, wherein longitude randomly is 0.0d, even when properly passed via JPEG_GPS_LOCATION // fine to ignore request.remove_device_exif, as this is a separate user option if( MyDebug.LOG ) Log.d(TAG, "store location"); // don't log location for privacy reasons! diff --git a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java index 07360e28118db95c0743744be96580c0e89634e5..222b911eb3efc3af75d8dd1eefd4c2ccd3e70a15 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java +++ b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java @@ -1,26 +1,37 @@ package net.sourceforge.opencamera; +import net.sourceforge.opencamera.camera2.CameraFinder; +import net.sourceforge.opencamera.camera2.CameraIdentifier; import net.sourceforge.opencamera.cameracontroller.CameraController; import net.sourceforge.opencamera.cameracontroller.CameraControllerManager; import net.sourceforge.opencamera.cameracontroller.CameraControllerManager2; +import net.sourceforge.opencamera.camera2.model.CameraModel; +import net.sourceforge.opencamera.camera2.model.CameraType; import net.sourceforge.opencamera.preview.Preview; import net.sourceforge.opencamera.preview.VideoProfile; +import net.sourceforge.opencamera.qr.QrImageAnalyzer; import net.sourceforge.opencamera.remotecontrol.BluetoothRemoteControl; +import net.sourceforge.opencamera.ui.CircleImageView; import net.sourceforge.opencamera.ui.DrawPreview; import net.sourceforge.opencamera.ui.FolderChooserDialog; import net.sourceforge.opencamera.ui.MainUI; import net.sourceforge.opencamera.ui.ManualSeekbars; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Hashtable; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -38,6 +49,9 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraMetadata; import android.hardware.display.DisplayManager; import android.media.MediaMetadataRetriever; import android.net.Uri; @@ -96,6 +110,7 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.SeekBar; @@ -104,6 +119,12 @@ import android.widget.ZoomControls; import androidx.appcompat.app.AppCompatActivity; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.Dispatchers; +import kotlinx.coroutines.Job; +import kotlinx.coroutines.JobKt; + /** The main Activity for Open Camera. */ public class MainActivity extends AppCompatActivity implements PreferenceFragment.OnPreferenceStartFragmentCallback { @@ -119,6 +140,11 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // components: always non-null (after onCreate()) private BluetoothRemoteControl bluetoothRemoteControl; private PermissionHandler permissionHandler; + private CameraManager cameraManager; + private CameraFinder camerafinder; + private CameraIdentifier cameraIdentifier; + private ArrayList cameraModel; + private CameraCharacteristics cameraCharacteristics; private SettingsManager settingsManager; private MainUI mainUI; private ManualSeekbars manualSeekbars; @@ -141,24 +167,34 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen private boolean camera_in_background; // whether the camera is covered by a fragment/dialog (such as settings or folder picker) private GestureDetector gestureDetector; private boolean screen_is_locked; // whether screen is "locked" - this is Open Camera's own lock to guard against accidental presses, not the standard Android lock - private final Map preloaded_bitmap_resources = new Hashtable<>(); + private final Map preloaded_bitmap_resources = new HashMap<>(); private ValueAnimator gallery_save_anim; private boolean last_continuous_fast_burst; // whether the last photo operation was a continuous_fast_burst private Future update_gallery_future; + private boolean should_run_continuous_fast_burst = false; private TextToSpeech textToSpeech; private boolean textToSpeechSuccess; + private List frontZoomRatios = new ArrayList<>(); + private List backZoomRatios = new ArrayList<>(); + private AudioListener audio_listener; // may be null - created when needed //private boolean ui_placement_right = true; private boolean want_no_limits; // whether we want to run with FLAG_LAYOUT_NO_LIMITS + private boolean can_draw_nav_bar = true; private boolean set_window_insets_listener; // whether we've enabled a setOnApplyWindowInsetsListener() private int navigation_gap; public static volatile boolean test_preview_want_no_limits; // test flag, if set to true then instead use test_preview_want_no_limits_value; needs to be static, as it needs to be set before activity is created to take effect public static volatile boolean test_preview_want_no_limits_value; + private boolean murenaTwoHwKill = false; + + private Handler handler; + private Runnable retryRunnable = null; + // whether this is a multi-camera device (note, this isn't simply having more than 1 camera, but also having more than one with the same facing) // note that in most cases, code should check the MultiCamButtonPreferenceKey preference as well as the is_multi_cam flag, // this can be done via isMultiCamEnabled(). @@ -168,6 +204,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen private List back_camera_ids; private List front_camera_ids; private List other_camera_ids; + private List logical_camera_ids; private final ToastBoxer switch_video_toast = new ToastBoxer(); private final ToastBoxer screen_locked_toast = new ToastBoxer(); @@ -210,6 +247,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen //private final String CHANNEL_ID = "open_camera_channel"; //private final int image_saving_notification_id = 1; + private final float defaultLensZoom = 1.0F; + private static final float WATER_DENSITY_FRESHWATER = 1.0f; private static final float WATER_DENSITY_SALTWATER = 1.03f; private float mWaterDensity = 1.0f; @@ -218,6 +257,11 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen //public static final boolean lock_to_landscape = true; public static final boolean lock_to_landscape = false; + // QRCode + public QrImageAnalyzer qrImageAnalyzer; + private Job job; + private CoroutineScope coroutineScope; + // handling for lock_to_landscape==false: public enum SystemOrientation { @@ -250,6 +294,16 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "activity_count: " + activity_count); super.onCreate(savedInstanceState); + //QRCode + job = JobKt.Job(null); + coroutineScope = new CoroutineScope() { + @Override + public CoroutineContext getCoroutineContext() { + return Dispatchers.getMain().plus(job); + } + }; + qrImageAnalyzer = new QrImageAnalyzer(this, coroutineScope); + setContentView(R.layout.activity_main); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); // initialise any unset preferences to their default values if( MyDebug.LOG ) @@ -312,6 +366,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen magneticSensor = new MagneticSensor(this); //speechControl = new SpeechControl(this); + handler = new Handler(); + // determine whether we support Camera2 API // must be done before setDeviceDefaults() initCamera2Support(); @@ -348,6 +404,30 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen if( MyDebug.LOG ) Log.d(TAG, "onCreate: time after updating folder history: " + (System.currentTimeMillis() - debug_time)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + int isFirstTime = sharedPreferences.getInt("isFirstTimeChange", 0); + switch (isFirstTime) { + case 0: + String edge_pref = applicationInterface.getEdgeModePref(); + if (DeviceSettings.isMurenaOne() && edge_pref.equals("default")) { + editor.putString(PreferenceKeys.EdgeModePreferenceKey, "off"); + } + String save_location = sharedPreferences.getString( + PreferenceKeys.SaveLocationPreferenceKey, "Camera"); + if (save_location.equals("OpenCamera")) { + editor.putString(PreferenceKeys.SaveLocationPreferenceKey, "Camera"); + } + isFirstTime++; + case 1: + editor.putBoolean(PreferenceKeys.MaxBrightnessPreferenceKey, false); + isFirstTime++; + break; + default: + break; + } + + editor.putInt("isFirstTimeChange", isFirstTime).apply(); + // set up sensors mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); @@ -402,6 +482,20 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen getWindow().setAttributes(layout); } + if (getResources().getBoolean(R.bool.zoom_level_switch_supported)) { + cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); + camerafinder = new CameraFinder(cameraManager); + cameraModel = camerafinder.getCameraModels(); + cameraIdentifier = new CameraIdentifier(cameraModel); + camerafinder.init(); + cameraIdentifier.init(); + } + + String[] ignoredCameraIDs = getResources().getStringArray(R.array.config_ignoredCameraIds); + if( MyDebug.LOG ) { + Log.d(TAG, "ignoredCameraIDs: " + Arrays.toString(ignoredCameraIDs)); + } + // Setup multi-camera buttons (must be done after creating preview so we know which Camera API is being used, // and before initialising on-screen visibility). // We only allow the separate icon for switching cameras if: @@ -412,12 +506,55 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // If there are more than two cameras, but all cameras have the same "facing, we still stick // with using the switch camera icon to iterate over all cameras. int n_cameras = preview.getCameraControllerManager().getNumberOfCameras(); - if( n_cameras > 2 ) { + if (getResources().getBoolean(R.bool.zoom_level_switch_supported)) { + n_cameras = camerafinder.getAllCameraIdList().size(); + if( n_cameras > 2 ) { + this.back_camera_ids = new ArrayList<>(); + this.front_camera_ids = new ArrayList<>(); + this.other_camera_ids = new ArrayList<>(); + this.logical_camera_ids = new ArrayList<>(); + + if (!cameraModel.isEmpty()) { + for (CameraModel model : cameraModel) { + if (model.getCameraType() == CameraType.LOGICAL) { + logical_camera_ids.add(model.getId()); + } + } + } + + for(String cameraId: camerafinder.getAllCameraIdList()) { + if (Arrays.stream(ignoredCameraIDs).anyMatch(id -> id.equals(cameraId))) { + continue; + } + int id = Integer.parseInt(cameraId); + cameraCharacteristics = camerafinder.getCameraCharacteristics(id); + Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + if (!logical_camera_ids.contains(id) + || getResources().getBoolean(R.bool.allow_logical_camera_ids)) { + switch(facing) { + case CameraMetadata.LENS_FACING_BACK: + back_camera_ids.add(id); + break; + case CameraMetadata.LENS_FACING_FRONT: + front_camera_ids.add(id); + break; + case CameraMetadata.LENS_FACING_EXTERNAL: + other_camera_ids.add(id); + break; + } + } + } + } + } else if (n_cameras > 2) { this.back_camera_ids = new ArrayList<>(); this.front_camera_ids = new ArrayList<>(); this.other_camera_ids = new ArrayList<>(); - for(int i=0;i id.equals(currentCameraID))) { + continue; + } + switch (preview.getCameraControllerManager().getFacing(i)) { case FACING_BACK: back_camera_ids.add(i); break; @@ -430,6 +567,15 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen break; } } + } + + if (n_cameras > 2) { + if( MyDebug.LOG ) { + Log.d(TAG, "back_camera_ids: " + back_camera_ids); + Log.d(TAG, "front_camera_ids: " + front_camera_ids); + Log.d(TAG, "other_camera_ids: " + other_camera_ids); + Log.d(TAG, "logical_camera_ids" + logical_camera_ids); + } boolean multi_same_facing = back_camera_ids.size() >= 2 || front_camera_ids.size() >= 2 || other_camera_ids.size() >= 2; int n_facing = 0; if( back_camera_ids.size() > 0 ) @@ -446,6 +592,10 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "is_multi_cam: " + is_multi_cam); } + if (getResources().getBoolean(R.bool.zoom_level_switch_supported)) { + initZoomRatios(); + } + if( !is_multi_cam ) { this.back_camera_ids = null; this.front_camera_ids = null; @@ -454,7 +604,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } // initialise on-screen button visibility - View switchCameraButton = findViewById(R.id.switch_camera); + ImageButton switchCameraButton = findViewById(R.id.switch_camera); switchCameraButton.setVisibility(n_cameras > 1 ? View.VISIBLE : View.GONE); // switchMultiCameraButton visibility updated below in mainUI.updateOnScreenIcons(), as it also depends on user preference View speechRecognizerButton = findViewById(R.id.audio_control); @@ -463,10 +613,12 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "onCreate: time after setting button visibility: " + (System.currentTimeMillis() - debug_time)); View pauseVideoButton = findViewById(R.id.pause_video); pauseVideoButton.setVisibility(View.GONE); - View takePhotoVideoButton = findViewById(R.id.take_photo_when_video_recording); + ImageButton takePhotoVideoButton = findViewById(R.id.take_photo_when_video_recording); takePhotoVideoButton.setVisibility(View.GONE); View cancelPanoramaButton = findViewById(R.id.cancel_panorama); cancelPanoramaButton.setVisibility(View.GONE); + View finishPanoramaButton = findViewById(R.id.finish_panorama); + finishPanoramaButton.setVisibility(View.GONE); // We initialise optional controls to invisible/gone, so they don't show while the camera is opening - the actual visibility is // set in cameraSetup(). @@ -480,6 +632,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen zoomControls.setVisibility(View.GONE); View zoomSeekbar = findViewById(R.id.zoom_seekbar); zoomSeekbar.setVisibility(View.INVISIBLE); + View zoomSeekbarIcon = findViewById(R.id.zoom_seekbar_icon); + zoomSeekbarIcon.setVisibility(View.INVISIBLE); // initialise state of on-screen icons mainUI.updateOnScreenIcons(); @@ -576,7 +730,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen }); // set up switch camera button long click - must be done after setting is_multi_cam - if( n_cameras > 2 ) { + if( n_cameras > 2 && !getResources().getBoolean(R.bool.zoom_level_switch_supported)) { View.OnLongClickListener long_click_listener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { @@ -612,7 +766,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "onCreate: time after setting system ui visibility listener: " + (System.currentTimeMillis() - debug_time)); // show "about" dialog for first time use - if( !has_done_first_time ) { + if(false) { if( !is_test ) { AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); alertDialog.setTitle(R.string.app_name); @@ -690,7 +844,6 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // We set the latest_version whether or not the dialog is shown - if we showed the first time dialog, we don't // want to then show the What's New dialog next time we run! Similarly if the user had disabled showing the dialog, // but then enables it, we still shouldn't show the dialog until the new time Open Camera upgrades. - SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putInt(PreferenceKeys.LatestVersionPreferenceKey, version_code); editor.apply(); } @@ -750,10 +903,49 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen notificationManager.createNotificationChannel(channel); }*/ + calculateNavigationGap(); + if( MyDebug.LOG ) Log.d(TAG, "onCreate: total time for Activity startup: " + (System.currentTimeMillis() - debug_time)); } + @Override + protected void onStart() { + super.onStart(); + EventBus.getDefault().register(this); + } + + /** + * if navigationMode is no gesture, then retrieve navigationBar's height & update navigation_gap + */ + private void calculateNavigationGap() { + int resourceId = getResources().getIdentifier("config_navBarInteractionMode", "integer", "android"); + if (resourceId > 0) { + int navType = getResources().getInteger(resourceId); + if (navType != 2) { //gesture mode = 2 + resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + navigation_gap = getResources().getDimensionPixelSize(resourceId); + } + } + } + } + + public void handleDecorFitsSystemWindows(double previewRatio, double screenRatio) { + boolean decorFitsSystemWindows = screenRatio >= previewRatio; + setDecorFitsSystemWindows(decorFitsSystemWindows); + can_draw_nav_bar = !decorFitsSystemWindows; + } + + /** + * check is the preview size = maximise && is not in video mode, then return true + */ + public boolean isInFullScreenMode() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + String preview_size = sharedPreferences.getString(PreferenceKeys.PreviewSizePreferenceKey, "preference_preview_size_display"); + return !(preview_size.equals("preference_preview_size_wysiwyg") || preview.isVideo()); + } + /** Whether to use codepaths that are compatible with scoped storage. */ public static boolean useScopedStorage() { @@ -801,15 +993,15 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen int cameraId = getActualCameraId(); switch( preview.getCameraControllerManager().getFacing(cameraId) ) { case FACING_BACK: - if( back_camera_ids.size() > 0 ) + if( back_camera_ids.size() > 1 ) return true; break; case FACING_FRONT: - if( front_camera_ids.size() > 0 ) + if( front_camera_ids.size() > 1 ) return true; break; default: - if( other_camera_ids.size() > 0 ) + if( other_camera_ids.size() > 1 ) return true; break; } @@ -847,6 +1039,14 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen //Log.d(TAG, "is_pixel_phone? " + is_pixel_phone); //Log.d(TAG, "is_pixel_xl_phone? " + is_pixel_xl_phone); }*/ + if ( DeviceSettings.deviceUsingFakeFlash() ) { + if( MyDebug.LOG ) + Log.d(TAG, "set fake flash for camera2"); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(PreferenceKeys.Camera2FakeFlashPreferenceKey, true); + editor.apply(); + } /*if( is_samsung || is_oneplus ) { // The problems we used to have on Samsung Galaxy devices are now fixed, by setting // TEMPLATE_PREVIEW for the precaptureBuilder in CameraController2. This also fixes the @@ -912,6 +1112,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } } } + + updateCameraResolution(); } /** Switches modes if required, if called from a relevant intent/tile. @@ -1091,7 +1293,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen String new_folder; if( res.alt == null ) { // no alternative, fall back to default - new_folder = "OpenCamera"; + new_folder = "Camera"; } else { // replace with the alternative @@ -1245,6 +1447,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen if( MyDebug.LOG ) Log.d(TAG, "onStop"); super.onStop(); + EventBus.getDefault().unregister(this); // we stop location listening in onPause, but done here again just to be certain! applicationInterface.getLocationSupplier().freeLocationListeners(); @@ -1435,6 +1638,26 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen else { mainUI.onKeyUp(keyCode, event); } + + if (!camera_in_background && keyCode == 131 && DeviceSettings.isMurenaTwo()) { + String cameraState = Utils.getProperty("persist.sys.hwswitch.state", null); + if (cameraState == null) return super.onKeyUp(keyCode, event); + + if (retryRunnable != null) { + handler.removeCallbacks(retryRunnable); + } + + murenaTwoHwKill = Objects.equals(cameraState, "1"); + retryRunnable = () -> { + if (!murenaTwoHwKill) { + preview.retryOpenCamera(); + } else { + restartOpenCamera(); + } + }; + handler.postDelayed(retryRunnable, murenaTwoHwKill ? 200 : 5000); + } + return super.onKeyUp(keyCode, event); } @@ -1466,11 +1689,11 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } public void zoomIn() { - zoomByStep(1); + zoomByStep(-1); } public void zoomOut() { - zoomByStep(-1); + zoomByStep(1); } public void changeExposure(int change) { @@ -2025,6 +2248,13 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen applicationInterface.stopPanorama(true); } + public void clickedFinishPanorama(View view) { + if (MyDebug.LOG) { + Log.d(TAG, "clickedFinishPanorama"); + } + this.takePicture(false); + } + public void clickedCycleRaw(View view) { if( MyDebug.LOG ) Log.d(TAG, "clickedCycleRaw"); @@ -2321,20 +2551,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.e(TAG, "getNextMultiCameraId() called but not in multi-cam mode"); throw new RuntimeException("getNextMultiCameraId() called but not in multi-cam mode"); } - List camera_set; - // don't use preview.getCameraController(), as it may be null if user quickly switches between cameras int currCameraId = getActualCameraId(); - switch( preview.getCameraControllerManager().getFacing(currCameraId) ) { - case FACING_BACK: - camera_set = back_camera_ids; - break; - case FACING_FRONT: - camera_set = front_camera_ids; - break; - default: - camera_set = other_camera_ids; - break; - } + List camera_set = getCameraSet(currCameraId); int cameraId; int indx = camera_set.indexOf(currCameraId); if( indx == -1 ) { @@ -2358,6 +2576,24 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen return cameraId; } + private List getCameraSet(int currCameraId) { + // don't use preview.getCameraController(), as it may be null if user quickly switches between cameras + List camera_set; + switch( preview.getCameraControllerManager().getFacing(currCameraId) ) { + case FACING_BACK: + camera_set = back_camera_ids; + break; + case FACING_FRONT: + camera_set = front_camera_ids; + break; + default: + camera_set = other_camera_ids; + break; + } + + return camera_set; + } + private void pushCameraIdToast(int cameraId) { if( MyDebug.LOG ) Log.d(TAG, "pushCameraIdToast: " + cameraId); @@ -2374,7 +2610,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } } - private void userSwitchToCamera(int cameraId) { + private void userSwitchToCamera(int cameraId, boolean is_multi_cam) { if( MyDebug.LOG ) Log.d(TAG, "userSwitchToCamera: " + cameraId); View switchCameraButton = findViewById(R.id.switch_camera); @@ -2384,13 +2620,93 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen switchMultiCameraButton.setEnabled(false); applicationInterface.reset(true); this.getApplicationInterface().getDrawPreview().setDimPreview(true); - this.preview.setCamera(cameraId); + if (is_multi_cam && getResources().getBoolean(R.bool.zoom_level_switch_supported)) { + changeZoomFactor(); + } else { + this.preview.setCamera(cameraId); + } switchCameraButton.setEnabled(true); switchMultiCameraButton.setEnabled(true); // no need to call mainUI.setSwitchCameraContentDescription - this will be called from Preview.cameraSetup when the // new camera is opened } + private void initZoomRatios() { + String[] auxZoomRatios = getResources().getStringArray(R.array.config_auxCameraZoomRatios); + if (auxZoomRatios.length == 0 && !cameraModel.isEmpty()) { + for (CameraModel model : cameraModel) { + cameraCharacteristics = camerafinder.getCameraCharacteristics(model.getId()); + Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + CameraType cameraType = model.getCameraType(); + if (cameraType != CameraType.LOGICAL) { + if (facing == CameraMetadata.LENS_FACING_BACK) { + if (cameraType == CameraType.MAIN || cameraType == CameraType.ULTRAWIDE + || cameraType == CameraType.TELE || cameraType == CameraType.MACRO) { + backZoomRatios.add(model.getZoomFactor()); + } + } else if (facing == CameraMetadata.LENS_FACING_FRONT) { + if (cameraType == CameraType.MAIN || cameraType == CameraType.ULTRAWIDE) { + frontZoomRatios.add(model.getZoomFactor()); + } + } + } + } + } else { + for (String zoom : auxZoomRatios) { + String[] separated = zoom.split(":"); + float id = Integer.parseInt(separated[0]); + float zoomRatio = Float.parseFloat(separated[1]); + if (id == 1) { + frontZoomRatios.add(zoomRatio); + } else { + backZoomRatios.add(zoomRatio); + } + } + } + + if( MyDebug.LOG ) { + Log.d(TAG, "frontZoomRatios" + frontZoomRatios); + Log.d(TAG, "backZoomRatios" + backZoomRatios); + } + } + + private void changeZoomFactor() { + float zoom = getNextMultiZoomRatio(backZoomRatios); + if (preview.getCameraControllerManager().getFacing(getActualCameraId()) + == CameraController.Facing.FACING_FRONT) { + zoom = getNextMultiZoomRatio(frontZoomRatios); + } + + preview.zoomTo(preview.findNxZoom(zoom), true); + updateMultiPixelIcon(backZoomRatios.indexOf(zoom)); + + if( MyDebug.LOG ) + Log.d(TAG, "changeZoomFactor" + zoom); + } + + public float getNextMultiZoomRatio(List zoomRations) { + float currentZoom = preview.getZoomRatio(); + float zoomRatio; + int index = zoomRations.indexOf(currentZoom); + if ( index == -1 ) { + zoomRatio = zoomRations.get(0); + } else { + index = (index+1) % zoomRations.size(); + zoomRatio = zoomRations.get(index); + } + if( MyDebug.LOG ) + Log.d(TAG, "next zoom: " + zoomRatio); + return zoomRatio; + } + + private void updateMultiPixelIcon(int index) { + Button multiCameraButton = findViewById(R.id.switch_multi_camera); + if (multiCameraButton.getVisibility() != View.GONE) { + String text = getString(R.string.switch_multi_camera_lens) + " " + (index + 1); + multiCameraButton.setText(text); + } + } + /** * Selects the next camera on the phone - in practice, switches between * front and back cameras @@ -2419,7 +2735,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // disappear when the user touches the screen anyway.) preview.clearActiveFakeToast(); } - userSwitchToCamera(cameraId); + userSwitchToCamera(cameraId, false); push_switched_camera = true; } @@ -2441,7 +2757,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen if( this.preview.canSwitchCamera() ) { int cameraId = getNextMultiCameraId(); pushCameraIdToast(cameraId); - userSwitchToCamera(cameraId); + userSwitchToCamera(cameraId, true); } } @@ -2490,7 +2806,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen MainActivity.this.closePopup(); if( MainActivity.this.preview.canSwitchCamera() ) { pushCameraIdToast(which); - userSwitchToCamera(which); + userSwitchToCamera(which, true); } } setWindowFlagsForCamera(); @@ -2508,6 +2824,56 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen showAlert(alertDialog.create()); } + /* getBetterQRCodeCameraID() + Returns the best camera ID, based on the fact that it's probably the first rear camera available. + The user always has the option of selecting with the lens switcher in case the choice is wrong. + Returns -1 if no camera available. In that case we do *NOT* trig any switch. + */ + public int getBetterQRCodeCameraID() { + int best_qrcode_camera = -1; + if( MyDebug.LOG ) + Log.d(TAG, "getBetterQRCodeCameraID"); + if( !isMultiCamEnabled() ) { + Log.e(TAG, "getBetterQRCodeCameraID switch multi camera icon shouldn't have been visible"); + return best_qrcode_camera; + } + if( preview.isOpeningCamera() ) { + if( MyDebug.LOG ) + Log.d(TAG, "getBetterQRCodeCameraID already opening camera in background thread"); + return best_qrcode_camera; + } + if( this.preview.canSwitchCamera() ) { + try { + CameraManager _cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); + for (String _cameraId : _cameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = _cameraManager.getCameraCharacteristics(_cameraId); + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) { + best_qrcode_camera = Integer.parseInt(_cameraId); + if( MyDebug.LOG ) + Log.d(TAG, "best_qrcode_camera ="+best_qrcode_camera); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return best_qrcode_camera; + } + + private void updateMultiCameraIcon() { + Button multiCameraButton = findViewById(R.id.switch_multi_camera); + + if (multiCameraButton.getVisibility() != View.GONE) { + int currCameraId = getActualCameraId(); + List camera_set = getCameraSet(currCameraId); + int index = camera_set.indexOf(currCameraId); + String text = getString(R.string.switch_multi_camera_lens) + " " + (index + 1); + multiCameraButton.setText(text); + } + } + /** * Toggles Photo/Video mode */ @@ -2705,7 +3071,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen //case "preference_show_take_photo": // need to update the UI case "preference_show_toasts": case "preference_show_whats_new": - //case "preference_multi_cam_button": // need to update the UI + //case "preference_multi_cam_button": // need to update the UI case "preference_keep_display_on": case "preference_max_brightness": //case "preference_resolution": // need to set up camera controller and preview @@ -2947,6 +3313,10 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen if( preview.getCurrentPictureSize() != null ) { bundle.putInt("resolution_width", preview.getCurrentPictureSize().width); bundle.putInt("resolution_height", preview.getCurrentPictureSize().height); + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + if (pref.getBoolean("isFirstTimeResolution_" + preview.getCameraId(), true)) { + updateCameraResolution(); + } } //List video_quality = this.preview.getVideoQualityHander().getSupportedVideoQuality(); @@ -3097,6 +3467,38 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen return true; } + private void updateCameraResolution() { + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = pref.edit(); + String[] defaultResolutions = getResources().getStringArray( + R.array.config_e_os_camera_default_resolution_lens); + for (String entry : defaultResolutions) { + if (!entry.matches("\\d+:\\d+x\\d+")) continue; + + String[] mainParts = entry.split(":"); + String[] resParts = mainParts[1].split("x"); + int cameraId = Integer.parseInt(mainParts[0]); + String resolution = resParts[0] + " " + resParts[1]; + + boolean existing = true; + if (preview != null && preview.getSupportedPictureSizes(false) != null) { + List supportedResolutions = + preview.getSupportedPictureSizes(false); + String highestResolution = supportedResolutions.get(0).width + " " + + supportedResolutions.get(0).height; + String currentResolution = pref.getString( + PreferenceKeys.getResolutionPreferenceKey(cameraId), ""); + existing = highestResolution.equals(currentResolution) && cameraId == preview.getCameraId(); + } + if (existing) { + editor.putString(PreferenceKeys.getResolutionPreferenceKey(cameraId), resolution); + editor.putBoolean("isFirstTimeResolution_" + cameraId, false); + } + } + + editor.apply(); + } + public void updateForSettings(boolean update_camera) { updateForSettings(update_camera, null); } @@ -3380,6 +3782,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen changed = changed || (button.getVisibility() != View.GONE); button.setVisibility(View.GONE); } + updateMultiCameraIcon(); + if( MyDebug.LOG ) Log.d(TAG, "checkDisableGUIIcons: " + changed); return changed; @@ -3592,7 +3996,13 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } } + /** + * if sdk>=R & can't draw navigation bar, means the DecorFitsSystemWindows=true, then we can ignore navigationGap. + */ public int getNavigationGap() { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !can_draw_nav_bar) { + return 0; + } return want_no_limits ? navigation_gap : 0; } @@ -3607,7 +4017,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen return; SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MainActivity.this); - String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); boolean hide_ui = immersive_mode.equals("immersive_mode_gui") || immersive_mode.equals("immersive_mode_everything"); if( visible ) { @@ -3628,8 +4038,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } /** Set up listener to handle listening for system ui changes (for immersive mode), and setting - * a WindowsInsetsListener to find the navigation_gap. - */ + * a WindowsInsetsListener to find the navigation_gap. + */ private void setupSystemUiVisibilityListener() { View decorView = getWindow().getDecorView(); @@ -3783,7 +4193,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // whether we are using a Kit Kat style immersive mode (either hiding navigation bar, GUI, or everything) if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); if( immersive_mode.equals("immersive_mode_navigation") || immersive_mode.equals("immersive_mode_gui") || immersive_mode.equals("immersive_mode_everything") ) return true; } @@ -3794,7 +4204,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // whether we are using a Kit Kat style immersive mode for everything if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); if( immersive_mode.equals("immersive_mode_everything") ) return true; } @@ -3854,7 +4264,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } else { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); if( immersive_mode.equals("immersive_mode_low_profile") ) getWindow().getDecorView().setSystemUiVisibility(saved_flags | View.SYSTEM_UI_FLAG_LOW_PROFILE); else @@ -3865,6 +4275,18 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen getWindow().getDecorView().setSystemUiVisibility(saved_flags); } + /** + * setDecorFitsSystemWindows for build >=R + * @return if the operation successful or not + */ + public boolean setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + getWindow().setDecorFitsSystemWindows(decorFitsSystemWindows); + return true; + } + return false; + } + /** Sets the brightness level for normal operation (when camera preview is visible). * If force_max is true, this always forces maximum brightness; otherwise this depends on user preference. */ @@ -3875,7 +4297,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // done here rather than onCreate, so that changing it in preferences takes effect without restarting app SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); final WindowManager.LayoutParams layout = getWindow().getAttributes(); - if( force_max || sharedPreferences.getBoolean(PreferenceKeys.MaxBrightnessPreferenceKey, true) ) { + if( force_max || sharedPreferences.getBoolean(PreferenceKeys.MaxBrightnessPreferenceKey, false) ) { layout.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL; } else { @@ -4059,6 +4481,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } setImmersiveMode(false); + setDecorFitsSystemWindows(true); camera_in_background = true; // we disable location listening when showing settings or a dialog etc - saves battery life, also better for privacy @@ -4259,7 +4682,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen private void updateGalleryIconToBlank() { if( MyDebug.LOG ) Log.d(TAG, "updateGalleryIconToBlank"); - ImageButton galleryButton = this.findViewById(R.id.gallery); + CircleImageView galleryButton = this.findViewById(R.id.gallery); int bottom = galleryButton.getPaddingBottom(); int top = galleryButton.getPaddingTop(); int right = galleryButton.getPaddingRight(); @@ -4267,7 +4690,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen /*if( MyDebug.LOG ) Log.d(TAG, "padding: " + bottom);*/ galleryButton.setImageBitmap(null); - galleryButton.setImageResource(R.drawable.baseline_photo_library_white_48); + galleryButton.setImageResource(R.drawable.ic_album); + galleryButton.setBorderWidth(0); // workaround for setImageResource also resetting padding, Android bug galleryButton.setPadding(left, top, right, bottom); gallery_bitmap = null; @@ -4287,8 +4711,9 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "cancel update_gallery_future"); update_gallery_future.cancel(true); } - ImageButton galleryButton = this.findViewById(R.id.gallery); + CircleImageView galleryButton = this.findViewById(R.id.gallery); galleryButton.setImageBitmap(thumbnail); + galleryButton.setBorderWidth(6); gallery_bitmap = thumbnail; } @@ -4444,7 +4869,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen update_gallery_future = null; } - //}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + //}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }; ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -4461,7 +4886,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen this.runOnUiThread(new Runnable() { public void run() { - final ImageButton galleryButton = findViewById(R.id.gallery); + final CircleImageView galleryButton = findViewById(R.id.gallery); if( started ) { //galleryButton.setColorFilter(0x80ffffff, PorterDuff.Mode.MULTIPLY); if( gallery_save_anim == null ) { @@ -4506,7 +4931,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } /** Creates a notification to indicate still saving images (or updates an existing one). - * Update: notifications now removed due to needing permissions on Android 13+. + * Update: notifications now removed due to needing permissions on Android 13+. */ private void createImageSavingNotification() { if( MyDebug.LOG ) @@ -4550,8 +4975,9 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen if( MyDebug.LOG ) Log.d(TAG, "openGallery"); //Intent intent = new Intent(Intent.ACTION_VIEW, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - Uri uri = applicationInterface.getStorageUtils().getLastMediaScanned(); - boolean is_raw = uri != null && applicationInterface.getStorageUtils().getLastMediaScannedIsRaw(); + Uri primaryUri = applicationInterface.getStorageUtils().getLastMediaScanned(); + Uri uri = null; + boolean is_raw = false; if( MyDebug.LOG && uri != null ) { Log.d(TAG, "found cached most recent uri: " + uri); Log.d(TAG, " is_raw: " + is_raw); @@ -4602,11 +5028,20 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; is_raw = false; } + + boolean allowEdit = true; + if( uri == null ) { + uri = primaryUri; + is_raw = uri != null && applicationInterface.getStorageUtils().getLastMediaScannedIsRaw(); + allowEdit = false; + } + if( !is_test ) { // don't do if testing, as unclear how to exit activity to finish test (for testGallery()) if( MyDebug.LOG ) Log.d(TAG, "launch uri:" + uri); final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; + final String KEY_FROM_SNAPCAM = "from-snapcam"; boolean done = false; if( !is_raw ) { // REVIEW_ACTION means we can view video files without autoplaying. @@ -4618,6 +5053,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "try REVIEW_ACTION"); try { Intent intent = new Intent(REVIEW_ACTION, uri); + intent.putExtra(KEY_FROM_SNAPCAM, allowEdit); this.startActivity(intent); done = true; } @@ -4630,6 +5066,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "try ACTION_VIEW"); try { Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.putExtra(KEY_FROM_SNAPCAM, allowEdit); this.startActivity(intent); } catch(ActivityNotFoundException e) { @@ -4967,7 +5404,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen editText.setHint(getResources().getString(R.string.preference_save_location)); editText.setInputType(InputType.TYPE_CLASS_TEXT); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - editText.setText(sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera")); + editText.setText(sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "Camera")); InputFilter filter = new InputFilter() { // whilst Android seems to allow any characters on internal memory, SD cards are typically formatted with FAT32 final String disallowed = "|\\?*<\":>"; @@ -5284,6 +5721,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen closePopup(); this.last_continuous_fast_burst = continuous_fast_burst; + this.should_run_continuous_fast_burst = continuous_fast_burst; this.preview.takePicturePressed(photo_snapshot, continuous_fast_burst); } @@ -5484,36 +5922,38 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen Log.d(TAG, "set up zoom"); if( MyDebug.LOG ) Log.d(TAG, "has_zoom? " + preview.supportsZoom()); - ZoomControls zoomControls = findViewById(R.id.zoom); + View zoomControlHolder = findViewById(R.id.zoom); SeekBar zoomSeekBar = findViewById(R.id.zoom_seekbar); + View zoomSeekbarIcon = findViewById(R.id.zoom_seekbar_icon); if( preview.supportsZoom() ) { if( sharedPreferences.getBoolean(PreferenceKeys.ShowZoomControlsPreferenceKey, false) ) { - zoomControls.setIsZoomInEnabled(true); - zoomControls.setIsZoomOutEnabled(true); - zoomControls.setZoomSpeed(20); - - zoomControls.setOnZoomInClickListener(new View.OnClickListener(){ - public void onClick(View v){ - zoomIn(); - } - }); - zoomControls.setOnZoomOutClickListener(new View.OnClickListener(){ - public void onClick(View v){ - zoomOut(); - } - }); if( !mainUI.inImmersiveMode() ) { - zoomControls.setVisibility(View.VISIBLE); + View zoomInButton = findViewById(R.id.button_zoom_in); + View zoomOutButton = findViewById(R.id.button_zoom_out); + zoomInButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomIn(); + } + }); + + zoomOutButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomOut(); + } + }); + zoomControlHolder.setVisibility(View.VISIBLE); } } else { - zoomControls.setVisibility(View.GONE); + zoomControlHolder.setVisibility(View.GONE); } zoomSeekBar.setOnSeekBarChangeListener(null); // clear an existing listener - don't want to call the listener when setting up the progress bar to match the existing state zoomSeekBar.setMax(preview.getMaxZoom()); - zoomSeekBar.setProgress(preview.getMaxZoom()-preview.getCameraController().getZoom()); + zoomSeekBar.setProgress(preview.getCameraController().getZoom()); zoomSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @@ -5523,7 +5963,8 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen // indirectly set zoom via this method, from setting the zoom slider // if hasSmoothZoom()==true, then the preview already handled zooming to the current value if( !preview.hasSmoothZoom() ) - preview.zoomTo(preview.getMaxZoom() - progress, false); + preview.zoomTo(progress, false); + updateMultiCameraIcon(); } @Override @@ -5535,23 +5976,26 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen } }); - if( sharedPreferences.getBoolean(PreferenceKeys.ShowZoomSliderControlsPreferenceKey, true) ) { + if( sharedPreferences.getBoolean(PreferenceKeys.ShowZoomSliderControlsPreferenceKey, false) ) { if( !mainUI.inImmersiveMode() ) { zoomSeekBar.setVisibility(View.VISIBLE); + zoomSeekbarIcon.setVisibility(View.VISIBLE); } } else { zoomSeekBar.setVisibility(View.INVISIBLE); // should be INVISIBLE not GONE, as the focus_seekbar is aligned to be left to this; in future we might want this similarly for exposure panel + zoomSeekbarIcon.setVisibility(View.INVISIBLE); } } else { - zoomControls.setVisibility(View.GONE); + zoomControlHolder.setVisibility(View.GONE); zoomSeekBar.setVisibility(View.INVISIBLE); // should be INVISIBLE not GONE, as the focus_seekbar is aligned to be left to this; in future we might want this similarly for the exposure panel + zoomSeekbarIcon.setVisibility(View.INVISIBLE); } if( MyDebug.LOG ) Log.d(TAG, "cameraSetup: time after setting up zoom: " + (System.currentTimeMillis() - debug_time)); - View takePhotoButton = findViewById(R.id.take_photo); + ImageButton takePhotoButton = findViewById(R.id.take_photo); if( sharedPreferences.getBoolean(PreferenceKeys.ShowTakePhotoPreferenceKey, true) ) { if( !mainUI.inImmersiveMode() ) { takePhotoButton.setVisibility(View.VISIBLE); @@ -5668,18 +6112,6 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen public void onStopTrackingTouch(SeekBar seekBar) { } }); - - ZoomControls seek_bar_zoom = findViewById(R.id.exposure_seekbar_zoom); - seek_bar_zoom.setOnZoomInClickListener(new View.OnClickListener(){ - public void onClick(View v){ - changeExposure(1); - } - }); - seek_bar_zoom.setOnZoomOutClickListener(new View.OnClickListener(){ - public void onClick(View v){ - changeExposure(-1); - } - }); } } if( MyDebug.LOG ) @@ -6189,7 +6621,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen simple = false; } } - else { + else { //Camera if( photo_mode == MyApplicationInterface.PhotoMode.Panorama ) { // don't show resolution in panorama mode toast_string = ""; @@ -6223,7 +6655,9 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen simple = false; } } - if( applicationInterface.getFaceDetectionPref() ) { + if (preview.isQRCode()) { + toast_string = "QRCode"; + } else if( applicationInterface.getFaceDetectionPref() ) { //Camera || Video // important so that the user realises why touching for focus/metering areas won't work - easy to forget that face detection has been turned on! toast_string += "\n" + getResources().getString(R.string.preference_face_detection); simple = false; @@ -6479,7 +6913,18 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen public void takePhotoButtonLongClickCancelled() { if( MyDebug.LOG ) Log.d(TAG, "takePhotoButtonLongClickCancelled"); - if( preview.getCameraController() != null && preview.getCameraController().isContinuousBurstInProgress() ) { + if (preview.getCameraController() == null) { + return; + } + if (preview.getCameraController().isContinuousBurstInProgress()) { + preview.getCameraController().stopContinuousBurst(); + } + should_run_continuous_fast_burst = false; + } + + @Subscribe(threadMode = ThreadMode.MAIN_ORDERED) + public void onContinuousBurstImageRunningAction(ContinuousBurstImageRunningAction action) { + if (!should_run_continuous_fast_burst && preview.getCameraController() != null && preview.getCameraController().isContinuousBurstInProgress()) { preview.getCameraController().stopContinuousBurst(); } } diff --git a/app/src/main/java/net/sourceforge/opencamera/MyApplicationInterface.java b/app/src/main/java/net/sourceforge/opencamera/MyApplicationInterface.java index e0c997e5ba663c9f267803639d140781665dff72..0ef85af2c448cb62f11b3ffa83b8210900519bd0 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MyApplicationInterface.java +++ b/app/src/main/java/net/sourceforge/opencamera/MyApplicationInterface.java @@ -18,6 +18,7 @@ import net.sourceforge.opencamera.preview.ApplicationInterface; import net.sourceforge.opencamera.preview.BasicApplicationInterface; import net.sourceforge.opencamera.preview.Preview; import net.sourceforge.opencamera.preview.VideoProfile; +import net.sourceforge.opencamera.ui.CircleImageView; import net.sourceforge.opencamera.ui.DrawPreview; import android.annotation.TargetApi; @@ -1887,9 +1888,7 @@ public class MyApplicationInterface extends BasicApplicationInterface { panorama_pic_accepted = false; panorama_dir_left_to_right = true; - main_activity.getMainUI().setTakePhotoIcon(); - View cancelPanoramaButton = main_activity.findViewById(R.id.cancel_panorama); - cancelPanoramaButton.setVisibility(View.VISIBLE); + main_activity.getMainUI().handlePanoromaModeButtonsVisibility(); main_activity.getMainUI().closeExposureUI(); // close seekbars if open (popup is already closed when taking a photo) // taking the photo will end up calling MainUI.showGUI(), which will hide the other on-screen icons } @@ -1925,9 +1924,7 @@ public class MyApplicationInterface extends BasicApplicationInterface { if( is_cancelled ) { imageSaver.flushImageBatch(); } - main_activity.getMainUI().setTakePhotoIcon(); - View cancelPanoramaButton = main_activity.findViewById(R.id.cancel_panorama); - cancelPanoramaButton.setVisibility(View.GONE); + main_activity.getMainUI().handlePanoromaModeButtonsVisibility(); main_activity.getMainUI().showGUI(); // refresh UI icons now that we've stopped panorama } @@ -2352,6 +2349,9 @@ public class MyApplicationInterface extends BasicApplicationInterface { if( dategeo_subtitles && video_method != ApplicationInterface.VideoMethod.URI ) { startVideoSubtitlesTask(video_method); } + + ImageButton view = main_activity.findViewById(R.id.take_photo); + view.setImageResource(R.drawable.ic_camera_video_recording); } @Override @@ -2458,7 +2458,7 @@ public class MyApplicationInterface extends BasicApplicationInterface { } } if( thumbnail != null ) { - ImageButton galleryButton = main_activity.findViewById(R.id.gallery); + CircleImageView galleryButton = main_activity.findViewById(R.id.gallery); int width = thumbnail.getWidth(); int height = thumbnail.getHeight(); if( MyDebug.LOG ) @@ -2639,6 +2639,7 @@ public class MyApplicationInterface extends BasicApplicationInterface { @Override public void onCameraError() { + if (DeviceSettings.isMurenaTwo()) return; main_activity.getPreview().showToast(null, R.string.camera_error); } diff --git a/app/src/main/java/net/sourceforge/opencamera/MyDebug.java b/app/src/main/java/net/sourceforge/opencamera/MyDebug.java index 00e82f8f59e7016b25fcc99a8d0063e37661982b..f3ff6fff9820f6c3a911283991e303708090c767 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MyDebug.java +++ b/app/src/main/java/net/sourceforge/opencamera/MyDebug.java @@ -1,8 +1,9 @@ package net.sourceforge.opencamera; + /** Global constant to control logging, should always be set to false in * released versions. */ public class MyDebug { - public static final boolean LOG = false; + public static final boolean LOG = BuildConfig.DEBUG; } diff --git a/app/src/main/java/net/sourceforge/opencamera/MyPreferenceFragment.java b/app/src/main/java/net/sourceforge/opencamera/MyPreferenceFragment.java index 1947e707403fb6062cd6dd21f359527b1b03c6b0..24a38378c6d379f4b71101dd1c09f7481bc3f804 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MyPreferenceFragment.java +++ b/app/src/main/java/net/sourceforge/opencamera/MyPreferenceFragment.java @@ -66,6 +66,7 @@ import java.util.List; */ public class MyPreferenceFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener { private static final String TAG = "MyPreferenceFragment"; + public static final String BUILD_VERION = "build_version"; private int cameraId; @@ -86,6 +87,7 @@ public class MyPreferenceFragment extends PreferenceFragment implements OnShared Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); + findPreference(BUILD_VERION).setSummary(BuildConfig.VERSION_NAME); final Bundle bundle = getArguments(); this.cameraId = bundle.getInt("cameraId"); @@ -402,7 +404,7 @@ public class MyPreferenceFragment extends PreferenceFragment implements OnShared Log.d(TAG, "NameNotFoundException exception trying to get version number"); e.printStackTrace(); } - about_string.append("Open Camera v"); + about_string.append("Camera v"); about_string.append(version); about_string.append("\nCode: "); about_string.append(version_code); @@ -677,7 +679,7 @@ public class MyPreferenceFragment extends PreferenceFragment implements OnShared about_string.append("\nUsing SAF?: "); about_string.append(sharedPreferences.getBoolean(PreferenceKeys.UsingSAFPreferenceKey, false)); - String save_location = sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"); + String save_location = sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "Camera"); about_string.append("\nSave Location: "); about_string.append(save_location); String save_location_saf = sharedPreferences.getString(PreferenceKeys.SaveLocationSAFPreferenceKey, ""); diff --git a/app/src/main/java/net/sourceforge/opencamera/PreferenceKeys.java b/app/src/main/java/net/sourceforge/opencamera/PreferenceKeys.java index 8d12f7942754db20304eaf36ccdf4cf651d3b639..7938965b588c0904dd84adb442f6ac5b4ee4a567 100644 --- a/app/src/main/java/net/sourceforge/opencamera/PreferenceKeys.java +++ b/app/src/main/java/net/sourceforge/opencamera/PreferenceKeys.java @@ -37,7 +37,7 @@ public class PreferenceKeys { */ public static final String MagneticAccuracyPreferenceKey = "done_magnetic_accuracy"; - public static final String CameraAPIPreferenceDefault = "preference_camera_api_old"; + public static final String CameraAPIPreferenceDefault = "preference_camera_api_camera2"; public static final String CameraAPIPreferenceKey = "preference_camera_api"; public static String getFlashPreferenceKey(int cameraId) { diff --git a/app/src/main/java/net/sourceforge/opencamera/PreferenceSubPhoto.java b/app/src/main/java/net/sourceforge/opencamera/PreferenceSubPhoto.java index f6a6486288885ea1b8e5030b35e580d2a63a4f95..9ea7c30fd9e9eb0160037d1f655295e02fde473f 100644 --- a/app/src/main/java/net/sourceforge/opencamera/PreferenceSubPhoto.java +++ b/app/src/main/java/net/sourceforge/opencamera/PreferenceSubPhoto.java @@ -196,6 +196,18 @@ public class PreferenceSubPhoto extends PreferenceSubScreen { pg.removePreference(pref); } + if ( DeviceSettings.deviceUsingFakeFlash() ) { + if( MyDebug.LOG ) + Log.d(TAG, "set fake flash for camera2"); + Preference pref = findPreference(PreferenceKeys.Camera2FakeFlashPreferenceKey); + pref.setDefaultValue(true); + pref.setEnabled(false); + + final SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(PreferenceKeys.Camera2FakeFlashPreferenceKey, true); + editor.apply(); + } + if( !supports_expo_bracketing || max_expo_bracketing_n_images <= 3 ) { Preference pref = findPreference("preference_expo_bracketing_n_images"); //PreferenceGroup pg = (PreferenceGroup) this.findPreference("preference_screen_photo_settings"); diff --git a/app/src/main/java/net/sourceforge/opencamera/ScaleUtils.java b/app/src/main/java/net/sourceforge/opencamera/ScaleUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0b50e16b869b51b56657f363869c37057524f876 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ScaleUtils.java @@ -0,0 +1,17 @@ +package net.sourceforge.opencamera; + +import android.content.Context; + +public class ScaleUtils { + + private ScaleUtils() { + } + + public static float convertPxToDp(Context context, float px) { + return px / context.getResources().getDisplayMetrics().density; + } + + public static float convertDpToPx(Context context, float dp) { + return dp * context.getResources().getDisplayMetrics().density; + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/StorageUtils.java b/app/src/main/java/net/sourceforge/opencamera/StorageUtils.java index e42fce1d9dcf08aae8f504d096535f2b63918c25..d2d61cf762ead3c6d421f1949372ccc83b2c1c47 100644 --- a/app/src/main/java/net/sourceforge/opencamera/StorageUtils.java +++ b/app/src/main/java/net/sourceforge/opencamera/StorageUtils.java @@ -378,7 +378,7 @@ public class StorageUtils { // only valid if !isUsingSAF() String getSaveLocation() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - return sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "OpenCamera"); + return sharedPreferences.getString(PreferenceKeys.SaveLocationPreferenceKey, "Camera"); } // only valid if isUsingSAF() diff --git a/app/src/main/java/net/sourceforge/opencamera/Utils.java b/app/src/main/java/net/sourceforge/opencamera/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..761e8e0d81e994b1a03692ff03f100b1a6631077 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/Utils.java @@ -0,0 +1,19 @@ +package net.sourceforge.opencamera; + +import android.os.Build; + +import java.lang.reflect.Method; +import java.util.Locale; + +public class Utils { + public static String getProperty(String key, String defaultValue) { + try { + Class systemProperties = Class.forName("android.os.SystemProperties"); + Method get = systemProperties.getMethod("get", String.class, String.class); + return (String) get.invoke(null, key, defaultValue); + } catch (Exception e) { + e.printStackTrace(); + return defaultValue; + } + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinder.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..72451906591a09bc79a3f32dd91e1a85085ebd55 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinder.kt @@ -0,0 +1,91 @@ +package net.sourceforge.opencamera.camera2 + +import android.graphics.ImageFormat +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraManager +import android.hardware.camera2.CameraMetadata +import android.os.Build +import android.util.Size +import net.sourceforge.opencamera.camera2.model.Camera2ApiProperties +import net.sourceforge.opencamera.camera2.model.CameraModel +import net.sourceforge.opencamera.camera2.model.DerivedProperties +import java.util.function.Consumer + +class CameraFinder(cameraManager: CameraManager) : + CameraFinderAbstract>(cameraManager) { + init { + cameraModels = ArrayList() + } + + public override fun createModels() { + validCameraIds.forEach(Consumer { cameraModels.add(CameraModel(it.toInt())) }) + cameraModels.forEach(Consumer { + it.camera2ApiProperties = findProperties(it.id, getCameraCharacteristics(it.id)!!) + }) + cameraModels.forEach(Consumer { + it.derivedProperties = deriveProperties(it.id, it.camera2ApiProperties) + }) + } + + public override fun findProperties( + cameraId: Int, characteristics: CameraCharacteristics): Camera2ApiProperties { + return Camera2ApiProperties(cameraId).apply { + facing = characteristics.get(CameraCharacteristics.LENS_FACING)!! + focalLength = + characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)!![0] + aperture = + characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)!![0] + aeModes = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)!! + isFlashSupported = characteristics.get( + CameraCharacteristics.FLASH_INFO_AVAILABLE)!! + val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) + if (configMap?.getOutputSizes(ImageFormat.RAW_SENSOR) != null) { + rawSensorSizes = configMap.getOutputSizes(ImageFormat.RAW_SENSOR) + } + sensorSize = characteristics.get( + CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!! + pixelArraySize = characteristics.get( + CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)!! + supportedHardwareLevel = characteristics.get( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)!! + supportedHardwareLevelString = reflectionProvider.getResultFieldName( + CameraMetadata::class.java, + "INFO_SUPPORTED_HARDWARE_LEVEL_", + supportedHardwareLevel + ) + physicalIds = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + characteristics.physicalCameraIds + } else { + setOf() + } + } + } + + public override fun deriveProperties( + cameraId: Int, + camera2ApiProperties: Camera2ApiProperties + ): DerivedProperties { + return DerivedProperties(cameraId).apply { + facing = reflectionProvider.getResultFieldName( + CameraMetadata::class.java, + "LENS_FACING_", + camera2ApiProperties.facing + ) + isLogical = camera2ApiProperties.physicalIds.isNotEmpty() + angleOfView = CameraUtil.calculateAngleOfView( + camera2ApiProperties.focalLength, + camera2ApiProperties.sensorSize, + camera2ApiProperties.pixelArraySize + ) + pixelSize = CameraUtil.calculatePixelSize( + camera2ApiProperties.pixelArraySize.width, + camera2ApiProperties.sensorSize.width + ) + mm35FocalLength = + CameraUtil.calculate35mmeqv( + camera2ApiProperties.focalLength, + camera2ApiProperties.sensorSize + ) + } + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinderAbstract.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinderAbstract.kt new file mode 100644 index 0000000000000000000000000000000000000000..860dd367af725ad5b8253a28abf012341ed24c94 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraFinderAbstract.kt @@ -0,0 +1,68 @@ +package net.sourceforge.opencamera.camera2 + +import android.hardware.camera2.CameraAccessException +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraManager +import net.sourceforge.opencamera.camera2.model.Camera2ApiProperties +import net.sourceforge.opencamera.camera2.model.CameraModel +import net.sourceforge.opencamera.camera2.model.DerivedProperties +import java.util.* +import java.util.stream.Collectors + +abstract class CameraFinderAbstract>( + private val cameraManager: CameraManager +) : CameraIDs.Finder { + protected val validCameraIds: MutableList = ArrayList() + protected val reflectionProvider = ReflectionProvider() + override lateinit var cameraModels: T + protected set + + override fun init() { + scanCameras(cameraManager) + createModels() + } + + protected abstract fun createModels() + protected abstract fun findProperties( + cameraId: Int, + characteristics: CameraCharacteristics + ): Camera2ApiProperties + + protected abstract fun deriveProperties( + cameraId: Int, + camera2ApiProperties: Camera2ApiProperties + ): DerivedProperties + + private fun scanCameras(cameraManager: CameraManager) { + for (id in 0..511) { + try { + cameraManager.getCameraCharacteristics(id.toString()) + validCameraIds.add(id.toString()) + } catch (ignored: IllegalArgumentException) { + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + override fun getCameraCharacteristics(cameraId: Int): CameraCharacteristics? { + try { + return cameraManager.getCameraCharacteristics(cameraId.toString()) + } catch (e: CameraAccessException) { + e.printStackTrace() + } + return null + } + + override val apiCameraIdList: List + get() { + try { + return Arrays.stream(cameraManager.cameraIdList).collect(Collectors.toList()) + } catch (e: CameraAccessException) { + e.printStackTrace() + } + return ArrayList() + } + override val allCameraIdList: List + get() = validCameraIds +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIDs.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIDs.kt new file mode 100644 index 0000000000000000000000000000000000000000..059eb1a638634a7c2c027da217e47ca2034f90bc --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIDs.kt @@ -0,0 +1,19 @@ +package net.sourceforge.opencamera.camera2 + +import android.hardware.camera2.CameraCharacteristics +import net.sourceforge.opencamera.camera2.model.CameraModel + +interface CameraIDs { + interface Finder> { + fun init() + fun getCameraCharacteristics(cameraId: Int): CameraCharacteristics? + val cameraModels: T + val apiCameraIdList: List + val allCameraIdList: List + } + + interface Identifier> { + fun init() + fun identifyCamera(cameraModels: T) + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifier.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifier.kt new file mode 100644 index 0000000000000000000000000000000000000000..df908f1e1d898dc52785e0e47c83404784dc1a4a --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifier.kt @@ -0,0 +1,104 @@ +package net.sourceforge.opencamera.camera2 + +import android.hardware.camera2.CameraCharacteristics +import net.sourceforge.opencamera.camera2.model.Camera2ApiProperties +import net.sourceforge.opencamera.camera2.model.CameraModel +import net.sourceforge.opencamera.camera2.model.CameraType +import java.util.TreeSet +import java.util.function.Consumer +import kotlin.Comparator +import kotlin.collections.ArrayList + +class CameraIdentifier(cameraModels: ArrayList) : + CameraIdentifierAbstract>(cameraModels) { + private val frontCameraModels = ArrayList() + private val backCameraModels = ArrayList() + private val widerThanMain = TreeSet(SORT_BY_ANGLE_OF_VIEW) + private val narrowerThanMain = ArrayList() + private val propertiesArrayList = ArrayList() + override fun init() { + cameraModels.forEach(Consumer { + propertiesArrayList.add(it.camera2ApiProperties) + }) + frontOrBack(cameraModels) + identifyCamera(frontCameraModels) + identifyCamera(backCameraModels) + } + + private fun frontOrBack(cameraModels: Collection) { + for (model in cameraModels) { + if (model.camera2ApiProperties.facing == CameraCharacteristics.LENS_FACING_BACK) { + backCameraModels.add(model) + } + if (model.camera2ApiProperties.facing == CameraCharacteristics.LENS_FACING_FRONT) { + frontCameraModels.add(model) + } + } + } + + override fun identifyCamera(cameraModels: ArrayList) { + if (cameraModels.isNotEmpty()) { + val main = cameraModels[0] + main.cameraType = CameraType.MAIN + main.zoomFactor = 1f + cameraModels.removeAt(0) + + //Determine whether camera is logical + cameraModels.forEach(Consumer { model: CameraModel -> + if (model.derivedProperties.isLogical || getBit(6, model.id)) { + model.cameraType = CameraType.LOGICAL + } + propertiesArrayList.forEach(Consumer { + if (model.id != it.id && model.camera2ApiProperties == it) { + model.cameraType = CameraType.LOGICAL + } + }) + }) + cameraModels.removeIf { it.isTypeSet } + cameraModels.sortWith(SORT_BY_ANGLE_OF_VIEW) + cameraModels.forEach(Consumer { + val zoom = + it.derivedProperties.mm35FocalLength / main.derivedProperties.mm35FocalLength + it.zoomFactor = zoom + + //Determine whether camera is Depth or Other + it.cameraType = CameraType.OTHER + if (!it.camera2ApiProperties.isFlashSupported) { + it.cameraType = CameraType.DEPTH + } else { + if (it.derivedProperties.angleOfView > main.derivedProperties.angleOfView) { + widerThanMain.add(it) + } else { + narrowerThanMain.add(it) + } + } + }) + + //Determine whether camera is Ultrawide or Macro + widerThanMain.forEach(Consumer { + it.cameraType = if ( + it.derivedProperties.angleOfView == + widerThanMain.last().derivedProperties.angleOfView + ) { + CameraType.ULTRAWIDE + } else { + CameraType.MACRO + } + }) + + // Determine whether camera is Tele + narrowerThanMain.forEach(Consumer { cameraModel: CameraModel -> + cameraModel.cameraType = CameraType.TELE + }) + } + } + + private fun getBit(num: Int, `val`: Int): Boolean { + return `val` shr num - 1 and 1 == 1 + } + + companion object { + private val SORT_BY_ANGLE_OF_VIEW = + Comparator.comparingDouble { cameraModel: CameraModel -> cameraModel.derivedProperties.angleOfView } + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifierAbstract.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifierAbstract.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f2e1d8a82d0e19a22da30ebbcef91cb4e5e0bff --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraIdentifierAbstract.kt @@ -0,0 +1,7 @@ +package net.sourceforge.opencamera.camera2 + +import net.sourceforge.opencamera.camera2.model.CameraModel + +abstract class CameraIdentifierAbstract>( + protected val cameraModels: T +) : CameraIDs.Identifier diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/CameraUtil.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraUtil.kt new file mode 100644 index 0000000000000000000000000000000000000000..cfef5db5906bc8a456074eac8719fa7f484c63f3 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/CameraUtil.kt @@ -0,0 +1,26 @@ +package net.sourceforge.opencamera.camera2 + +import android.util.Size +import android.util.SizeF +import kotlin.math.atan +import kotlin.math.pow +import kotlin.math.sqrt + +object CameraUtil { + fun calculatePixelSize(pixelArrayWidth: Int, sensorWidth: Float): Float { + return sensorWidth / pixelArrayWidth.toFloat() * 1000.0f + } + + fun calculateAngleOfView( + focalLength: Float, sensorSize: SizeF, pixelArraySize: Size + ): Double { + val pixelSize = calculatePixelSize(pixelArraySize.width, sensorSize.width) + return Math.toDegrees(atan(sqrt((sensorSize.width * pixelSize).toDouble().pow(2.0) + + (sensorSize.height * pixelSize).toDouble().pow(2.0) + ) / (2.0f * focalLength).toDouble()) * 2.0) + } + + fun calculate35mmeqv(focalLength: Float, sensorSize: SizeF): Float { + return 36.0f / sensorSize.width * focalLength + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionApi.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionApi.kt new file mode 100644 index 0000000000000000000000000000000000000000..73240f6a4a5b48acc2e35f0aaababf08906446f9 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionApi.kt @@ -0,0 +1,9 @@ +package net.sourceforge.opencamera.camera2 + +import java.lang.reflect.Field +import java.lang.reflect.Method + +interface ReflectionApi { + fun getFields(aClass: Class<*>): Array + fun getMethods(aClass: Class<*>): Array +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionProvider.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..16400bed9149f9dbe4122378d3d312982b78ea51 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/ReflectionProvider.kt @@ -0,0 +1,25 @@ +package net.sourceforge.opencamera.camera2 + +import java.lang.reflect.Field +import java.lang.reflect.Method + +class ReflectionProvider : ReflectionApi { + override fun getFields(aClass: Class<*>): Array { + return aClass.declaredFields + } + + override fun getMethods(aClass: Class<*>): Array { + return aClass.declaredMethods + } + + fun getResultFieldName(aClass: Class<*>, prefix: String, value: Int): String { + for (f in getFields(aClass)) if (f.name.startsWith(prefix)) { + try { + if (f.getInt(f) == value) return f.name.replace(prefix, "") + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } + return "" + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/model/Camera2ApiProperties.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/model/Camera2ApiProperties.kt new file mode 100644 index 0000000000000000000000000000000000000000..d8bba062ae089cd1e41a3b1ed8c73e459a0561b0 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/model/Camera2ApiProperties.kt @@ -0,0 +1,35 @@ +package net.sourceforge.opencamera.camera2.model + +import android.util.Size +import android.util.SizeF +import java.util.Objects + +class Camera2ApiProperties(val id: Int) { + var facing = 0 + var focalLength = 0f + var aperture = 0f + lateinit var aeModes: IntArray + lateinit var rawSensorSizes: Array + lateinit var sensorSize: SizeF + lateinit var pixelArraySize: Size + var isFlashSupported = false + var supportedHardwareLevel = 0 + lateinit var supportedHardwareLevelString: String + lateinit var physicalIds: Set + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + val that = other as Camera2ApiProperties + return facing == that.facing && that.focalLength.compareTo(focalLength) == 0 && that.aperture.compareTo( + aperture + ) == 0 && isFlashSupported == that.isFlashSupported && + aeModes.contentEquals(that.aeModes) && sensorSize == that.sensorSize + } + + override fun hashCode(): Int { + var result = Objects.hash(facing, focalLength, aperture, sensorSize, isFlashSupported) + result = 31 * result + aeModes.contentHashCode() + return result + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraModel.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..eaa1d8ece9d6065e5a266dc1a71ae370c62fc39a --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraModel.kt @@ -0,0 +1,48 @@ +package net.sourceforge.opencamera.camera2.model + +import java.util.Locale +import kotlin.math.roundToInt + +class CameraModel(val id: Int) { + var cameraType: CameraType? = null + var zoomFactor = 0f + lateinit var camera2ApiProperties: Camera2ApiProperties + lateinit var derivedProperties: DerivedProperties + + val isTypeSet: Boolean + get() = cameraType != null + + fun cameraFullModel(): MutableMap { + return mutableMapOf().apply { + this["CameraID"] = "[$id]" + if (cameraType != CameraType.LOGICAL) { + this["CameraID"] += " \u2605" + } else if (camera2ApiProperties.physicalIds.isNotEmpty()) { + this["CameraID"] += + " = ${camera2ApiProperties.physicalIds.toString().replace(", ", "+")}" + } + + this["Facing"] = "${derivedProperties.facing}" + + if (cameraType != CameraType.LOGICAL) { + this["Zoom"] = String.format(Locale.ROOT, "%.2fx", zoomFactor) + .replace(".00", "") + } + + this["Type"] = "$cameraType" + this["FocalLength"] = String.format( + Locale.ROOT, "%.2fmm", camera2ApiProperties.focalLength) + this["35mm eqv FocalLength"] = String.format( + Locale.ROOT, "%.2fmm", derivedProperties.mm35FocalLength) + this["Aperture"] = "${camera2ApiProperties.aperture}" + this["SensorSize"] = "${camera2ApiProperties.sensorSize}" + this["PixelArray"] = "${camera2ApiProperties.pixelArraySize}" + this["PixelSize"] = String.format(Locale.ROOT, "%.2fµm", derivedProperties.pixelSize) + this["AngleOfView(Diagonal)"] = "${derivedProperties.angleOfView.roundToInt()}°" + this["AEModes"] = camera2ApiProperties.aeModes.contentToString() + this["FlashSupported"] = "${camera2ApiProperties.isFlashSupported}" + this["RAW_SENSOR sizes"] = camera2ApiProperties.rawSensorSizes.contentDeepToString() + this["SupportedHardwareLevel"] = camera2ApiProperties.supportedHardwareLevelString + } + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraType.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraType.kt new file mode 100644 index 0000000000000000000000000000000000000000..601886465efac1069f44b45ba50c5d0db0a136e2 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/model/CameraType.kt @@ -0,0 +1,11 @@ +package net.sourceforge.opencamera.camera2.model + +enum class CameraType { + MAIN, + ULTRAWIDE, + TELE, + MACRO, + DEPTH, + LOGICAL, + OTHER +} \ No newline at end of file diff --git a/app/src/main/java/net/sourceforge/opencamera/camera2/model/DerivedProperties.kt b/app/src/main/java/net/sourceforge/opencamera/camera2/model/DerivedProperties.kt new file mode 100644 index 0000000000000000000000000000000000000000..266a9349cdbd62bc036b4a03767711b77243fa8c --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/camera2/model/DerivedProperties.kt @@ -0,0 +1,9 @@ +package net.sourceforge.opencamera.camera2.model + +class DerivedProperties(val id: Int) { + var isLogical = false + var facing: String? = null + var angleOfView = 0.0 + var mm35FocalLength = 0f + var pixelSize = 0f +} diff --git a/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController1.java b/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController1.java index 3e573d219d3295fbff10e7fc807491076cbc580f..3596a0e557959e9a24a0ebcc7024ba0afe001312 100644 --- a/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController1.java +++ b/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController1.java @@ -3,6 +3,7 @@ package net.sourceforge.opencamera.cameracontroller; import net.sourceforge.opencamera.MyDebug; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -58,15 +59,28 @@ public class CameraController1 extends CameraController { if( MyDebug.LOG ) Log.d(TAG, "create new CameraController1: " + cameraId); this.camera_error_cb = camera_error_cb; + try { - camera = Camera.open(cameraId); + Method openMethod = Class.forName("android.hardware.Camera") + .getMethod("openLegacy", int.class, int.class); + camera = (Camera) openMethod.invoke(null, + cameraId, 0x100); + } catch (Exception e) { + /* Retry with open if openLegacy doesn't exist/fails */ + camera = null; } - catch(RuntimeException e) { - if( MyDebug.LOG ) - Log.e(TAG, "failed to open camera"); - e.printStackTrace(); - throw new CameraControllerException(); + + if (camera == null) { + try { + camera = Camera.open(cameraId); + } catch (RuntimeException e) { + if (MyDebug.LOG) + Log.e(TAG, "failed to open camera"); + e.printStackTrace(); + throw new CameraControllerException(); + } } + if( camera == null ) { // Although the documentation says Camera.open() should throw a RuntimeException, it seems that it some cases it can return null // I've seen this in some crashes reported in Google Play; also see: diff --git a/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController2.java b/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController2.java index c94d6d938d243cb3b2fe46fc7daa96fc306b3254..d0abeb0b09f7c68b0540145733f0829d2ff58fea 100644 --- a/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController2.java +++ b/app/src/main/java/net/sourceforge/opencamera/cameracontroller/CameraController2.java @@ -1,7 +1,9 @@ package net.sourceforge.opencamera.cameracontroller; +import net.sourceforge.opencamera.ContinuousBurstImageRunningAction; import net.sourceforge.opencamera.HDRProcessor; import net.sourceforge.opencamera.MyDebug; +import net.sourceforge.opencamera.R; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -63,6 +65,9 @@ import android.view.Display; import android.view.Surface; import android.view.SurfaceHolder; import android.view.TextureView; +import android.widget.Toast; + +import org.greenrobot.eventbus.EventBus; /** Provides support using Android 5's Camera 2 API * android.hardware.camera2.*. @@ -75,6 +80,7 @@ public class CameraController2 extends CameraController { private CameraDevice camera; private final String cameraIdS; + private final boolean is_fairphone; private final boolean is_samsung; private final boolean is_samsung_s7; // Galaxy S7 or Galaxy S7 Edge private final boolean is_samsung_galaxy_s; @@ -100,6 +106,8 @@ public class CameraController2 extends CameraController { private long min_exposure_time; private long max_exposure_time; + private int toastCounter = 0; + private final static int tonemap_log_max_curve_points_c = 64; private final static float [] jtvideo_values_base = new float[] { 0.00f, 0.00f, @@ -180,7 +188,7 @@ public class CameraController2 extends CameraController { private boolean previewIsVideoMode; private AutoFocusCallback autofocus_cb; private long autofocus_time_ms = -1; // time we set autofocus_cb to non-null - private static final long autofocus_timeout_c = 1000; // timeout for calling autofocus_cb (applies for both auto and continuous focus) + private static final long autofocus_timeout_c = 500; // timeout for calling autofocus_cb (applies for both auto and continuous focus) private boolean capture_follows_autofocus_hint; private boolean ready_for_capture; private FaceDetectionListener face_detection_listener; @@ -265,8 +273,8 @@ public class CameraController2 extends CameraController { private static final int STATE_WAITING_FAKE_PRECAPTURE_DONE = 5; private int state = STATE_NORMAL; private long precapture_state_change_time_ms = -1; // time we changed state for precapture modes - private static final long precapture_start_timeout_c = 2000; - private static final long precapture_done_timeout_c = 3000; + private static final long precapture_start_timeout_c = 500; + private static final long precapture_done_timeout_c = 1000; private boolean use_fake_precapture; // see CameraController.setUseCamera2FakeFlash() for details - this is the user/application setting, see use_fake_precapture_mode for whether fake precapture is enabled (as we may do this for other purposes, e.g., front screen flash) private boolean use_fake_precapture_mode; // true if either use_fake_precapture is true, or we're temporarily using fake precapture mode (e.g., for front screen flash or exposure bracketing) @@ -1634,6 +1642,9 @@ public class CameraController2 extends CameraController { takePhotoCompleted(); } + if (n_burst_taken == 1) { + EventBus.getDefault().post(new ContinuousBurstImageRunningAction()); + } if( MyDebug.LOG ) Log.d(TAG, "done onImageAvailable"); } @@ -2050,10 +2061,12 @@ public class CameraController2 extends CameraController { this.camera_error_cb = camera_error_cb; //this.is_oneplus = Build.MANUFACTURER.toLowerCase(Locale.US).contains("oneplus"); + this.is_fairphone = Build.MANUFACTURER.toLowerCase(Locale.US).contains("fairphone"); this.is_samsung = Build.MANUFACTURER.toLowerCase(Locale.US).contains("samsung"); this.is_samsung_s7 = Build.MODEL.toLowerCase(Locale.US).contains("sm-g93"); this.is_samsung_galaxy_s = is_samsung && Build.MODEL.toLowerCase(Locale.US).contains("sm-g"); if( MyDebug.LOG ) { + Log.d(TAG, "is_fairphone: " + is_fairphone); Log.d(TAG, "is_samsung: " + is_samsung); Log.d(TAG, "is_samsung_s7: " + is_samsung_s7); Log.d(TAG, "is_samsung_galaxy_s: " + is_samsung_galaxy_s); @@ -2901,6 +2914,27 @@ public class CameraController2 extends CameraController { camera_features.picture_sizes.add(new CameraController.Size(camera_size.getWidth(), camera_size.getHeight())); } } + + if(is_fairphone && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + CameraCharacteristics.Key qcfaDimensionKey = + new CameraCharacteristics.Key<>("org.codeaurora.qcamera3.quadra_cfa.qcfa_dimension", int[].class); + try { + int[] qcfaDimension = characteristics.get(qcfaDimensionKey); + if (qcfaDimension != null && qcfaDimension.length == 2) { + int qcfaWidth = qcfaDimension[0]; + int qcfaHeight = qcfaDimension[1]; + Size qcfaSize = new Size(qcfaWidth, qcfaHeight); + if (!camera_features.picture_sizes.contains(qcfaSize)) { + if (MyDebug.LOG) + Log.d(TAG, "Adding QCFA size: " + qcfaWidth + " x " + qcfaHeight); + camera_features.picture_sizes.add(qcfaSize); + } + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + // sizes are usually already sorted from high to low, but sort just in case // note some devices do have sizes in a not fully sorted order (e.g., Nokia 8) Collections.sort(camera_features.picture_sizes, new CameraController.SizeSorter()); @@ -8402,14 +8436,17 @@ public class CameraController2 extends CameraController { // need to check af_state != null, I received Google Play crash in 1.33 where it was null boolean focus_success = af_state != null && ( af_state == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || af_state == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED ); if( MyDebug.LOG ) { - if( focus_success ) + if( focus_success ) { Log.d(TAG, "autofocus success"); - else + } else { Log.d(TAG, "autofocus failed"); - if( af_state == null ) + onFocusFailed(); + } + if( af_state == null ) { Log.e(TAG, "continuous focus mode but af_state is null"); - else + } else { Log.d(TAG, "af_state: " + af_state); + } } if( af_state == null ) { test_af_state_null_focus++; @@ -8462,10 +8499,12 @@ public class CameraController2 extends CameraController { ) { boolean focus_success = af_state == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || af_state == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED; if( MyDebug.LOG ) { - if( focus_success ) + if( focus_success ) { Log.d(TAG, "onCaptureCompleted: autofocus success"); - else + } else { Log.d(TAG, "onCaptureCompleted: autofocus failed"); + onFocusFailed(); + } Log.d(TAG, "af_state: " + af_state); } state = STATE_NORMAL; @@ -8655,6 +8694,18 @@ public class CameraController2 extends CameraController { } } + private void onFocusFailed() { + // Show only if focus failed twice. + if (toastCounter == 1) { + Toast.makeText(context, + R.string.preference_failed_focus_mode_message, + Toast.LENGTH_SHORT).show(); + toastCounter = 0; + } else { + toastCounter++; + } + } + private void handleContinuousFocusMove(CaptureResult result) { Integer af_state = result.get(CaptureResult.CONTROL_AF_STATE); if( af_state != null && af_state == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN && af_state != last_af_state ) { diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..ba23aedf8f95fd5dcc2af7b622197ea488510058 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/AddressBookParsedResult.kt @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.ContactsContract +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.AddressBookParsedResult +import net.sourceforge.opencamera.R + +fun AddressBookParsedResult.createIntent() = Intent( + Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI +).apply { + names.firstOrNull()?.let { + putExtra(ContactsContract.Intents.Insert.NAME, it) + } + + pronunciation?.let { + putExtra(ContactsContract.Intents.Insert.PHONETIC_NAME, it) + } + + phoneNumbers?.let { phoneNumbers -> + val phoneTypes = phoneTypes ?: arrayOf() + + for ((key, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.PHONE, + ContactsContract.Intents.Insert.PHONE_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.SECONDARY_PHONE, + ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.TERTIARY_PHONE, + ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE, + ), + ).withIndex()) { + phoneNumbers.getOrNull(key)?.let { phone -> + putExtra(keys.first(), phone) + phoneTypes.getOrNull(key)?.let { + putExtra(keys.last(), it) + } + } + } + } + + emails?.let { emails -> + val emailTypes = emailTypes ?: arrayOf() + + for ((key, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.EMAIL, + ContactsContract.Intents.Insert.EMAIL_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.SECONDARY_EMAIL, + ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE, + ), + listOf( + ContactsContract.Intents.Insert.TERTIARY_EMAIL, + ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE, + ), + ).withIndex()) { + emails.getOrNull(key)?.let { phone -> + putExtra(keys.first(), phone) + emailTypes.getOrNull(key)?.let { + putExtra(keys.last(), it) + } + } + } + } + + instantMessenger?.let { + putExtra(ContactsContract.Intents.Insert.IM_HANDLE, it) + } + + note?.let { + putExtra(ContactsContract.Intents.Insert.NOTES, it) + } + + addresses?.let { emails -> + val addressTypes = addressTypes ?: arrayOf() + + for ((key, keys) in listOf( + listOf( + ContactsContract.Intents.Insert.POSTAL, + ContactsContract.Intents.Insert.POSTAL_TYPE, + ), + ).withIndex()) { + emails.getOrNull(key)?.let { phone -> + putExtra(keys.first(), phone) + addressTypes.getOrNull(key)?.let { + putExtra(keys.last(), it) + } + } + } + } + + org?.let { + putExtra(ContactsContract.Intents.Insert.COMPANY, it) + } +} + +fun AddressBookParsedResult.createTextClassification( + context: Context +) = TextClassification.Builder() + .setText(title ?: names.firstOrNull() ?: "") + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_contact_phone, + R.string.qr_address_title, + R.string.qr_address_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..a76232ea0deee74a79b79080a09607d940c0f3c5 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/CalendarParsedResult.kt @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.CalendarContract +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.core.os.bundleOf +import com.google.zxing.client.result.CalendarParsedResult +import net.sourceforge.opencamera.R + +fun CalendarParsedResult.createIntent() = Intent( + Intent.ACTION_INSERT, CalendarContract.Events.CONTENT_URI +).apply { + summary?.let { + putExtra(CalendarContract.Events.TITLE, it) + } + description?.let { + putExtra(CalendarContract.Events.DESCRIPTION, it) + } + location?.let { + putExtra(CalendarContract.Events.EVENT_LOCATION, it) + } + organizer?.let { + putExtra(CalendarContract.Events.ORGANIZER, it) + } + attendees?.let { + putExtra(Intent.EXTRA_EMAIL, it.joinToString(",")) + } + + putExtras( + bundleOf( + CalendarContract.EXTRA_EVENT_BEGIN_TIME to startTimestamp, + CalendarContract.EXTRA_EVENT_END_TIME to endTimestamp, + CalendarContract.EXTRA_EVENT_ALL_DAY to (isStartAllDay && isEndAllDay), + ) + ) +} + +fun CalendarParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(summary) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_calendar_add_on, + R.string.qr_calendar_title, + R.string.qr_calendar_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt b/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt new file mode 100644 index 0000000000000000000000000000000000000000..4026fb52ee304448cbbbdf029b665cd6bde33690 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/Context.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.Context +import android.util.TypedValue +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt + +@ColorInt +fun Context.getThemeColor(@AttrRes attribute: Int) = TypedValue().let { + theme.resolveAttribute(attribute, it, true) + it.data +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..764e70016e8975450a4c3ca06b5c19f1c32a821b --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/EmailAddressParsedResult.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.core.os.bundleOf +import com.google.zxing.client.result.EmailAddressParsedResult +import net.sourceforge.opencamera.R + +fun EmailAddressParsedResult.createIntent() = Intent( + Intent.ACTION_SENDTO, + Uri.parse("mailto:${tos?.firstOrNull() ?: ""}") +).apply { + putExtras( + bundleOf( + Intent.EXTRA_EMAIL to tos, + Intent.EXTRA_CC to cCs, + Intent.EXTRA_BCC to bcCs, + Intent.EXTRA_SUBJECT to subject, + Intent.EXTRA_TEXT to body, + ) + ) +} + +fun EmailAddressParsedResult.createTextClassification( + context: Context +) = TextClassification.Builder() + .setText(tos.joinToString()) + .setEntityType(TextClassifier.TYPE_EMAIL, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_email, + R.string.qr_email_title, + R.string.qr_email_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..ecac26eeb6dbf210a1d8b342c7c5e585240814d3 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/GeoParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.GeoParsedResult +import net.sourceforge.opencamera.R + +fun GeoParsedResult.createIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(geoURI)) + +fun GeoParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(displayResult) + .setEntityType(TextClassifier.TYPE_ADDRESS, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_location_on, + R.string.qr_geo_title, + R.string.qr_geo_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..44f6bd8c79eb6503677008691329d447d83c8d46 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ISBNParsedResult.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.ISBNParsedResult +import net.sourceforge.opencamera.R + +fun ISBNParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://isbnsearch.org/isbn/${isbn}") +) + +fun ISBNParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(isbn) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_book, + R.string.qr_isbn_title, + R.string.qr_isbn_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b0d19456afd94def412d71280151712a7775823 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import androidx.camera.core.ImageProxy +import com.google.zxing.PlanarYUVLuminanceSource + +private fun rotateYUVLuminancePlane(data: ByteArray, width: Int, height: Int): ByteArray { + val yuv = ByteArray(width * height) + // Rotate the Y luma + var i = 0 + for (x in 0 until width) { + for (y in height - 1 downTo 0) { + yuv[i] = data[y * width + x] + i++ + } + } + return yuv +} + +internal val ImageProxy.planarYUVLuminanceSource: PlanarYUVLuminanceSource + get() { + val plane = planes[0] + val buffer = plane.buffer + var bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + + var width = width + var height = height + + if (imageInfo.rotationDegrees == 90 || imageInfo.rotationDegrees == 270) { + bytes = rotateYUVLuminancePlane(bytes, width, height) + width = height.also { height = width } + } + + return PlanarYUVLuminanceSource( + bytes, width, height, 0, 0, width, height, true + ) + } diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt b/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3e8d83ff0a2df53e9291e54075e90107e68a8ba --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/Int.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.res.Resources.getSystem +import android.util.Range +import kotlin.math.roundToInt + +val Int.px + get() = (this * getSystem().displayMetrics.density).roundToInt() + +val Int.dp + get() = (this / getSystem().displayMetrics.density).roundToInt() + +internal fun Int.Companion.mapToRange(range: Range, percentage: Float): Int { + return (((range.upper - range.lower) * percentage) + range.lower).roundToInt() +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..a35f89e48e0da9e75ef4af3275a889f855451c6c --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ParsedResult.kt @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.content.Context +import com.google.zxing.client.result.AddressBookParsedResult +import com.google.zxing.client.result.CalendarParsedResult +import com.google.zxing.client.result.EmailAddressParsedResult +import com.google.zxing.client.result.GeoParsedResult +import com.google.zxing.client.result.ISBNParsedResult +import com.google.zxing.client.result.ParsedResult +import com.google.zxing.client.result.ProductParsedResult +import com.google.zxing.client.result.SMSParsedResult +import com.google.zxing.client.result.TelParsedResult +import com.google.zxing.client.result.TextParsedResult +import com.google.zxing.client.result.URIParsedResult +import com.google.zxing.client.result.VINParsedResult +import com.google.zxing.client.result.WifiParsedResult + +fun ParsedResult.createTextClassification(context: Context) = when (this) { + is AddressBookParsedResult -> createTextClassification(context) + + is CalendarParsedResult -> createTextClassification(context) + + is EmailAddressParsedResult -> createTextClassification(context) + + is GeoParsedResult -> createTextClassification(context) + + is ISBNParsedResult -> createTextClassification(context) + + is ProductParsedResult -> createTextClassification(context) + + is SMSParsedResult -> createTextClassification(context) + + is TelParsedResult -> createTextClassification(context) + + is TextParsedResult -> null // Try with the next methods + + is URIParsedResult -> null // We handle this manually + + is VINParsedResult -> createTextClassification(context) + + is WifiParsedResult -> createTextClassification(context) + + else -> null +} + diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..4c43b9c40a1bd738bc103de032e5123a1f662386 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/ProductParsedResult.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.ProductParsedResult +import net.sourceforge.opencamera.R + +fun ProductParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://www.barcodelookup.com/${productID}") +) + +fun ProductParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(productID) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_shopping_cart, + R.string.qr_product_title, + R.string.qr_product_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt b/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt new file mode 100644 index 0000000000000000000000000000000000000000..bc510110112ae93a994e22ee5b95e1915ad36a28 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/RemoteAction.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.PendingIntent +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Icon +import androidx.annotation.AttrRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.toBitmap +import kotlin.reflect.KClass + +fun KClass.build( + context: Context, + @DrawableRes iconRes: Int, + @StringRes titleRes: Int, + @StringRes contentDescriptionRes: Int, + intent: Intent, + requestCode: Int = 0, + flags: Int = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + @AttrRes iconTint: Int = com.google.android.material.R.attr.colorOnBackground, +) = RemoteAction( + Icon.createWithBitmap( + AppCompatResources.getDrawable(context, iconRes)?.let { + DrawableCompat.wrap(it.mutate()).apply { + DrawableCompat.setTint( + this, + context.getThemeColor(iconTint) + ) + } + }?.toBitmap() + ), + context.getString(titleRes), + context.getString(contentDescriptionRes), + PendingIntent.getActivity(context, requestCode, intent, flags) +) diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..cdb2cfd5de5cb7bef825ee744677d25ee779a921 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/SMSParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.SMSParsedResult +import net.sourceforge.opencamera.R + +fun SMSParsedResult.createIntent() = Intent(Intent.ACTION_SENDTO, Uri.parse(smsuri)) + +fun SMSParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(numbers.first()) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_sms, + R.string.qr_sms_title, + R.string.qr_sms_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..a2af666fe80a598a79da30bae3775ac155fa4408 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/TelParsedResult.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.TelParsedResult +import net.sourceforge.opencamera.R + +fun TelParsedResult.createIntent() = Intent(Intent.ACTION_SENDTO, Uri.parse(telURI)) + +fun TelParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(number) + .setEntityType(TextClassifier.TYPE_PHONE, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_phone, + R.string.qr_tel_title, + R.string.qr_tel_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..a27c3aebd34cc3bb7a9e4eef7a380b2e1bddbba6 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/VINParsedResult.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.client.result.VINParsedResult +import net.sourceforge.opencamera.R + +fun VINParsedResult.createIntent() = Intent( + Intent.ACTION_VIEW, Uri.parse("https://www.vindecoderz.com/EN/check-lookup/${vin}") +) + +fun VINParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(vin) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_directions_car, + R.string.qr_vin_title, + R.string.qr_vin_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt b/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..c523f3e888b5b7e8db0b512cf7aaf75a9a961b85 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ext/WifiParsedResult.kt @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.ext + + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.wifi.WifiNetworkSuggestion +import android.os.Build +import android.provider.Settings +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import androidx.annotation.RequiresApi +import com.google.zxing.client.result.WifiParsedResult +import net.sourceforge.opencamera.R + +@RequiresApi(Build.VERSION_CODES.R) +fun WifiParsedResult.createIntent() = Intent(Settings.ACTION_WIFI_ADD_NETWORKS).apply { + putExtra( + Settings.EXTRA_WIFI_NETWORK_LIST, + arrayListOf( + WifiNetworkSuggestion.Builder() + .setSsid(ssid) + .setIsHiddenSsid(isHidden) + .apply { + password?.let { + when (networkEncryption) { + "WPA" -> { + // Per specs, Wi-Fi QR codes are only used for + // WPA2 and WPA-Mixed networks, we can safely assume + // this networks supports WPA2 + setWpa2Passphrase(it) + } + + "SAE" -> { + setWpa3Passphrase(it) + } + } + } + } + .build() + ) + ) +} + +fun WifiParsedResult.createTextClassification(context: Context) = TextClassification.Builder() + .setText(ssid) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_network_wifi, + R.string.qr_wifi_title, + R.string.qr_wifi_content_description, + createIntent() + ) + ) + } + } + .build() diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java b/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java new file mode 100644 index 0000000000000000000000000000000000000000..90113f94c4833b92d2aa18afde2de5b9ccf86687 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java @@ -0,0 +1,64 @@ +/* + * Copyright MURENA SAS 2024 + * 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 . + */ + +package net.sourceforge.opencamera.preview; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.view.View; + +import androidx.core.content.ContextCompat; + +import net.sourceforge.opencamera.R; + +public class OverlayQRCodeView extends View { + private Drawable qrcode; + // Do not draw the additional resource if something goes wrong + private boolean isValid = true; + + public OverlayQRCodeView(Context context) { + super(context); + init(); + } + + private void init() { + qrcode = ContextCompat.getDrawable(this.getContext(), R.drawable.scan_area); + DisplayMetrics displayMetrics =this.getContext().getResources().getDisplayMetrics(); + final int screenWidth = displayMetrics.widthPixels; + final int screenHeight = displayMetrics.heightPixels; + + final int left = (screenWidth - qrcode.getIntrinsicWidth()) / 2; + final int top = (screenHeight - qrcode.getIntrinsicHeight()) / 2; + final int right = left + qrcode.getIntrinsicWidth(); + final int bottom = top + qrcode.getIntrinsicHeight(); + + if (left <= 0 || top <= 0 || right <= 0 || bottom <= 0) { + isValid = false; + } else { + qrcode.setBounds(left, top, right, bottom); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (isValid) { + qrcode.draw(canvas); + } + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index cc1698d2201b24efd15dcb501331c2146a38cb6e..78fcee29035b7b73cda493159121926df0698112 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -3,8 +3,8 @@ package net.sourceforge.opencamera.preview; import net.sourceforge.opencamera.HDRProcessor; import net.sourceforge.opencamera.JavaImageFunctions; import net.sourceforge.opencamera.JavaImageProcessing; +import net.sourceforge.opencamera.MainActivity; import net.sourceforge.opencamera.cameracontroller.RawImage; -//import net.sourceforge.opencamera.MainActivity; import net.sourceforge.opencamera.MyDebug; import net.sourceforge.opencamera.R; import net.sourceforge.opencamera.ScriptC_histogram_compute; @@ -101,6 +101,15 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.RGBLuminanceSource; + /** This class was originally named due to encapsulating the camera preview, * but in practice it's grown to more than this, and includes most of the * operation of the camera. It exists at a higher level than CameraController @@ -198,7 +207,15 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private AsyncTask open_camera_task; // background task used for opening camera private CloseCameraTask close_camera_task; // background task used for closing camera private boolean has_permissions = true; // whether we have permissions necessary to operate the camera (camera, storage); assume true until we've been denied one of them - private boolean is_video; + public static enum FunctionalMode { + PHOTO, + VIDEO, + QRCODE + }; + private FunctionalMode functionalMode = FunctionalMode.PHOTO; + private boolean is_video() {return functionalMode == FunctionalMode.VIDEO;}; + private boolean is_qrcode() {return functionalMode == FunctionalMode.QRCODE;}; + private boolean is_photo() {return functionalMode == FunctionalMode.PHOTO;}; private volatile MediaRecorder video_recorder; // must be volatile for test project reading the state private volatile boolean video_start_time_set; // must be volatile for test project reading the state private long video_start_time; // system time when the video recording was started, or last resumed if it was paused @@ -211,6 +228,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu * Important to call close() when the video recording is finished, to free up any resources * (e.g., supplied ParcelFileDescriptor). */ + + private OverlayQRCodeView overlayQRCodeView; private static class VideoFileInfo { private final ApplicationInterface.VideoMethod video_method; private final Uri video_uri; // for VideoMethod.SAF, VideoMethod.URI or VideoMethod.MEDIASTORE @@ -487,6 +506,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( canvasView != null ) { parent.addView(canvasView); } + + overlayQRCodeView = new OverlayQRCodeView(getContext()); } /*private void previewToCamera(float [] coords) { @@ -690,14 +711,14 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "handleSingleTouch"); - if( !this.is_video && this.isTakingPhotoOrOnTimer() ) { + if( !this.is_video() && this.isTakingPhotoOrOnTimer() ) { // if video, okay to refocus when recording return true; } // note, we always try to force start the preview (in case is_preview_paused has become false) // except if recording video (firstly, the preview should be running; secondly, we don't want to reset the phase!) - if( !this.is_video ) { + if( !this.is_video() ) { startCameraPreview(); } cancelAutoFocus(); @@ -795,7 +816,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu has_smooth_zoom = true; smooth_zoom = zoom_ratios.get(multitouch_start_zoom_factor)/100.0f; } - else { + else { has_multitouch_start_zoom_factor = false; multitouch_start_zoom_factor = 0; has_smooth_zoom = false; @@ -1106,6 +1127,40 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture arg0) { refreshPreviewBitmap(); + + if (isQRCode()) { + enablePreviewBitmap(); + TextureView textureView = (TextureView) this.cameraSurface; + Bitmap bitmap = textureView.getBitmap(preview_bitmap); + + if (bitmap!=null) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + LuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + Reader reader = new MultiFormatReader(); + + try { + Result result = reader.decode(binaryBitmap); + String qrcodeContent = result.getText(); + MainActivity mActivity = (MainActivity) this.getContext(); + if (MyDebug.LOG) + Log.d(TAG, "Find QRCode qrcodeContent="+qrcodeContent ); + if (result.getBarcodeFormat() == BarcodeFormat.QR_CODE) { + mActivity.qrImageAnalyzer.showQrDialog(result); + } + } catch (Exception e) { + // K1ZFP TODO Error 2 + } + } else { + // K1ZFP TODO Error 1 + } + } else { + disablePreviewBitmap(); + } } private void configureTransform() { @@ -1276,7 +1331,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } if( due_to_max_filesize || remaining_restart_video > 0 ) { - if( is_video ) { + if( is_video() ) { String toast = null; if( !due_to_max_filesize ) toast = remaining_restart_video + " " + getContext().getResources().getString(R.string.repeats_to_go); @@ -1657,7 +1712,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu current_focus_index = -1; max_num_focus_areas = 0; applicationInterface.cameraInOperation(false, false); - if( is_video ) + if( is_video() ) applicationInterface.cameraInOperation(false, true); if( !this.has_surface ) { if( MyDebug.LOG ) { @@ -2037,7 +2092,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu saved_is_video = false; } // must switch video before setupCameraParameters(), and starting preview - if( saved_is_video != this.is_video ) { + if( saved_is_video != this.is_video() ) { if( MyDebug.LOG ) Log.d(TAG, "switch video mode as not in correct mode"); this.switchVideo(true, false); @@ -2088,7 +2143,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu updateFlashForVideo(); if( take_photo ) { - if( this.is_video ) { + if( this.is_video() ) { if( MyDebug.LOG ) Log.d(TAG, "switch to video for take_photo widget"); this.switchVideo(true, true); @@ -2097,8 +2152,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // must be done after switching to video mode (so is_video is set correctly) if( MyDebug.LOG ) - Log.d(TAG, "is_video?: " + is_video); - if( this.is_video ) { + Log.d(TAG, "is_video?: " + is_video()); + if( this.is_video() ) { CameraController.TonemapProfile tonemap_profile = CameraController.TonemapProfile.TONEMAPPROFILE_OFF; if( supports_tonemap_curve ) { tonemap_profile = applicationInterface.getVideoTonemapProfile(); @@ -2117,7 +2172,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // Setup for high speed - must be done after setupCameraParameters() and switching to video mode, but before setPreviewSize() and startCameraPreview(). // In theory it shouldn't matter if we call setVideoHighSpeed(true) if is_video==false, as it should only have an effect // when recording video; but don't set high speed mode in photo mode just to be safe. - camera_controller.setVideoHighSpeed(is_video && video_high_speed); + camera_controller.setVideoHighSpeed(is_video() && video_high_speed); if( do_startup_focus && using_android_l && camera_controller.supportsAutoFocus() ) { // need to switch flash off for autofocus - and for Android L, need to do this before starting preview (otherwise it won't work in time); for old camera API, need to do this after starting preview! @@ -2276,9 +2331,13 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } - private int find1xZoom() { + public int find1xZoom() { + return findNxZoom(1.0f); + } + + public int findNxZoom(float zoom) { for(int i=0;i= Build.VERSION_CODES.JELLY_BEAN - ) { + ) { int n_faces = local_faces.length; FaceLocation face_location = FaceLocation.FACELOCATION_UNKNOWN; if( n_faces > 0 ) { @@ -2634,10 +2693,10 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu { if( MyDebug.LOG ) { Log.d(TAG, "set up video stabilization"); - Log.d(TAG, "is_video?: " + is_video); + Log.d(TAG, "is_video?: " + is_video()); } if( this.supports_video_stabilization ) { - boolean using_video_stabilization = is_video && applicationInterface.getVideoStabilizationPref(); + boolean using_video_stabilization = is_video() && applicationInterface.getVideoStabilizationPref(); if( MyDebug.LOG ) Log.d(TAG, "using_video_stabilization?: " + using_video_stabilization); camera_controller.setVideoStabilization(using_video_stabilization); @@ -3083,7 +3142,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "check if we need high speed video for " + profile.videoFrameWidth + " x " + profile.videoFrameHeight + " at fps " + profile.videoCaptureRate); CameraController.Size best_video_size = video_quality_handler.findVideoSizeForFrameRate(profile.videoFrameWidth, profile.videoFrameHeight, profile.videoCaptureRate, false); - // n.b., we should pass videoCaptureRate and not videoFrameRate (as for slow motion, it's videoCaptureRate that will be high, not videoFrameRate) + // n.b., we should pass videoCaptureRate and not videoFrameRate (as for slow motion, it's videoCaptureRate that will be high, not videoFrameRate) if( best_video_size == null && fpsIsHighSpeed("" + profile.videoFrameRate) && video_quality_handler.getSupportedVideoSizesHighSpeed() != null ) { Log.e(TAG, "can't find match for capture rate: " + profile.videoCaptureRate + " and video size: " + profile.videoFrameWidth + " x " + profile.videoFrameHeight + " at fps " + profile.videoCaptureRate); @@ -3137,7 +3196,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "video_high_speed?: " + video_high_speed); } - if( is_video && video_high_speed && supports_iso_range && is_manual_iso ) { + if( is_video() && video_high_speed && supports_iso_range && is_manual_iso ) { if( MyDebug.LOG ) Log.d(TAG, "manual mode not supported for video_high_speed"); camera_controller.setManualISO(false, 0); @@ -3290,7 +3349,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } // first set picture size (for photo mode, must be done now so we can set the picture size from this; for video, doesn't really matter when we set it) CameraController.Size new_size; - if( this.is_video ) { + if( this.is_video() ) { // see comments for getOptimalVideoPictureSize() VideoProfile profile = getVideoProfile(); if( MyDebug.LOG ) @@ -3820,8 +3879,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu String preview_size = applicationInterface.getPreviewSizePref(); // should always use wysiwig for video mode, otherwise we get incorrect aspect ratio shown when recording video (at least on Galaxy Nexus, e.g., at 640x480) // also not using wysiwyg mode with video caused corruption on Samsung cameras (tested with Samsung S3, Android 4.3, front camera, infinity focus) - if( preview_size.equals("preference_preview_size_wysiwyg") || this.is_video ) { - if( this.is_video ) { + if( preview_size.equals("preference_preview_size_wysiwyg") || this.is_video() ) { + if( this.is_video() ) { if( MyDebug.LOG ) Log.d(TAG, "set preview aspect ratio from video size (wysiwyg)"); VideoProfile profile = getVideoProfile(); @@ -3879,7 +3938,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu final double ASPECT_TOLERANCE = 0.05; if( sizes == null ) return null; - if( is_video && video_high_speed ) { + if( is_video() && video_high_speed ) { VideoProfile profile = getVideoProfile(); if( MyDebug.LOG ) Log.d(TAG, "video size: " + profile.videoFrameWidth + " x " + profile.videoFrameHeight); @@ -3939,6 +3998,9 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "no preview size matches the aspect ratio"); optimalSize = getClosestSize(sizes, targetRatio, null); } + + handleDecorFitsSystemWindows(optimalSize, display_size); + if( MyDebug.LOG ) { Log.d(TAG, "chose optimalSize: " + optimalSize.width + " x " + optimalSize.height); Log.d(TAG, "optimalSize ratio: " + ((double)optimalSize.width / optimalSize.height)); @@ -3946,6 +4008,12 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu return optimalSize; } + private void handleDecorFitsSystemWindows(CameraController.Size optimalSize, Point display_size) { + double previewRatio = (double) optimalSize.width / optimalSize.height; + double screenRatio = (double) display_size.x / display_size.y; + ((MainActivity)getContext()).handleDecorFitsSystemWindows(previewRatio, screenRatio); + } + public CameraController.Size getOptimalVideoPictureSize(List sizes, double targetRatio) { if( MyDebug.LOG ) Log.d(TAG, "getOptimalVideoPictureSize()"); @@ -4161,6 +4229,13 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "getImageVideoRotation() lock to portrait, returns " + result); return result; + } else { + int cameraRotationOffset = camera_controller.getCameraOrientation(); + boolean isFrontFacing = camera_controller.getFacing() == CameraController.Facing.FACING_FRONT; + int rotation = isFrontFacing ? cameraRotationOffset + getDisplayRotationDegrees(true) : + cameraRotationOffset - getDisplayRotationDegrees(true); + + current_rotation = (360 + rotation) % 360; } if( MyDebug.LOG ) Log.d(TAG, "getImageVideoRotation() returns current_rotation " + current_rotation); @@ -4700,7 +4775,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // (important not to return here however - still want to call // camera_controller.clearPreviewFpsRange() to clear a previously set fps) } - else if( this.is_video ) { + else if( this.is_video() ) { // For Nexus 5 and Nexus 6, we need to set the preview fps using matchPreviewFpsToVideo to avoid problem of dark preview in low light, as described above. // When the video recording starts, the preview automatically adjusts, but still good to avoid too-dark preview before the user starts recording. // However I'm wary of changing the behaviour for all devices at the moment, since some devices can be @@ -4771,22 +4846,27 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "camera not opened!"); return; } - if( !is_video && !supports_video ) { + if( !is_video() && !supports_video ) { if( MyDebug.LOG ) Log.d(TAG, "video not supported"); return; } - boolean old_is_video = is_video; - if( this.is_video ) { + + boolean old_is_video = is_video(); + boolean old_is_qrcode = is_qrcode(); + if( this.is_video() ) { if( video_recorder != null ) { stopVideo(false); } - this.is_video = false; - } - else { + setQRCode(true); + } else if (this.is_qrcode()) { + this.functionalMode = FunctionalMode.PHOTO; + setQRCode(false); + } else if (this.is_photo()) { if( this.isOnTimer() ) { cancelTimer(); - this.is_video = true; + ((MainActivity)getContext()).setDecorFitsSystemWindows(true); + this.functionalMode = FunctionalMode.VIDEO; } else if( this.phase == PHASE_TAKING_PHOTO ) { // wait until photo taken @@ -4794,11 +4874,17 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "wait until photo taken"); } else { - this.is_video = true; + ((MainActivity)getContext()).setDecorFitsSystemWindows(true); + this.functionalMode = FunctionalMode.VIDEO; } + // nothing to do for overlayQRCodeView in this mode } - if( is_video != old_is_video ) { + if (is_qrcode() != old_is_qrcode) { + applicationInterface.setVideoPref(false); + this.reopenCamera(); + } + else if (is_video() != old_is_video) { setFocusPref(false); // first restore the saved focus for the new photo/video mode; don't do autofocus, as it'll be cancelled when restarting preview /*if( !is_video ) { // changing from video to photo mode @@ -4807,7 +4893,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( change_user_pref ) { // now save - applicationInterface.setVideoPref(is_video); + applicationInterface.setVideoPref(true); } if( !during_startup ) { // if during startup, updateFlashForVideo() needs to always be explicitly called anyway @@ -4834,7 +4920,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // changing from photo to video mode setFocusPref(false); }*/ - if( is_video ) { + if( is_video() ) { if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && applicationInterface.getRecordAudioPref() ) { // check for audio permission now, rather than when user starts video recording // we restrict the checks to Android 6 or later just in case, see note in LocationSupplier.setupLocationListener() @@ -4862,7 +4948,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private void setFocusPref(boolean auto_focus) { if( MyDebug.LOG ) Log.d(TAG, "setFocusPref()"); - String focus_value = applicationInterface.getFocusPref(is_video); + String focus_value = applicationInterface.getFocusPref(is_video()); if( focus_value.length() > 0 ) { if( MyDebug.LOG ) Log.d(TAG, "found existing focus_value: " + focus_value); @@ -4877,7 +4963,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "found no existing focus_value"); // here we set the default values for focus mode // note if updating default focus value for photo mode, also update MainActivityTest.setToDefault() - if( !updateFocus(is_video ? "focus_mode_continuous_video" : "focus_mode_continuous_picture", true, true, auto_focus) ) { + if( !updateFocus(is_video() ? "focus_mode_continuous_video" : "focus_mode_continuous_picture", true, true, auto_focus) ) { if( MyDebug.LOG ) Log.d(TAG, "continuous focus not supported, so fall back to first"); updateFocus(0, true, true, auto_focus); @@ -4896,12 +4982,12 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "updateFocusForVideo()"); String old_focus_mode = null; - if( this.supported_focus_values != null && camera_controller != null && is_video ) { + if( this.supported_focus_values != null && camera_controller != null && is_video() ) { boolean focus_is_video = focusIsVideo(); if( MyDebug.LOG ) { - Log.d(TAG, "focus_is_video: " + focus_is_video + " , is_video: " + is_video); + Log.d(TAG, "focus_is_video: " + focus_is_video + " , is_video: " + is_video()); } - if( focus_is_video != is_video ) { + if( focus_is_video != is_video() ) { if( MyDebug.LOG ) Log.d(TAG, "need to change focus mode"); old_focus_mode = this.getCurrentFocusValue(); @@ -4918,7 +5004,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu private void updateFlashForVideo() { if( MyDebug.LOG ) Log.d(TAG, "updateFlashForVideo()"); - if( is_video ) { + if( is_video() ) { // check flash is not auto or on String current_flash = getCurrentFlashValue(); if( current_flash != null && !isFlashSupportedForVideo(current_flash) ) { @@ -4982,7 +5068,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu public void updateFlash(String flash_value) { if( MyDebug.LOG ) Log.d(TAG, "updateFlash(): " + flash_value); - if( this.phase == PHASE_TAKING_PHOTO && !is_video ) { + if( this.phase == PHASE_TAKING_PHOTO && !is_video() ) { // just to be safe - risk of cancelling the autofocus before taking a photo, or otherwise messing things up if( MyDebug.LOG ) Log.d(TAG, "currently taking a photo"); @@ -5023,7 +5109,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // don't bother setting done to false as we shouldn't have two torches in a row... } - if( is_video ) { + if( is_video() ) { // check supported for video String new_flash_value = supported_flash_values.get(new_flash_index); if( !isFlashSupportedForVideo(new_flash_value) ) { @@ -5193,7 +5279,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( save ) { // now save - applicationInterface.setFocusPref(focus_value, is_video); + applicationInterface.setFocusPref(focus_value, is_video()); } } } @@ -5242,7 +5328,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu String focus_value = current_focus_index != -1 ? supported_focus_values.get(current_focus_index) : null; if( MyDebug.LOG ) Log.d(TAG, "focus_value is " + focus_value); - if( camera_controller != null && focus_value != null && focus_value.equals("focus_mode_continuous_picture") && !this.is_video ) { + if( camera_controller != null && focus_value != null && focus_value.equals("focus_mode_continuous_picture") && !this.is_video()) { if( MyDebug.LOG ) Log.d(TAG, "set continuous picture focus move callback"); camera_controller.setContinuousFocusMoveCallback(new CameraController.ContinuousFocusMoveCallback() { @@ -5326,7 +5412,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu this.phase = PHASE_NORMAL; return; } - if( is_video && continuous_fast_burst ) { + if( is_video() && continuous_fast_burst ) { Log.e(TAG, "continuous_fast_burst not supported for video mode"); this.phase = PHASE_NORMAL; return; @@ -5338,7 +5424,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } //if( !photo_snapshot && this.phase == PHASE_TAKING_PHOTO ) { //if( (is_video && is_video_recording && !photo_snapshot) || this.phase == PHASE_TAKING_PHOTO ) { - if( is_video && isVideoRecording() && !photo_snapshot ) { + if( is_video() && isVideoRecording() && !photo_snapshot ) { // user requested stop video if( !video_start_time_set || System.currentTimeMillis() - video_start_time < 500 ) { // if user presses to stop too quickly, we ignore @@ -5352,7 +5438,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } return; } - else if( ( !is_video || photo_snapshot ) && this.phase == PHASE_TAKING_PHOTO ) { + else if( ( !is_video() || photo_snapshot ) && this.phase == PHASE_TAKING_PHOTO ) { // user requested take photo while already taking photo if( MyDebug.LOG ) Log.d(TAG, "already taking a photo"); @@ -5360,14 +5446,14 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu cancelRepeat(); showToast(take_photo_toast, R.string.cancelled_repeat_mode, true); } - else if( !is_video && camera_controller.getBurstType() == CameraController.BurstType.BURSTTYPE_FOCUS && camera_controller.isCapturingBurst() ) { + else if( !is_video() && camera_controller.getBurstType() == CameraController.BurstType.BURSTTYPE_FOCUS && camera_controller.isCapturingBurst() ) { camera_controller.stopFocusBracketingBurst(); showToast(take_photo_toast, R.string.cancelled_focus_bracketing, true); } return; } - if( !is_video || photo_snapshot ) { + if( !is_video() || photo_snapshot ) { // check it's okay to take a photo if( !applicationInterface.canTakeNewPhoto() ) { if( MyDebug.LOG ) @@ -5651,7 +5737,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "takePicture"); //this.thumbnail_anim = false; - if( !is_video || photo_snapshot ) + if( !is_video() || photo_snapshot ) this.phase = PHASE_TAKING_PHOTO; else { if( phase == PHASE_TIMER ) @@ -5666,7 +5752,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "camera not opened!"); this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if( is_video ) + if( is_video() ) applicationInterface.cameraInOperation(false, true); return; } @@ -5675,7 +5761,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "preview surface not yet available"); this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if( is_video ) + if( is_video() ) applicationInterface.cameraInOperation(false, true); return; } @@ -5691,17 +5777,17 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "location data required, but not available"); showToast(null, R.string.location_not_available, true); - if( !is_video || photo_snapshot ) + if( !is_video() || photo_snapshot ) this.phase = PHASE_NORMAL; applicationInterface.cameraInOperation(false, false); - if( is_video ) + if( is_video() ) applicationInterface.cameraInOperation(false, true); return; } } } - if( is_video && !photo_snapshot ) { + if( is_video() && !photo_snapshot ) { if( MyDebug.LOG ) Log.d(TAG, "start video recording"); startVideoRecording(max_filesize_restart); @@ -6559,7 +6645,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu camera_controller.setRotation(getImageVideoRotation()); boolean enable_sound = applicationInterface.getShutterSoundPref(); - if( is_video && isVideoRecording() ) + if( is_video() && isVideoRecording() ) enable_sound = false; // always disable shutter sound if we're taking a photo while recording video if( MyDebug.LOG ) Log.d(TAG, "enable_sound? " + enable_sound); @@ -6663,7 +6749,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu if( MyDebug.LOG ) Log.d(TAG, "preview not yet started"); } - else if( !(manual && this.is_video) && (this.isVideoRecording() || this.isTakingPhotoOrOnTimer()) ) { + else if( !(manual && this.is_video()) && (this.isVideoRecording() || this.isTakingPhotoOrOnTimer()) ) { // if taking a video, we allow manual autofocuses // autofocus may cause problem if there is a video corruption problem, see testTakeVideoBitrate() on Nexus 7 at 30Mbs or 50Mbs, where the startup autofocus would cause a problem here if( MyDebug.LOG ) @@ -6674,7 +6760,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu // remove any previous request to switch back to continuous removePendingContinuousFocusReset(); } - if( manual && !is_video && camera_controller.focusIsContinuous() && supportedFocusValue("focus_mode_auto") ) { + if( manual && !is_video() && camera_controller.focusIsContinuous() && supportedFocusValue("focus_mode_auto") ) { if( MyDebug.LOG ) Log.d(TAG, "switch from continuous to autofocus mode for touch focus"); camera_controller.setFocusValue("focus_mode_auto"); // switch to autofocus @@ -6856,8 +6942,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu Log.d(TAG, "starting the camera preview"); { if( MyDebug.LOG ) - Log.d(TAG, "setRecordingHint: " + is_video); - camera_controller.setRecordingHint(this.is_video); + Log.d(TAG, "setRecordingHint: " + is_video()); + camera_controller.setRecordingHint(this.is_video()); } setPreviewFps(); try { @@ -7157,7 +7243,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu public boolean isVideoHighSpeed() { if( MyDebug.LOG ) Log.d(TAG, "isVideoHighSpeed"); - return is_video && video_high_speed; + return is_video() && video_high_speed; } public boolean canDisableShutterSound() { @@ -7415,8 +7501,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu /** Returns the horizontal angle of view in degrees (when unzoomed). */ public float getViewAngleX(boolean for_preview) { - if( MyDebug.LOG ) - Log.d(TAG, "getViewAngleX: " + for_preview); + if( MyDebug.LOG ) + Log.d(TAG, "getViewAngleX: " + for_preview); CameraController.Size size = for_preview ? this.getCurrentPreviewSize() : this.getCurrentPictureSize(); if( size == null ) { Log.e(TAG, "can't find view angle x size"); @@ -7449,8 +7535,8 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu /** Returns the vertical angle of view in degrees (when unzoomed). */ public float getViewAngleY(boolean for_preview) { - if( MyDebug.LOG ) - Log.d(TAG, "getViewAngleY: " + for_preview); + if( MyDebug.LOG ) + Log.d(TAG, "getViewAngleY: " + for_preview); CameraController.Size size = for_preview ? this.getCurrentPreviewSize() : this.getCurrentPictureSize(); if( size == null ) { Log.e(TAG, "can't find view angle y size"); @@ -8526,7 +8612,7 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } else { - result.new_histogram = computeHistogramRS(allocation_in, preview.rs, histogramScript, preview.histogram_type); + result.new_histogram = computeHistogramRS(allocation_in, preview.rs, histogramScript, preview.histogram_type); } if( MyDebug.LOG ) { @@ -8552,27 +8638,27 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } else { - Allocation output_allocation = Allocation.createFromBitmap(preview.rs, zebra_stripes_bitmap_buffer); - - histogramScript.set_zebra_stripes_threshold(preview.zebra_stripes_threshold); - histogramScript.set_zebra_stripes_foreground_r(Color.red(preview.zebra_stripes_color_foreground)); - histogramScript.set_zebra_stripes_foreground_g(Color.green(preview.zebra_stripes_color_foreground)); - histogramScript.set_zebra_stripes_foreground_b(Color.blue(preview.zebra_stripes_color_foreground)); - histogramScript.set_zebra_stripes_foreground_a(Color.alpha(preview.zebra_stripes_color_foreground)); - histogramScript.set_zebra_stripes_background_r(Color.red(preview.zebra_stripes_color_background)); - histogramScript.set_zebra_stripes_background_g(Color.green(preview.zebra_stripes_color_background)); - histogramScript.set_zebra_stripes_background_b(Color.blue(preview.zebra_stripes_color_background)); - histogramScript.set_zebra_stripes_background_a(Color.alpha(preview.zebra_stripes_color_background)); - histogramScript.set_zebra_stripes_width(zebra_stripes_width); + Allocation output_allocation = Allocation.createFromBitmap(preview.rs, zebra_stripes_bitmap_buffer); + + histogramScript.set_zebra_stripes_threshold(preview.zebra_stripes_threshold); + histogramScript.set_zebra_stripes_foreground_r(Color.red(preview.zebra_stripes_color_foreground)); + histogramScript.set_zebra_stripes_foreground_g(Color.green(preview.zebra_stripes_color_foreground)); + histogramScript.set_zebra_stripes_foreground_b(Color.blue(preview.zebra_stripes_color_foreground)); + histogramScript.set_zebra_stripes_foreground_a(Color.alpha(preview.zebra_stripes_color_foreground)); + histogramScript.set_zebra_stripes_background_r(Color.red(preview.zebra_stripes_color_background)); + histogramScript.set_zebra_stripes_background_g(Color.green(preview.zebra_stripes_color_background)); + histogramScript.set_zebra_stripes_background_b(Color.blue(preview.zebra_stripes_color_background)); + histogramScript.set_zebra_stripes_background_a(Color.alpha(preview.zebra_stripes_color_background)); + histogramScript.set_zebra_stripes_width(zebra_stripes_width); - if( MyDebug.LOG ) - Log.d(TAG, "time before histogramScript generate_zebra_stripes: " + (System.currentTimeMillis() - debug_time)); - histogramScript.forEach_generate_zebra_stripes(allocation_in, output_allocation); - if( MyDebug.LOG ) - Log.d(TAG, "time after histogramScript generate_zebra_stripes: " + (System.currentTimeMillis() - debug_time)); + if( MyDebug.LOG ) + Log.d(TAG, "time before histogramScript generate_zebra_stripes: " + (System.currentTimeMillis() - debug_time)); + histogramScript.forEach_generate_zebra_stripes(allocation_in, output_allocation); + if( MyDebug.LOG ) + Log.d(TAG, "time after histogramScript generate_zebra_stripes: " + (System.currentTimeMillis() - debug_time)); - output_allocation.copyTo(zebra_stripes_bitmap_buffer); - output_allocation.destroy(); + output_allocation.copyTo(zebra_stripes_bitmap_buffer); + output_allocation.destroy(); } // The original orientation of the bitmap we get from textureView.getBitmap() needs to be rotated to @@ -8631,29 +8717,29 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } else { - Allocation output_allocation = Allocation.createFromBitmap(preview.rs, focus_peaking_bitmap_buffer); + Allocation output_allocation = Allocation.createFromBitmap(preview.rs, focus_peaking_bitmap_buffer); - histogramScript.set_bitmap(allocation_in); + histogramScript.set_bitmap(allocation_in); - if( MyDebug.LOG ) - Log.d(TAG, "time before histogramScript generate_focus_peaking: " + (System.currentTimeMillis() - debug_time)); - histogramScript.forEach_generate_focus_peaking(allocation_in, output_allocation); - if( MyDebug.LOG ) - Log.d(TAG, "time after histogramScript generate_focus_peaking: " + (System.currentTimeMillis() - debug_time)); + if( MyDebug.LOG ) + Log.d(TAG, "time before histogramScript generate_focus_peaking: " + (System.currentTimeMillis() - debug_time)); + histogramScript.forEach_generate_focus_peaking(allocation_in, output_allocation); + if( MyDebug.LOG ) + Log.d(TAG, "time after histogramScript generate_focus_peaking: " + (System.currentTimeMillis() - debug_time)); - // median filter - Allocation filtered_allocation = Allocation.createTyped(preview.rs, Type.createXY(preview.rs, Element.RGBA_8888(preview.rs), focus_peaking_bitmap_buffer.getWidth(), focus_peaking_bitmap_buffer.getHeight())); - histogramScript.set_bitmap(output_allocation); - if( MyDebug.LOG ) - Log.d(TAG, "time before histogramScript generate_focus_peaking_filtered: " + (System.currentTimeMillis() - debug_time)); - histogramScript.forEach_generate_focus_peaking_filtered(output_allocation, filtered_allocation); - if( MyDebug.LOG ) - Log.d(TAG, "time after histogramScript generate_focus_peaking_filtered: " + (System.currentTimeMillis() - debug_time)); - output_allocation.destroy(); - output_allocation = filtered_allocation; + // median filter + Allocation filtered_allocation = Allocation.createTyped(preview.rs, Type.createXY(preview.rs, Element.RGBA_8888(preview.rs), focus_peaking_bitmap_buffer.getWidth(), focus_peaking_bitmap_buffer.getHeight())); + histogramScript.set_bitmap(output_allocation); + if( MyDebug.LOG ) + Log.d(TAG, "time before histogramScript generate_focus_peaking_filtered: " + (System.currentTimeMillis() - debug_time)); + histogramScript.forEach_generate_focus_peaking_filtered(output_allocation, filtered_allocation); + if( MyDebug.LOG ) + Log.d(TAG, "time after histogramScript generate_focus_peaking_filtered: " + (System.currentTimeMillis() - debug_time)); + output_allocation.destroy(); + output_allocation = filtered_allocation; - output_allocation.copyTo(focus_peaking_bitmap_buffer); - output_allocation.destroy(); + output_allocation.copyTo(focus_peaking_bitmap_buffer); + output_allocation.destroy(); } @@ -8787,10 +8873,33 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu } } + public void setQRCode(boolean enabled) { + final Activity activity = (Activity) Preview.this.getContext(); + final FrameLayout rootLayout = activity.findViewById(android.R.id.content); + if (enabled) { + this.functionalMode = FunctionalMode.QRCODE; + rootLayout.addView(overlayQRCodeView); + int qrcodeCamId = ((MainActivity)getContext()).getBetterQRCodeCameraID(); + if (qrcodeCamId >= 0) { + applicationInterface.setCameraIdPref(qrcodeCamId); + } + } else if (overlayQRCodeView.getParent() != null) { + rootLayout.removeView(overlayQRCodeView); + } + } + + public boolean isQRCode() { + return functionalMode == FunctionalMode.QRCODE; + } + /** Whether we are in video mode, or photo mode. */ public boolean isVideo() { - return is_video; + return functionalMode == FunctionalMode.VIDEO; + } + + public boolean isPhoto() { + return functionalMode == FunctionalMode.PHOTO; } public boolean isVideoRecording() { diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt new file mode 100644 index 0000000000000000000000000000000000000000..e4676434eab2dfc4e873239a36d33ecebaf558e0 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.qr + +import android.app.Activity +import android.app.KeyguardManager +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Intent +import android.content.pm.ActivityInfo +import android.os.Build +import android.text.method.LinkMovementMethod +import android.view.textclassifier.TextClassificationManager +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams +import androidx.cardview.widget.CardView +import androidx.core.graphics.drawable.DrawableCompat +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton +import com.google.zxing.MultiFormatReader +import com.google.zxing.Result +import net.sourceforge.opencamera.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.sourceforge.opencamera.ext.getThemeColor +import net.sourceforge.opencamera.ext.px +import kotlin.reflect.cast + +class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) { + + // Views + private val bottomSheetDialog by lazy { + BottomSheetDialog(activity).apply { + setContentView(R.layout.qr_bottom_sheet_dialog) + } + } + + private val bottomSheetDialogCardView by lazy { + bottomSheetDialog.findViewById(R.id.cardView)!! + } + private val bottomSheetDialogTitle by lazy { + bottomSheetDialog.findViewById(R.id.title)!! + } + private val bottomSheetDialogData by lazy { + bottomSheetDialog.findViewById(R.id.data)!! + } + private val bottomSheetDialogIcon by lazy { + bottomSheetDialog.findViewById(R.id.icon)!! + } + private val bottomSheetDialogCopy by lazy { + bottomSheetDialog.findViewById(R.id.copy)!! + } + private val bottomSheetDialogShare by lazy { + bottomSheetDialog.findViewById(R.id.share)!! + } + private val bottomSheetDialogActionsLayout by lazy { + bottomSheetDialog.findViewById(R.id.actionsLayout)!! + } + + // System services + private val clipboardManager by lazy { activity.getSystemService(ClipboardManager::class.java) } + private val keyguardManager by lazy { activity.getSystemService(KeyguardManager::class.java) } + private val textClassificationManager by lazy { + activity.getSystemService(TextClassificationManager::class.java) + } + + // QR + private val reader by lazy { MultiFormatReader() } + + private val qrTextClassifier by lazy { + QrTextClassifier(activity, textClassificationManager.textClassifier) + } + + public fun showQrDialog(result: Result) { + scope.launch(Dispatchers.Main) { + if (bottomSheetDialog.isShowing) { + return@launch + } + + val text = result.text ?: return@launch + bottomSheetDialogData.text = text + + // Classify message + val textClassification = withContext(Dispatchers.IO) { + qrTextClassifier.classifyText(result) + } + + bottomSheetDialogData.text = textClassification.text + bottomSheetDialogActionsLayout.removeAllViews() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + textClassification.actions.isNotEmpty() + ) { + with(textClassification.actions[0]) { + bottomSheetDialogCardView.setOnClickListener { + try { + actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Toast.makeText( + activity, + R.string.qr_no_app_available_for_action, + Toast.LENGTH_SHORT + ).show() + } + } + bottomSheetDialogCardView.contentDescription = contentDescription + bottomSheetDialogData.movementMethod = null + bottomSheetDialogTitle.text = title + bottomSheetDialogIcon.setImageIcon(icon) + } + for (action in textClassification.actions.drop(1)) { + bottomSheetDialogActionsLayout.addView(inflateButton().apply { + setOnClickListener { + try { + action.actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Toast.makeText( + activity, + R.string.qr_no_app_available_for_action, + Toast.LENGTH_SHORT + ).show() + } + } + contentDescription = action.contentDescription + this.text = action.title + withContext(Dispatchers.IO) { + val drawable = action.icon.loadDrawable(activity)!! + drawable.setBounds(0, 0, 15.px, 15.px) + withContext(Dispatchers.Main) { + setCompoundDrawables( + drawable, null, null, null + ) + } + } + }) + } + } else { + bottomSheetDialogCardView.setOnClickListener {} + bottomSheetDialogTitle.text = activity.resources.getText(R.string.qr_text) + bottomSheetDialogIcon.setImageDrawable(AppCompatResources.getDrawable( + activity, R.drawable.ic_text_snippet + )?.let { + DrawableCompat.wrap(it.mutate()).apply { + DrawableCompat.setTint( + this, activity.getColor( + R.color.e_icon_color + ) + ) + } + }) + } + + // Make links clickable if not on locked keyguard + bottomSheetDialogData.movementMethod = + if (!keyguardManager.isKeyguardLocked) LinkMovementMethod.getInstance() + else null + + // Set buttons + bottomSheetDialogCopy.setOnClickListener { + clipboardManager.setPrimaryClip( + ClipData.newPlainText( + "", text + ) + ) + } + + bottomSheetDialogShare.setOnClickListener { + activity.startActivity( + Intent.createChooser( + Intent().apply { + action = Intent.ACTION_SEND + type = ClipDescription.MIMETYPE_TEXT_PLAIN + putExtra( + Intent.EXTRA_TEXT, result.text + ) + }, + activity.getString(R.string.abc_shareactionprovider_share_with) + ) + ) + } + + // Show dialog + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + + bottomSheetDialog.show() + + bottomSheetDialog.setOnDismissListener { + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + + } + } + + private fun inflateButton() = MaterialButton::class.cast( + activity.layoutInflater.inflate( + R.layout.qr_bottom_sheet_action_button, + bottomSheetDialogActionsLayout, + false + ) + ).apply { + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + } + +} diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrScannerActivity.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrScannerActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..94a501b74debf8f08e94294b7d6694e72f429b5e --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrScannerActivity.kt @@ -0,0 +1,11 @@ +package net.sourceforge.opencamera.qr + +import android.os.Bundle +import net.sourceforge.opencamera.MainActivity + +class QrScannerActivity : MainActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + preview.setQRCode(true) + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt new file mode 100644 index 0000000000000000000000000000000000000000..7a4ffb63d8afafb6528e834091c0644380c9e140 --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.sourceforge.opencamera.qr + +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.net.wifi.WifiManager +import android.os.Build +import android.os.LocaleList +import android.provider.Settings +import android.text.SpannableString +import android.view.textclassifier.TextClassification +import android.view.textclassifier.TextClassifier +import com.google.zxing.Result +import com.google.zxing.client.result.ResultParser +import com.google.zxing.client.result.URIParsedResult +import net.sourceforge.opencamera.R +import net.sourceforge.opencamera.ext.build +import net.sourceforge.opencamera.ext.createTextClassification +import kotlin.reflect.safeCast + +class QrTextClassifier( + private val context: Context, private val textClassifier: TextClassifier +) { + private val wifiManager by lazy { + runCatching { context.getSystemService(WifiManager::class.java) }.getOrNull() + } + + fun classifyText(result: Result): TextClassification { + // Try with ZXing parser + val parsedResult = ResultParser.parseResult(result) + parsedResult?.createTextClassification(context)?.let { + return it + } + + // We handle URIParsedResult here + val text = URIParsedResult::class.safeCast(parsedResult)?.uri ?: result.text + + // Try parsing it as a Uri + Uri.parse(text.toString()).let { uri -> + when (uri.scheme?.lowercase()) { + // Wi-Fi DPP + SCHEME_DPP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && + wifiManager?.isEasyConnectSupported == true + ) { + return TextClassification.Builder() + .setText(context.getString(R.string.qr_dpp_description)) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .addAction( + RemoteAction::class.build( + context, + R.drawable.ic_network_wifi, + R.string.qr_dpp_title, + R.string.qr_dpp_description, + Intent(Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI).apply { + data = uri + } + ) + ) + .build() + } + + SCHEME_FIDO -> return TextClassification.Builder() + .setText(context.getString(R.string.qr_fido_content_description)) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_passkey, + R.string.qr_fido_title, + R.string.qr_fido_content_description, + Intent(Intent.ACTION_VIEW).apply { + data = uri + } + ) + ) + } + } + .build() + } + } + + // Let Android classify it + val spannableString = SpannableString(text) + return textClassifier.classifyText( + spannableString, 0, spannableString.length, LocaleList.getDefault() + ) + } + + companion object { + private const val SCHEME_DPP = "dpp" + private const val SCHEME_FIDO = "fido" + } +} diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/CircleImageView.java b/app/src/main/java/net/sourceforge/opencamera/ui/CircleImageView.java new file mode 100644 index 0000000000000000000000000000000000000000..5a545c62b92d6b01b492ba968b4864f2f842322c --- /dev/null +++ b/app/src/main/java/net/sourceforge/opencamera/ui/CircleImageView.java @@ -0,0 +1,499 @@ +package net.sourceforge.opencamera.ui; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.ImageView; + +import net.sourceforge.opencamera.R; + +@SuppressWarnings("UnusedDeclaration") +public class CircleImageView extends ImageView { + + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + private static final int COLORDRAWABLE_DIMENSION = 2; + + private static final int DEFAULT_BORDER_WIDTH = 0; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT; + private static final int DEFAULT_IMAGE_ALPHA = 255; + private static final boolean DEFAULT_BORDER_OVERLAY = false; + + private final RectF mDrawableRect = new RectF(); + private final RectF mBorderRect = new RectF(); + + private final Matrix mShaderMatrix = new Matrix(); + private final Paint mBitmapPaint = new Paint(); + private final Paint mBorderPaint = new Paint(); + private final Paint mCircleBackgroundPaint = new Paint(); + + private int mBorderColor = DEFAULT_BORDER_COLOR; + private int mBorderWidth = DEFAULT_BORDER_WIDTH; + private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR; + private int mImageAlpha = DEFAULT_IMAGE_ALPHA; + + private Bitmap mBitmap; + private Canvas mBitmapCanvas; + + private float mDrawableRadius; + private float mBorderRadius; + + private ColorFilter mColorFilter; + + private boolean mInitialized; + private boolean mRebuildShader; + private boolean mDrawableDirty; + + private boolean mBorderOverlay; + private boolean mDisableCircularTransformation; + + public CircleImageView(Context context) { + super(context); + + init(); + } + + public CircleImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircleImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); + + mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); + mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); + mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); + mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_circle_background_color, DEFAULT_CIRCLE_BACKGROUND_COLOR); + + a.recycle(); + + init(); + } + + private void init() { + mInitialized = true; + + super.setScaleType(SCALE_TYPE); + + mBitmapPaint.setAntiAlias(true); + mBitmapPaint.setDither(true); + mBitmapPaint.setFilterBitmap(true); + mBitmapPaint.setAlpha(mImageAlpha); + mBitmapPaint.setColorFilter(mColorFilter); + + mBorderPaint.setStyle(Paint.Style.STROKE); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setColor(mBorderColor); + mBorderPaint.setStrokeWidth(mBorderWidth); + + mCircleBackgroundPaint.setStyle(Paint.Style.FILL); + mCircleBackgroundPaint.setAntiAlias(true); + mCircleBackgroundPaint.setColor(mCircleBackgroundColor); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setOutlineProvider(new OutlineProvider()); + } + } + + @Override + public void setScaleType(ScaleType scaleType) { + if (scaleType != SCALE_TYPE) { + throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); + } + } + + @Override + public void setAdjustViewBounds(boolean adjustViewBounds) { + if (adjustViewBounds) { + throw new IllegalArgumentException("adjustViewBounds not supported."); + } + } + + @SuppressLint("CanvasSize") + @Override + protected void onDraw(Canvas canvas) { + if (mDisableCircularTransformation) { + super.onDraw(canvas); + return; + } + + if (mCircleBackgroundColor != Color.TRANSPARENT) { + canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint); + } + + if (mBitmap != null) { + if (mDrawableDirty && mBitmapCanvas != null) { + mDrawableDirty = false; + Drawable drawable = getDrawable(); + drawable.setBounds(0, 0, mBitmapCanvas.getWidth(), mBitmapCanvas.getHeight()); + drawable.draw(mBitmapCanvas); + } + + if (mRebuildShader) { + mRebuildShader = false; + + BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + bitmapShader.setLocalMatrix(mShaderMatrix); + + mBitmapPaint.setShader(bitmapShader); + } + + canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint); + } + + if (mBorderWidth > 0) { + canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint); + } + } + + @Override + public void invalidateDrawable(@NonNull Drawable dr) { + mDrawableDirty = true; + invalidate(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateDimensions(); + invalidate(); + } + + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom); + updateDimensions(); + invalidate(); + } + + @Override + public void setPaddingRelative(int start, int top, int end, int bottom) { + super.setPaddingRelative(start, top, end, bottom); + updateDimensions(); + invalidate(); + } + + public int getBorderColor() { + return mBorderColor; + } + + public void setBorderColor(@ColorInt int borderColor) { + if (borderColor == mBorderColor) { + return; + } + + mBorderColor = borderColor; + mBorderPaint.setColor(borderColor); + invalidate(); + } + + public int getCircleBackgroundColor() { + return mCircleBackgroundColor; + } + + public void setCircleBackgroundColor(@ColorInt int circleBackgroundColor) { + if (circleBackgroundColor == mCircleBackgroundColor) { + return; + } + + mCircleBackgroundColor = circleBackgroundColor; + mCircleBackgroundPaint.setColor(circleBackgroundColor); + invalidate(); + } + + /** + * @deprecated Use {@link #setCircleBackgroundColor(int)} instead + */ + @Deprecated + public void setCircleBackgroundColorResource(@ColorRes int circleBackgroundRes) { + setCircleBackgroundColor(getContext().getResources().getColor(circleBackgroundRes)); + } + + public int getBorderWidth() { + return mBorderWidth; + } + + public void setBorderWidth(int borderWidth) { + if (borderWidth == mBorderWidth) { + return; + } + + mBorderWidth = borderWidth; + mBorderPaint.setStrokeWidth(borderWidth); + updateDimensions(); + invalidate(); + } + + public boolean isBorderOverlay() { + return mBorderOverlay; + } + + public void setBorderOverlay(boolean borderOverlay) { + if (borderOverlay == mBorderOverlay) { + return; + } + + mBorderOverlay = borderOverlay; + updateDimensions(); + invalidate(); + } + + public boolean isDisableCircularTransformation() { + return mDisableCircularTransformation; + } + + public void setDisableCircularTransformation(boolean disableCircularTransformation) { + if (disableCircularTransformation == mDisableCircularTransformation) { + return; + } + + mDisableCircularTransformation = disableCircularTransformation; + + if (disableCircularTransformation) { + mBitmap = null; + mBitmapCanvas = null; + mBitmapPaint.setShader(null); + } else { + initializeBitmap(); + } + + invalidate(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + super.setImageResource(resId); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageAlpha(int alpha) { + alpha &= 0xFF; + + if (alpha == mImageAlpha) { + return; + } + + mImageAlpha = alpha; + + // This might be called during ImageView construction before + // member initialization has finished on API level >= 16. + if (mInitialized) { + mBitmapPaint.setAlpha(alpha); + invalidate(); + } + } + + @Override + public int getImageAlpha() { + return mImageAlpha; + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (cf == mColorFilter) { + return; + } + + mColorFilter = cf; + + // This might be called during ImageView construction before + // member initialization has finished on API level <= 19. + if (mInitialized) { + mBitmapPaint.setColorFilter(cf); + invalidate(); + } + } + + @Override + public ColorFilter getColorFilter() { + return mColorFilter; + } + + private Bitmap getBitmapFromDrawable(Drawable drawable) { + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + try { + Bitmap bitmap; + + if (drawable instanceof ColorDrawable) { + bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void initializeBitmap() { + mBitmap = getBitmapFromDrawable(getDrawable()); + + if (mBitmap != null && mBitmap.isMutable()) { + mBitmapCanvas = new Canvas(mBitmap); + } else { + mBitmapCanvas = null; + } + + if (!mInitialized) { + return; + } + + if (mBitmap != null) { + updateShaderMatrix(); + } else { + mBitmapPaint.setShader(null); + } + } + + private void updateDimensions() { + mBorderRect.set(calculateBounds()); + mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f); + + mDrawableRect.set(mBorderRect); + if (!mBorderOverlay && mBorderWidth > 0) { + mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f); + } + mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); + + updateShaderMatrix(); + } + + private RectF calculateBounds() { + int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + + int sideLength = Math.min(availableWidth, availableHeight); + + float left = getPaddingLeft() + (availableWidth - sideLength) / 2f; + float top = getPaddingTop() + (availableHeight - sideLength) / 2f; + + return new RectF(left, top, left + sideLength, top + sideLength); + } + + private void updateShaderMatrix() { + if (mBitmap == null) { + return; + } + + float scale; + float dx = 0; + float dy = 0; + + mShaderMatrix.set(null); + + int bitmapHeight = mBitmap.getHeight(); + int bitmapWidth = mBitmap.getWidth(); + + if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) { + scale = mDrawableRect.height() / (float) bitmapHeight; + dx = (mDrawableRect.width() - bitmapWidth * scale) * 0.5f; + } else { + scale = mDrawableRect.width() / (float) bitmapWidth; + dy = (mDrawableRect.height() - bitmapHeight * scale) * 0.5f; + } + + mShaderMatrix.setScale(scale, scale); + mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); + + mRebuildShader = true; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mDisableCircularTransformation) { + return super.onTouchEvent(event); + } + + return inTouchableArea(event.getX(), event.getY()) && super.onTouchEvent(event); + } + + private boolean inTouchableArea(float x, float y) { + if (mBorderRect.isEmpty()) { + return true; + } + + return Math.pow(x - mBorderRect.centerX(), 2) + Math.pow(y - mBorderRect.centerY(), 2) <= Math.pow(mBorderRadius, 2); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private class OutlineProvider extends ViewOutlineProvider { + + @Override + public void getOutline(View view, Outline outline) { + if (mDisableCircularTransformation) { + ViewOutlineProvider.BACKGROUND.getOutline(view, outline); + } else { + Rect bounds = new Rect(); + mBorderRect.roundOut(bounds); + outline.setRoundRect(bounds, bounds.width() / 2.0f); + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/DrawPreview.java b/app/src/main/java/net/sourceforge/opencamera/ui/DrawPreview.java index 682ccf0957390a7e5f472fece96f175d08cf6d3e..3e49690a9f4efca4a0db9329f1caf0bbbd02fc9f 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ui/DrawPreview.java +++ b/app/src/main/java/net/sourceforge/opencamera/ui/DrawPreview.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import net.sourceforge.opencamera.DeviceSettings; import net.sourceforge.opencamera.GyroSensor; import net.sourceforge.opencamera.ImageSaver; import net.sourceforge.opencamera.LocationSupplier; @@ -19,6 +21,7 @@ import net.sourceforge.opencamera.MainActivity; import net.sourceforge.opencamera.MyApplicationInterface; import net.sourceforge.opencamera.MyDebug; import net.sourceforge.opencamera.PreferenceKeys; +import net.sourceforge.opencamera.Utils; import net.sourceforge.opencamera.preview.ApplicationInterface; import net.sourceforge.opencamera.R; import net.sourceforge.opencamera.cameracontroller.CameraController; @@ -42,6 +45,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.location.Location; import android.net.Uri; import android.os.BatteryManager; @@ -55,6 +59,8 @@ import android.view.Surface; import android.view.View; import android.widget.RelativeLayout; +import androidx.annotation.NonNull; + public class DrawPreview { private static final String TAG = "DrawPreview"; @@ -243,7 +249,7 @@ public class DrawPreview { private Bitmap location_off_bitmap; private Bitmap raw_jpeg_bitmap; private Bitmap raw_only_bitmap; - private Bitmap auto_stabilise_bitmap; + private Drawable auto_stabilise_drawable; private Bitmap dro_bitmap; private Bitmap hdr_bitmap; private Bitmap panorama_bitmap; @@ -256,8 +262,8 @@ public class DrawPreview { private Bitmap x_bokeh_bitmap; private Bitmap x_beauty_bitmap; private Bitmap photostamp_bitmap; - private Bitmap flash_bitmap; - private Bitmap face_detection_bitmap; + private Drawable flash_drawable; + private Drawable face_detection_drawable; private Bitmap audio_disabled_bitmap; private Bitmap high_speed_fps_bitmap; private Bitmap slow_motion_bitmap; @@ -344,7 +350,7 @@ public class DrawPreview { location_off_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_gps_off_white_48dp); raw_jpeg_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.raw_icon); raw_only_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.raw_only_icon); - auto_stabilise_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.auto_stabilise_icon); + auto_stabilise_drawable = getContext().getResources().getDrawable(R.drawable.ic_preference_auto_stabilise); dro_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.dro_icon); hdr_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_hdr_on_white_48dp); panorama_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.baseline_panorama_horizontal_white_48); @@ -356,8 +362,8 @@ public class DrawPreview { x_bokeh_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.baseline_portrait_white_48); x_beauty_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.baseline_face_retouching_natural_white_48); photostamp_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_text_format_white_48dp); - flash_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.flash_on); - face_detection_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_face_white_48dp); + flash_drawable = getContext().getResources().getDrawable(R.drawable.ic_camera_flash_on); + face_detection_drawable = getContext().getResources().getDrawable(R.drawable.ic_face); audio_disabled_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_mic_off_white_48dp); high_speed_fps_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_fast_forward_white_48dp); slow_motion_bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_slow_motion_video_white_48dp); @@ -393,9 +399,8 @@ public class DrawPreview { raw_only_bitmap.recycle(); raw_only_bitmap = null; } - if( auto_stabilise_bitmap != null ) { - auto_stabilise_bitmap.recycle(); - auto_stabilise_bitmap = null; + if( auto_stabilise_drawable != null ) { + auto_stabilise_drawable = null; } if( dro_bitmap != null ) { dro_bitmap.recycle(); @@ -441,13 +446,9 @@ public class DrawPreview { photostamp_bitmap.recycle(); photostamp_bitmap = null; } - if( flash_bitmap != null ) { - flash_bitmap.recycle(); - flash_bitmap = null; - } - if( face_detection_bitmap != null ) { - face_detection_bitmap.recycle(); - face_detection_bitmap = null; + + if( face_detection_drawable != null ) { + face_detection_drawable = null; } if( audio_disabled_bitmap != null ) { audio_disabled_bitmap.recycle(); @@ -684,7 +685,7 @@ public class DrawPreview { show_pitch_lines_pref = sharedPreferences.getBoolean(PreferenceKeys.ShowPitchLinesPreferenceKey, false); show_geo_direction_lines_pref = sharedPreferences.getBoolean(PreferenceKeys.ShowGeoDirectionLinesPreferenceKey, false); - String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); immersive_mode_everything_pref = immersive_mode.equals("immersive_mode_everything"); has_stamp_pref = applicationInterface.getStampPref().equals("preference_stamp_yes"); @@ -1180,7 +1181,7 @@ public class DrawPreview { int ui_rotation = preview.getUIRotation(); // set up text etc for the multiple lines of "info" (time, free mem, etc) - p.setTextSize(16 * scale_font + 0.5f); // convert dps to pixels + p.setTextSize(12.6f * scale_font + 0.5f); // convert dps to pixels p.setTextAlign(Paint.Align.LEFT); int location_x = top_x; int location_y = top_y; @@ -1311,7 +1312,7 @@ public class DrawPreview { Color.WHITE, Color.BLACK, location_x, bottom_y, MyApplicationInterface.Alignment.ALIGNMENT_BOTTOM, null, MyApplicationInterface.Shadow.SHADOW_OUTLINE); } - p.setTextSize(16 * scale_font + 0.5f); // Restore text size + p.setTextSize(12.6f * scale_font + 0.5f); // Restore text size if( camera_controller != null && show_iso_pref ) { if( iso_exposure_string == null || time_ms > last_iso_exposure_time + 500 ) { @@ -1454,7 +1455,7 @@ public class DrawPreview { p.setAlpha(64); canvas.drawRect(icon_dest, p); p.setAlpha(255); - canvas.drawBitmap(face_detection_bitmap, null, icon_dest, p); + face_detection_drawable.draw(canvas); if( device_ui_rotation == 180 ) { location_x2 -= icon_size + flash_padding; @@ -1471,7 +1472,7 @@ public class DrawPreview { p.setAlpha(64); canvas.drawRect(icon_dest, p); p.setAlpha(255); - canvas.drawBitmap(auto_stabilise_bitmap, null, icon_dest, p); + auto_stabilise_drawable.draw(canvas); if( device_ui_rotation == 180 ) { location_x2 -= icon_size + flash_padding; @@ -1627,7 +1628,7 @@ public class DrawPreview { p.setAlpha((int)(64*alpha)); canvas.drawRect(icon_dest, p); p.setAlpha((int)(255*alpha)); - canvas.drawBitmap(flash_bitmap, null, icon_dest, p); + flash_drawable.draw(canvas); p.setAlpha(255); } else { @@ -2104,7 +2105,7 @@ public class DrawPreview { // Convert the dps to pixels, based on density scale p.setTextSize(14 * scale_font + 0.5f); // convert dps to pixels p.setTextAlign(Paint.Align.CENTER); - applicationInterface.drawTextWithBackground(canvas, p, getContext().getResources().getString(R.string.zoom) + ": " + zoom_ratio +"x", Color.WHITE, Color.BLACK, canvas.getWidth() / 2, text_base_y - text_y, MyApplicationInterface.Alignment.ALIGNMENT_BOTTOM, ybounds_text, MyApplicationInterface.Shadow.SHADOW_OUTLINE); + applicationInterface.drawTextWithBackground(canvas, p, getZoomText(zoom_ratio), Color.WHITE, Color.BLACK, canvas.getWidth() / 2, text_base_y - text_y - 100, MyApplicationInterface.Alignment.ALIGNMENT_BOTTOM, ybounds_text, MyApplicationInterface.Shadow.SHADOW_OUTLINE); } } @@ -2119,7 +2120,16 @@ public class DrawPreview { p.setTextAlign(Paint.Align.CENTER); int pixels_offset = (int) (20 * scale_font + 0.5f); // convert dps to pixels if( preview.hasPermissions() ) { - if( preview.openCameraFailed() ) { + String cameraState = Utils.getProperty("persist.sys.hwswitch.state", null); + if (cameraState != null && DeviceSettings.isMurenaTwo() && preview.openCameraFailed()) { + boolean enabled = Objects.equals(cameraState, "1"); + int text1 = enabled ? R.string.failed_to_open_camera_two_1_disabled : R.string.failed_to_open_camera_two_1_enabled; + int text2 = enabled ? R.string.failed_to_open_camera_two_2_disabled : R.string.failed_to_open_camera_two_2_enabled; + canvas.drawText(getContext().getResources().getString(text1), canvas.getWidth() / 2.0f, canvas.getHeight() / 2.0f, p); + canvas.drawText(getContext().getResources().getString(text2), canvas.getWidth() / 2.0f, canvas.getHeight() / 2.0f + pixels_offset, p); + } + + if ( !DeviceSettings.isMurenaTwo() && preview.openCameraFailed() ) { canvas.drawText(getContext().getResources().getString(R.string.failed_to_open_camera_1), canvas.getWidth() / 2.0f, canvas.getHeight() / 2.0f, p); canvas.drawText(getContext().getResources().getString(R.string.failed_to_open_camera_2), canvas.getWidth() / 2.0f, canvas.getHeight() / 2.0f + pixels_offset, p); canvas.drawText(getContext().getResources().getString(R.string.failed_to_open_camera_3), canvas.getWidth() / 2.0f, canvas.getHeight() / 2.0f + 2 * pixels_offset, p); @@ -2136,7 +2146,7 @@ public class DrawPreview { } int top_x = (int) (5 * scale_dp + 0.5f); // convert dps to pixels - int top_y = (int) (5 * scale_dp + 0.5f); // convert dps to pixels + int top_y = (int) (15 * scale_dp + 0.5f); // convert dps to pixels View top_icon = main_activity.getMainUI().getTopIcon(); if( top_icon != null ) { if( last_top_icon_shift_time == 0 || time_ms > last_top_icon_shift_time + 1000 ) { @@ -2178,73 +2188,6 @@ public class DrawPreview { } } - { - /*int focus_seekbars_margin_left_dp = 85; - if( want_histogram ) - focus_seekbars_margin_left_dp += DrawPreview.histogram_height_dp;*/ - // 135 needed to make room for on-screen info lines in DrawPreview.onDrawInfoLines(), including the histogram - // but we also need to take the top_icon_shift into account, for widescreen aspect ratios and "icons along top" UI placement - int focus_seekbars_margin_left_dp = 135; - int new_focus_seekbars_margin_left = (int) (focus_seekbars_margin_left_dp * scale_dp + 0.5f); // convert dps to pixels - if( top_icon_shift > 0 ) { - //noinspection SuspiciousNameCombination - new_focus_seekbars_margin_left += top_icon_shift; - } - - if( focus_seekbars_margin_left == -1 || new_focus_seekbars_margin_left != focus_seekbars_margin_left ) { - // we check whether focus_seekbars_margin_left has changed, in case there is a performance cost for setting layoutparams - this.focus_seekbars_margin_left = new_focus_seekbars_margin_left; - if( MyDebug.LOG ) - Log.d(TAG, "set focus_seekbars_margin_left to " + focus_seekbars_margin_left); - - // "left" and "right" here are written assuming we're in landscape system orientation - - View view = main_activity.findViewById(R.id.focus_seekbar); - RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - preview.getView().getLocationOnScreen(gui_location); - int preview_left = gui_location[system_orientation_portrait ? 1 : 0]; - if( system_orientation == MainActivity.SystemOrientation.REVERSE_LANDSCAPE ) - preview_left += preview.getView().getWidth(); // actually want preview-right for reverse landscape - - view.getLocationOnScreen(gui_location); - int seekbar_right = gui_location[system_orientation_portrait ? 1 : 0]; - if( system_orientation == MainActivity.SystemOrientation.LANDSCAPE || system_orientation == MainActivity.SystemOrientation.PORTRAIT ) { - // n.b., we read view.getWidth() even if system_orientation is portrait, because the seekbar is rotated in portrait orientation - seekbar_right += view.getWidth(); - } - else { - // and for reversed landscape, the seekbar is rotated 180 degrees, and getLocationOnScreen() returns the location after the rotation - seekbar_right -= view.getWidth(); - } - - int min_seekbar_width = (int) (150 * scale_dp + 0.5f); // convert dps to pixels - int new_seekbar_width; - if( system_orientation == MainActivity.SystemOrientation.LANDSCAPE || system_orientation == MainActivity.SystemOrientation.PORTRAIT ) { - new_seekbar_width = seekbar_right - (preview_left+focus_seekbars_margin_left); - } - else { - // reversed landscape - new_seekbar_width = preview_left - focus_seekbars_margin_left - seekbar_right; - } - new_seekbar_width = Math.max(new_seekbar_width, min_seekbar_width); - /*if( MyDebug.LOG ) { - Log.d(TAG, "preview_left: " + preview_left); - Log.d(TAG, "seekbar_right: " + seekbar_right); - Log.d(TAG, "new_seekbar_width: " + new_seekbar_width); - }*/ - layoutParams.width = new_seekbar_width; - view.setLayoutParams(layoutParams); - - view = main_activity.findViewById(R.id.focus_bracketing_target_seekbar); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.width = new_seekbar_width; - view.setLayoutParams(layoutParams); - - // need to update due to changing width of focus seekbars - main_activity.getMainUI().setFocusSeekbarsRotation(); - } - } - int battery_x = top_x; int battery_y = top_y + (int) (5 * scale_dp + 0.5f); int battery_width = (int) (5 * scale_dp + 0.5f); // convert dps to pixels @@ -2312,6 +2255,11 @@ public class DrawPreview { return angle_step; } + @NonNull + private String getZoomText(float zoom_ratio) { + return "· " + zoom_ratio + "X" + " ·"; + } + private void drawAngleLines(Canvas canvas, int device_ui_rotation, long time_ms) { Preview preview = main_activity.getPreview(); CameraController camera_controller = preview.getCameraController(); @@ -2613,7 +2561,7 @@ public class DrawPreview { if( camera_controller != null && this.thumbnail_anim && last_thumbnail != null ) { int ui_rotation = preview.getUIRotation(); long time = time_ms - this.thumbnail_anim_start_ms; - final long duration = 500; + final long duration = 400; if( time > duration ) { if( MyDebug.LOG ) Log.d(TAG, "thumbnail_anim finished"); diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java index ea1ae939ac423c0b5b7b7de1fee4ae1186aee642..21cb642ee674b55bafc2bab0fa40bcc3e06c9efa 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java +++ b/app/src/main/java/net/sourceforge/opencamera/ui/MainUI.java @@ -1,6 +1,7 @@ package net.sourceforge.opencamera.ui; import net.sourceforge.opencamera.MyApplicationInterface; +import net.sourceforge.opencamera.ScaleUtils; import net.sourceforge.opencamera.cameracontroller.CameraController; import net.sourceforge.opencamera.MainActivity; import net.sourceforge.opencamera.MyDebug; @@ -41,6 +42,11 @@ import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.ZoomControls; +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; + import java.util.ArrayList; import java.util.Hashtable; import java.util.List; @@ -188,6 +194,8 @@ public class MainUI { } private UIPlacement computeUIPlacement() { + // Default top + if (true) return UIPlacement.UIPLACEMENT_TOP; SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(main_activity); String ui_placement_string = sharedPreferences.getString(PreferenceKeys.UIPlacementPreferenceKey, "ui_top"); switch( ui_placement_string ) { @@ -426,17 +434,6 @@ public class MainUI { if( ui_placement == UIPlacement.UIPLACEMENT_TOP ) { // not part of the icon panel in TOP mode view = main_activity.findViewById(R.id.gallery); - layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(above, 0); - layoutParams.addRule(below, 0); - layoutParams.addRule(left_of, 0); - layoutParams.addRule(right_of, 0); - setMarginsForSystemUI(layoutParams, 0, gallery_top_gap, gallery_navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); } else { @@ -514,6 +511,7 @@ public class MainUI { }*/ int total_button_size = count*button_size; int margin = 0; + int topMargin = (int) ScaleUtils.convertDpToPx(main_activity, 8.0f); if( total_button_size > display_height ) { if( MyDebug.LOG ) Log.d(TAG, "need to reduce button size"); @@ -523,7 +521,7 @@ public class MainUI { if( MyDebug.LOG ) Log.d(TAG, "need to increase margin"); if( count > 1 ) - margin = (display_height - total_button_size) / (count-1); + margin = (display_height - total_button_size - (2 * topMargin)) / (count - 1); } if( MyDebug.LOG ) { Log.d(TAG, "button_size: " + button_size); @@ -544,15 +542,16 @@ public class MainUI { // is displayed (when taking a photo) if it is still shown left-most, rather than centred; also // needed for "pause preview" trash/icons to be shown properly (test by rotating the phone to update // the layout) - int margin_first = this_view==first_visible_view ? 0 : margin/2; - int margin_last = this_view==last_visible_view ? 0 : margin/2; + int margin_first = this_view==first_visible_view ? topMargin : margin/2; + int margin_last = this_view==last_visible_view ? topMargin : margin/2; setMarginsForSystemUI(layoutParams, 0, margin_first, 0, margin_last); layoutParams.width = button_size; layoutParams.height = button_size; this_view.setLayoutParams(layoutParams); } } - top_icon = first_visible_view; + + top_icon = setUpTopBarHolderUI(system_orientation_portrait, system_orientation_reversed_landscape, button_size); } } else { @@ -576,166 +575,40 @@ public class MainUI { // end icon panel + int navigationBarSideMargin = (int) ScaleUtils.convertDpToPx(main_activity, 23.0f); + navigationBarSideMargin += navigation_gap; + + setUpBottomBarHolderUI(system_orientation_portrait, system_orientation_reversed_landscape, navigationBarSideMargin); + view = main_activity.findViewById(R.id.take_photo); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(center_vertical, RelativeLayout.TRUE); - layoutParams.addRule(center_horizontal, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); view = main_activity.findViewById(R.id.switch_camera); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(ui_independent_above, R.id.take_photo); - layoutParams.addRule(ui_independent_below, 0); - layoutParams.addRule(ui_independent_left_of, 0); - layoutParams.addRule(ui_independent_right_of, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); - view = main_activity.findViewById(R.id.switch_multi_camera); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(ui_independent_above, 0); - layoutParams.addRule(ui_independent_below, 0); - layoutParams.addRule(ui_independent_left_of, R.id.switch_camera); - layoutParams.addRule(ui_independent_right_of, 0); - layoutParams.addRule(align_top, R.id.switch_camera); - layoutParams.addRule(align_bottom, R.id.switch_camera); - layoutParams.addRule(align_left, 0); - layoutParams.addRule(align_right, 0); - { - int margin = (int) (5 * scale + 0.5f); // convert dps to pixels - setMarginsForSystemUI(layoutParams, 0, 0, margin, 0); - } - view.setLayoutParams(layoutParams); - setViewRotation(view, ui_rotation); + setupMultiCameraUI(ui_rotation, system_orientation_portrait, system_orientation_reversed_landscape); view = main_activity.findViewById(R.id.pause_video); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(ui_independent_above, R.id.take_photo); - layoutParams.addRule(ui_independent_below, 0); - layoutParams.addRule(ui_independent_left_of, 0); - layoutParams.addRule(ui_independent_right_of, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); view = main_activity.findViewById(R.id.cancel_panorama); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(above, R.id.take_photo); - layoutParams.addRule(below, 0); - layoutParams.addRule(left_of, 0); - layoutParams.addRule(right_of, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); + setViewRotation(view, ui_rotation); + + view = main_activity.findViewById(R.id.finish_panorama); setViewRotation(view, ui_rotation); view = main_activity.findViewById(R.id.switch_video); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(ui_independent_above, 0); - layoutParams.addRule(ui_independent_below, R.id.take_photo); - layoutParams.addRule(ui_independent_left_of, 0); - layoutParams.addRule(ui_independent_right_of, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); view = main_activity.findViewById(R.id.take_photo_when_video_recording); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(ui_independent_above, 0); - layoutParams.addRule(ui_independent_below, R.id.take_photo); - layoutParams.addRule(ui_independent_left_of, 0); - layoutParams.addRule(ui_independent_right_of, 0); - setMarginsForSystemUI(layoutParams, 0, 0, navigation_gap, 0); - view.setLayoutParams(layoutParams); setViewRotation(view, ui_rotation); - view = main_activity.findViewById(R.id.zoom); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, RelativeLayout.TRUE); - view.setLayoutParams(layoutParams); - setFixedRotation(main_activity.findViewById(R.id.zoom), 0, 0, navigation_gap, 0); - view.setRotation(view.getRotation()+180.0f); // should always match the zoom_seekbar, so that zoom in and out are in the same directions + setUpZoomControlUI(ui_rotation, system_orientation_portrait, system_orientation_reversed_landscape); - view = main_activity.findViewById(R.id.zoom_seekbar); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - // if we are showing the zoom control, the align next to that; otherwise have it aligned close to the edge of screen - if( sharedPreferences.getBoolean(PreferenceKeys.ShowZoomControlsPreferenceKey, false) ) { - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, 0); - layoutParams.addRule(above, R.id.zoom); - layoutParams.addRule(below, 0); - layoutParams.addRule(left_of, 0); - layoutParams.addRule(right_of, 0); - // margins set below in setFixedRotation() - } - else { - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, RelativeLayout.TRUE); - // margins set below in setFixedRotation() - // need to clear the others, in case we turn zoom controls on/off - layoutParams.addRule(above, 0); - layoutParams.addRule(below, 0); - layoutParams.addRule(left_of, 0); - layoutParams.addRule(right_of, 0); - } - view.setLayoutParams(layoutParams); - int margin = (int) (20 * scale + 0.5f); // convert dps to pixels - setFixedRotation(main_activity.findViewById(R.id.zoom_seekbar), 0, 0, margin+navigation_gap, 0); - - view = main_activity.findViewById(R.id.focus_seekbar); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(left_of, R.id.zoom_seekbar); - layoutParams.addRule(right_of, 0); - layoutParams.addRule(above, 0); - layoutParams.addRule(below, 0); - layoutParams.addRule(align_parent_top, 0); - layoutParams.addRule(align_parent_bottom, RelativeLayout.TRUE); - layoutParams.addRule(align_parent_left, 0); - layoutParams.addRule(align_parent_right, 0); - view.setLayoutParams(layoutParams); - - view = main_activity.findViewById(R.id.focus_bracketing_target_seekbar); - layoutParams = (RelativeLayout.LayoutParams)view.getLayoutParams(); - layoutParams.addRule(left_of, R.id.zoom_seekbar); - layoutParams.addRule(right_of, 0); - layoutParams.addRule(above, R.id.focus_seekbar); - layoutParams.addRule(below, 0); - view.setLayoutParams(layoutParams); + view = main_activity.findViewById(R.id.zoom_seekbar_holder); + setViewRotation(view, ui_rotation); - setFocusSeekbarsRotation(); + setupFocusSeekBarUI(ui_rotation, system_orientation_portrait, system_orientation_reversed_landscape); } if( !popup_container_only ) @@ -842,9 +715,6 @@ public class MainUI { lp.height = height_pixels; view.setLayoutParams(lp); - view = main_activity.findViewById(R.id.exposure_seekbar_zoom); - view.setAlpha(0.5f); - view = main_activity.findViewById(R.id.iso_seekbar); lp = (RelativeLayout.LayoutParams)view.getLayoutParams(); lp.width = width_pixels; @@ -876,19 +746,19 @@ public class MainUI { layoutParams.addRule(above, 0); layoutParams.addRule(below, 0); layoutParams.addRule(left_of, 0); - layoutParams.addRule(right_of, R.id.popup); + layoutParams.addRule(right_of, R.id.top_bg); layoutParams.addRule(align_parent_top, system_orientation_portrait ? 0 : RelativeLayout.TRUE); layoutParams.addRule(align_parent_bottom, system_orientation_portrait ? 0 : RelativeLayout.TRUE); layoutParams.addRule(align_parent_left, 0); layoutParams.addRule(align_parent_right, 0); } else { - layoutParams.addRule(align_right, R.id.popup); + layoutParams.addRule(align_right, R.id.top_bg); layoutParams.addRule(align_bottom, 0); layoutParams.addRule(align_left, 0); layoutParams.addRule(align_top, 0); layoutParams.addRule(above, 0); - layoutParams.addRule(below, R.id.popup); + layoutParams.addRule(below, R.id.top_bg); layoutParams.addRule(left_of, 0); layoutParams.addRule(right_of, 0); layoutParams.addRule(align_parent_top, 0); @@ -939,6 +809,298 @@ public class MainUI { } } + private void setupMultiCameraUI(int ui_rotation, boolean orientationPortrait, boolean orientationReverseLandscape) { + View multiCamera = main_activity.findViewById(R.id.switch_multi_camera); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(multiCamera.getLayoutParams().width, multiCamera.getLayoutParams().height); + + int size21dp = (int) ScaleUtils.convertDpToPx(main_activity, 21.0f); + int size20dp = (int) ScaleUtils.convertDpToPx(main_activity, 20.0f); + + if (orientationPortrait) { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE); + layoutParams.addRule(RelativeLayout.ABOVE, R.id.bottom_buttons_holder); + layoutParams.setMargins(0, 0, size21dp, size20dp); + } else if (orientationReverseLandscape) { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); + layoutParams.addRule(RelativeLayout.END_OF, R.id.bottom_buttons_holder); + layoutParams.setMargins(size20dp, 0, 0, size21dp); + } else { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); + layoutParams.addRule(RelativeLayout.START_OF, R.id.bottom_buttons_holder); + layoutParams.setMargins(0, size21dp, size20dp, 0); + } + + multiCamera.setLayoutParams(layoutParams); + setViewRotation(multiCamera, ui_rotation); + } + + private void setupFocusSeekBarUI(int rotation, boolean orientationPortrait, boolean orientationReverseLandscape) { + View focusSeekBarHolder = main_activity.findViewById(R.id.focus_seekbar_holder); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + if (orientationPortrait) { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE); + layoutParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } else if (orientationReverseLandscape) { + layoutParams.addRule(RelativeLayout.END_OF, R.id.bottom_buttons_holder); + layoutParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } else { + layoutParams.addRule(RelativeLayout.START_OF, R.id.bottom_buttons_holder); + layoutParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } + + focusSeekBarHolder.setLayoutParams(layoutParams); + setViewRotation(focusSeekBarHolder, rotation + 90); + } + + private void setUpZoomControlUI(int rotation, boolean orientationPortrait, boolean orientationReverseLandscape) { + int margin = (int) ScaleUtils.convertDpToPx(main_activity, 60.0f); + + View zoom = main_activity.findViewById(R.id.zoom); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(zoom.getLayoutParams().width, zoom.getLayoutParams().height); + + if (orientationPortrait) { + layoutParams.addRule(RelativeLayout.BELOW, R.id.top_bg); + layoutParams.setMargins(0, margin, 0, 0); + layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE); + } else if (orientationReverseLandscape) { + layoutParams.addRule(RelativeLayout.START_OF, R.id.top_bg); + layoutParams.setMargins(0, 0, margin, 0); + layoutParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } else { + layoutParams.addRule(RelativeLayout.END_OF, R.id.top_bg); + layoutParams.setMargins(margin, 0, 0, 0); + layoutParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } + + zoom.setLayoutParams(layoutParams); + setViewRotation(zoom, rotation); + } + + private View setUpTopBarHolderUI(boolean orientationPortrait, boolean orientationReverseLandscape, int topButtonSize) { + int width = orientationPortrait ? ViewGroup.LayoutParams.MATCH_PARENT : topButtonSize; + int height = orientationPortrait ? topButtonSize : ViewGroup.LayoutParams.MATCH_PARENT; + int topBarAlignment = orientationPortrait ? RelativeLayout.ALIGN_PARENT_TOP : (orientationReverseLandscape ? RelativeLayout.ALIGN_PARENT_END : RelativeLayout.ALIGN_PARENT_START); + + View topBar = main_activity.findViewById(R.id.top_bg); + RelativeLayout.LayoutParams topBarLayoutParams = new RelativeLayout.LayoutParams(width, height); + topBarLayoutParams.addRule(topBarAlignment, RelativeLayout.TRUE); + topBar.setLayoutParams(topBarLayoutParams); + return topBar; + } + + private void setUpBottomBarHolderUI(boolean orientationPortrait, boolean orientationReverseLandscape, int navigationBarSideMargin) { + ConstraintLayout bottomBar = main_activity.findViewById(R.id.bottom_buttons_holder); + // set up layoutParam + int bottomBarWidth = orientationPortrait ? ViewGroup.LayoutParams.MATCH_PARENT : ViewGroup.LayoutParams.WRAP_CONTENT; + int bottomBarHeight = orientationPortrait ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT; + int bottomBarAlignment = orientationPortrait ? RelativeLayout.ALIGN_PARENT_BOTTOM : (orientationReverseLandscape ? RelativeLayout.ALIGN_PARENT_START : RelativeLayout.ALIGN_PARENT_END); + RelativeLayout.LayoutParams bottomBarLayoutParams = new RelativeLayout.LayoutParams(bottomBarWidth, bottomBarHeight); + bottomBarLayoutParams.addRule(bottomBarAlignment, RelativeLayout.TRUE); + bottomBar.setLayoutParams(bottomBarLayoutParams); + + // set up padding + int size20dp = (int) ScaleUtils.convertDpToPx(main_activity, 20.0f); + int size23dp = (int) ScaleUtils.convertDpToPx(main_activity, 23.0f); + + int bottomBarLeftPadding = orientationPortrait ? size20dp : (orientationReverseLandscape ? navigationBarSideMargin : size23dp); + int bottomBarRightPadding = orientationPortrait ? size20dp : (orientationReverseLandscape ? size23dp : navigationBarSideMargin); + int bottomBarTopPadding = orientationPortrait ? size23dp : size20dp; + int bottomBarBottomPadding = orientationPortrait ? navigationBarSideMargin : size20dp; + bottomBar.setPadding(bottomBarLeftPadding, bottomBarTopPadding, bottomBarRightPadding, bottomBarBottomPadding); + + setUpBottomItemsUI(bottomBar, orientationPortrait, orientationReverseLandscape); + } + + private void setUpBottomItemsUI(ConstraintLayout bottomHolder, boolean orientationPortrait, boolean orientationReverseLandscape) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(bottomHolder); + + clearAllDynamicBottomBarItemConstraint(constraintSet); + + if (orientationPortrait) { + connectPortraitConstraintForBottomItems(constraintSet); + } else if (orientationReverseLandscape) { + connectReverseLandscapeConstraintForBottomItems(constraintSet); + } else { + connectLandscapeConstraintForBottomItems(constraintSet); + } + + constraintSet.applyTo(bottomHolder); + } + + private void clearAllDynamicBottomBarItemConstraint(@NonNull ConstraintSet constraintSet) { + clearAllConstraint(constraintSet, R.id.place_holder_view); + clearAllConstraint(constraintSet, R.id.gallery); + clearAllConstraint(constraintSet, R.id.switch_camera); + clearAllConstraint(constraintSet, R.id.pause_video); + clearAllConstraint(constraintSet, R.id.switch_video); + clearAllConstraint(constraintSet, R.id.take_photo_when_video_recording); + clearAllConstraint(constraintSet, R.id.cancel_panorama); + clearAllConstraint(constraintSet, R.id.finish_panorama); + } + + private void clearAllConstraint(@NonNull ConstraintSet constraintSet, @IdRes int id) { + constraintSet.clear(id, ConstraintSet.TOP); + constraintSet.clear(id, ConstraintSet.BOTTOM); + constraintSet.clear(id, ConstraintSet.START); + constraintSet.clear(id, ConstraintSet.END); + } + + private void connectConstraintParentTopBottom(@NonNull ConstraintSet constraintSet, @IdRes int id) { + constraintSet.connect(id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); + constraintSet.connect(id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); + } + + private void connectConstraintParentStartEnd(@NonNull ConstraintSet constraintSet, @IdRes int id) { + constraintSet.connect(id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); + constraintSet.connect(id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); + } + + private void connectPortraitConstraintForBottomItems(@NonNull ConstraintSet constraintSet) { + int id; + + //placeHolderView + id = R.id.place_holder_view; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); + + //gallery + id = R.id.gallery; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); + + //switchCamera + id = R.id.switch_camera; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, R.id.gallery, ConstraintSet.START); + constraintSet.connect(id, ConstraintSet.START, R.id.take_photo, ConstraintSet.END); + + //pauseVideo + id = R.id.pause_video; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, R.id.gallery, ConstraintSet.START); + constraintSet.connect(id, ConstraintSet.START, R.id.take_photo, ConstraintSet.END); + + //switchVideo + id = R.id.switch_video; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, R.id.take_photo, ConstraintSet.START); + constraintSet.connect(id, ConstraintSet.START, R.id.place_holder_view, ConstraintSet.END); + + //takePhotoOnVideo + id = R.id.take_photo_when_video_recording; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, R.id.take_photo, ConstraintSet.START); + constraintSet.connect(id, ConstraintSet.START, R.id.place_holder_view, ConstraintSet.END); + + //cancelPanorama + id = R.id.cancel_panorama; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.START, R.id.place_holder_center_view, ConstraintSet.END); + + //finishPanorama + id = R.id.finish_panorama; + connectConstraintParentTopBottom(constraintSet, id); + constraintSet.connect(id, ConstraintSet.END, R.id.place_holder_center_view, ConstraintSet.START); + } + + private void connectReverseLandscapeConstraintForBottomItems(@NonNull ConstraintSet constraintSet) { + int id; + + //placeHolderView + id = R.id.place_holder_view; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); + + //gallery + id = R.id.gallery; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); + + //switchCamera + id = R.id.switch_camera; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.gallery, ConstraintSet.TOP); + constraintSet.connect(id, ConstraintSet.TOP, R.id.take_photo, ConstraintSet.BOTTOM); + + //pauseVideo + id = R.id.pause_video; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.gallery, ConstraintSet.TOP); + constraintSet.connect(id, ConstraintSet.TOP, R.id.take_photo, ConstraintSet.BOTTOM); + + //switchVideo + id = R.id.switch_video; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.take_photo, ConstraintSet.TOP); + constraintSet.connect(id, ConstraintSet.TOP, R.id.place_holder_view, ConstraintSet.BOTTOM); + + //takePhotoOnVideo + id = R.id.take_photo_when_video_recording; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.take_photo, ConstraintSet.TOP); + constraintSet.connect(id, ConstraintSet.TOP, R.id.place_holder_view, ConstraintSet.BOTTOM); + + //cancelPanorama + id = R.id.cancel_panorama; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.place_holder_center_view, ConstraintSet.BOTTOM); + + //finishPanorama + id = R.id.finish_panorama; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.place_holder_center_view, ConstraintSet.TOP); + } + + private void connectLandscapeConstraintForBottomItems(@NonNull ConstraintSet constraintSet) { + int id; + + //placeHolderView + id = R.id.place_holder_view; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM); + + //gallery + id = R.id.gallery; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); + + //switchCamera + id = R.id.switch_camera; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.gallery, ConstraintSet.BOTTOM); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.take_photo, ConstraintSet.TOP); + + //pauseVideo + id = R.id.pause_video; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.gallery, ConstraintSet.BOTTOM); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.take_photo, ConstraintSet.TOP); + + //switchVideo + id = R.id.switch_video; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.take_photo, ConstraintSet.BOTTOM); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.place_holder_view, ConstraintSet.TOP); + + //takePhotoOnVideo + id = R.id.take_photo_when_video_recording; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.take_photo, ConstraintSet.BOTTOM); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.place_holder_view, ConstraintSet.TOP); + + //cancelPanorama + id = R.id.cancel_panorama; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.BOTTOM, R.id.place_holder_center_view, ConstraintSet.TOP); + + //finishPanorama + id = R.id.finish_panorama; + connectConstraintParentStartEnd(constraintSet, id); + constraintSet.connect(id, ConstraintSet.TOP, R.id.place_holder_center_view, ConstraintSet.BOTTOM); + } + /** Wrapper for layoutParams.setMargins, but where the margins are supplied for landscape orientation, * and if in portrait these are automatically rotated. */ @@ -1076,22 +1238,24 @@ public class MainUI { int resource; int content_description; int switch_video_content_description; + boolean panorama_recording = main_activity.getApplicationInterface().getPhotoMode() + == MyApplicationInterface.PhotoMode.Panorama && + main_activity.getApplicationInterface().getGyroSensor().isRecording(); + boolean isQrMode = main_activity.getPreview().isQRCode(); + // The switch order is camera -> video -> qrcode -> camera... if( main_activity.getPreview().isVideo() ) { if( MyDebug.LOG ) Log.d(TAG, "set icon to video"); - resource = main_activity.getPreview().isVideoRecording() ? R.drawable.take_video_recording : R.drawable.take_video_selector; + resource = main_activity.getPreview().isVideoRecording() ? R.drawable.ic_camera_video_recording : R.drawable.ic_camera_video; content_description = main_activity.getPreview().isVideoRecording() ? R.string.stop_video : R.string.start_video; + switch_video_content_description = R.string.switch_to_qrcode; + } else if (isQrMode) { + if (MyDebug.LOG) + Log.d(TAG, "set icon to qrcode"); + resource = R.drawable.empty; + content_description = 0; switch_video_content_description = R.string.switch_to_photo; - } - else if( main_activity.getApplicationInterface().getPhotoMode() == MyApplicationInterface.PhotoMode.Panorama && - main_activity.getApplicationInterface().getGyroSensor().isRecording() ) { - if( MyDebug.LOG ) - Log.d(TAG, "set icon to recording panorama"); - resource = R.drawable.baseline_check_white_48; - content_description = R.string.finish_panorama; - switch_video_content_description = R.string.switch_to_video; - } - else { + } else { // Camera case if( MyDebug.LOG ) Log.d(TAG, "set icon to photo"); resource = R.drawable.take_photo_selector; @@ -1099,14 +1263,57 @@ public class MainUI { switch_video_content_description = R.string.switch_to_video; } view.setImageResource(resource); - view.setContentDescription( main_activity.getResources().getString(content_description) ); + if (content_description==0) { + view.setContentDescription(""); + } else { + view.setContentDescription(main_activity.getResources().getString(content_description)); + } view.setTag(resource); // for testing view = main_activity.findViewById(R.id.switch_video); view.setContentDescription( main_activity.getResources().getString(switch_video_content_description) ); - resource = main_activity.getPreview().isVideo() ? R.drawable.take_photo : R.drawable.take_video; + if (main_activity.getPreview().isVideo()) { + resource = R.drawable.ic_switch_qrcode; + } else if (isQrMode) { + resource = R.drawable.ic_switch_camera; + } else { // camera + resource = R.drawable.ic_switch_video; + } view.setImageResource(resource); view.setTag(resource); // for testing + + // Hide/Show gallery & switch camera icons. + boolean showGalleryAndSwitch = isQrMode || panorama_recording; + main_activity.findViewById(R.id.gallery).setVisibility( + showGalleryAndSwitch ? View.INVISIBLE : View.VISIBLE); + main_activity.findViewById(R.id.switch_camera).setVisibility( + showGalleryAndSwitch ? View.INVISIBLE : View.VISIBLE); + } + } + /** + * Show/hide photoShutter, panorama cancel & finish buttons on photoMode. + * If user save not to show photoShutter button, don't show photoShutter & panorama finish buttons. + * If user wants to view the shutter button, update the shutterButton's icon to the appropriate one. + */ + public void handlePanoromaModeButtonsVisibility() { + boolean isPanoramaRunning = (main_activity.getApplicationInterface().getPhotoMode() == MyApplicationInterface.PhotoMode.Panorama && + main_activity.getApplicationInterface().getGyroSensor().isRecording()); + + View view = main_activity.findViewById(R.id.cancel_panorama); + view.setVisibility(isPanoramaRunning ? View.VISIBLE : View.GONE); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(main_activity); + if (!sharedPreferences.getBoolean(PreferenceKeys.ShowTakePhotoPreferenceKey, true)) { + return; + } + + view = main_activity.findViewById(R.id.finish_panorama); + view.setVisibility(isPanoramaRunning ? View.VISIBLE : View.GONE); + + view = main_activity.findViewById(R.id.take_photo); + view.setVisibility(isPanoramaRunning ? View.INVISIBLE : View.VISIBLE); + if (!isPanoramaRunning) { + setTakePhotoIcon(); } } @@ -1148,11 +1355,11 @@ public class MainUI { int content_description; if( main_activity.getPreview().isVideoRecordingPaused() ) { content_description = R.string.resume_video; - pauseVideoButton.setImageResource(R.drawable.ic_play_circle_outline_white_48dp); + pauseVideoButton.setImageResource(R.drawable.ic_play); } else { content_description = R.string.pause_video; - pauseVideoButton.setImageResource(R.drawable.ic_pause_circle_outline_white_48dp); + pauseVideoButton.setImageResource(R.drawable.ic_pause); } if( MyDebug.LOG ) Log.d(TAG, "content_description: " + main_activity.getResources().getString(content_description)); @@ -1346,6 +1553,7 @@ public class MainUI { View settingsButton = main_activity.findViewById(R.id.settings); View zoomControls = main_activity.findViewById(R.id.zoom); View zoomSeekBar = main_activity.findViewById(R.id.zoom_seekbar); + View zoomSeekbarIcon = main_activity.findViewById(R.id.zoom_seekbar_icon); View focusSeekBar = main_activity.findViewById(R.id.focus_seekbar); View focusBracketingTargetSeekBar = main_activity.findViewById(R.id.focus_bracketing_target_seekbar); if( main_activity.getPreview().getCameraControllerManager().getNumberOfCameras() > 1 ) @@ -1386,14 +1594,15 @@ public class MainUI { if( main_activity.getPreview().supportsZoom() && sharedPreferences.getBoolean(PreferenceKeys.ShowZoomControlsPreferenceKey, false) ) { zoomControls.setVisibility(visibility); } - if( main_activity.getPreview().supportsZoom() && sharedPreferences.getBoolean(PreferenceKeys.ShowZoomSliderControlsPreferenceKey, true) ) { + if( main_activity.getPreview().supportsZoom() && sharedPreferences.getBoolean(PreferenceKeys.ShowZoomSliderControlsPreferenceKey, false) ) { zoomSeekBar.setVisibility(visibility); + zoomSeekbarIcon.setVisibility(visibility); } if( main_activity.showManualFocusSeekbar(false) ) focusSeekBar.setVisibility(visibility); if( main_activity.showManualFocusSeekbar(true) ) focusBracketingTargetSeekBar.setVisibility(visibility); - String pref_immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_low_profile"); + String pref_immersive_mode = sharedPreferences.getString(PreferenceKeys.ImmersiveModePreferenceKey, "immersive_mode_off"); if( pref_immersive_mode.equals("immersive_mode_everything") ) { if( sharedPreferences.getBoolean(PreferenceKeys.ShowTakePhotoPreferenceKey, true) ) { View takePhotoButton = main_activity.findViewById(R.id.take_photo); @@ -1410,6 +1619,12 @@ public class MainUI { if( main_activity.getApplicationInterface().getGyroSensor().isRecording() ) { View cancelPanoramaButton = main_activity.findViewById(R.id.cancel_panorama); cancelPanoramaButton.setVisibility(visibility); + + if (sharedPreferences.getBoolean(PreferenceKeys.ShowTakePhotoPreferenceKey, true)) { + View finishPanoramaButton = main_activity.findViewById(R.id.finish_panorama); + finishPanoramaButton.setVisibility(visibility); + } + } } if( !immersive_mode ) { @@ -1524,14 +1739,15 @@ public class MainUI { public void updateExposureLockIcon() { ImageButton view = main_activity.findViewById(R.id.exposure_lock); boolean enabled = main_activity.getPreview().isExposureLocked(); - view.setImageResource(enabled ? R.drawable.exposure_locked : R.drawable.exposure_unlocked); + view.setImageResource(enabled ? R.drawable.ic_camera_exposure_locked : R.drawable.ic_camera_exposure_unlocked); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.exposure_unlock : R.string.exposure_lock) ); } public void updateWhiteBalanceLockIcon() { ImageButton view = main_activity.findViewById(R.id.white_balance_lock); boolean enabled = main_activity.getPreview().isWhiteBalanceLocked(); - view.setImageResource(enabled ? R.drawable.white_balance_locked : R.drawable.white_balance_unlocked); + view.setImageResource(R.drawable.ic_white_balance_unlocked); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.white_balance_unlock : R.string.white_balance_lock) ); } @@ -1544,7 +1760,7 @@ public class MainUI { view.setImageResource(R.drawable.raw_only_icon); } else { - view.setImageResource(R.drawable.raw_icon); + view.setImageResource(R.drawable.ic_raw); } } else { @@ -1555,34 +1771,39 @@ public class MainUI { public void updateStoreLocationIcon() { ImageButton view = main_activity.findViewById(R.id.store_location); boolean enabled = main_activity.getApplicationInterface().getGeotaggingPref(); - view.setImageResource(enabled ? R.drawable.ic_gps_fixed_red_48dp : R.drawable.ic_gps_fixed_white_48dp); + view.setImageResource(R.drawable.ic_gps_fixed_white_48dp); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.preference_location_disable : R.string.preference_location_enable) ); } public void updateTextStampIcon() { ImageButton view = main_activity.findViewById(R.id.text_stamp); boolean enabled = !main_activity.getApplicationInterface().getTextStampPref().isEmpty(); - view.setImageResource(enabled ? R.drawable.baseline_text_fields_red_48 : R.drawable.baseline_text_fields_white_48); + view.setImageResource(R.drawable.ic_text_stamp); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); } public void updateStampIcon() { ImageButton view = main_activity.findViewById(R.id.stamp); boolean enabled = main_activity.getApplicationInterface().getStampPref().equals("preference_stamp_yes"); - view.setImageResource(enabled ? R.drawable.ic_text_format_red_48dp : R.drawable.ic_text_format_white_48dp); + view.setImageResource(R.drawable.ic_preference_text_format); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.stamp_disable : R.string.stamp_enable) ); } public void updateFocusPeakingIcon() { ImageButton view = main_activity.findViewById(R.id.focus_peaking); boolean enabled = main_activity.getApplicationInterface().getFocusPeakingPref(); - view.setImageResource(enabled ? R.drawable.key_visualizer_red : R.drawable.key_visualizer); + view.setImageResource(R.drawable.ic_preference_focus_peaking); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.focus_peaking_disable : R.string.focus_peaking_enable) ); } public void updateAutoLevelIcon() { ImageButton view = main_activity.findViewById(R.id.auto_level); boolean enabled = main_activity.getApplicationInterface().getAutoStabilisePref(); - view.setImageResource(enabled ? R.drawable.auto_stabilise_icon_red : R.drawable.auto_stabilise_icon); + view.setImageResource(R.drawable.ic_preference_auto_stabilise); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.auto_level_disable : R.string.auto_level_enable) ); } @@ -1594,40 +1815,41 @@ public class MainUI { ImageButton view = main_activity.findViewById(R.id.cycle_flash); switch( flash_value ) { case "flash_off": - view.setImageResource(R.drawable.flash_off); + view.setImageResource(R.drawable.ic_camera_flash_off); break; case "flash_auto": case "flash_frontscreen_auto": - view.setImageResource(R.drawable.flash_auto); + view.setImageResource(R.drawable.ic_camera_flash_auto); break; case "flash_on": case "flash_frontscreen_on": - view.setImageResource(R.drawable.flash_on); + view.setImageResource(R.drawable.ic_camera_flash_on); break; case "flash_torch": case "flash_frontscreen_torch": - view.setImageResource(R.drawable.baseline_highlight_white_48); + view.setImageResource(R.drawable.ic_camera_highlight); break; case "flash_red_eye": - view.setImageResource(R.drawable.baseline_remove_red_eye_white_48); + view.setImageResource(R.drawable.ic_camera_remove_red_eye); break; default: // just in case?? Log.e(TAG, "unknown flash value " + flash_value); - view.setImageResource(R.drawable.flash_off); + view.setImageResource(R.drawable.ic_camera_flash_off); break; } } else { ImageButton view = main_activity.findViewById(R.id.cycle_flash); - view.setImageResource(R.drawable.flash_off); + view.setImageResource(R.drawable.ic_camera_flash_off); } } public void updateFaceDetectionIcon() { ImageButton view = main_activity.findViewById(R.id.face_detection); boolean enabled = main_activity.getApplicationInterface().getFaceDetectionPref(); - view.setImageResource(enabled ? R.drawable.ic_face_red_48dp : R.drawable.ic_face_white_48dp); + view.setImageResource(R.drawable.ic_face); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(enabled ? R.color.color_default_accent : R.color.white))); view.setContentDescription( main_activity.getResources().getString(enabled ? R.string.face_detection_disable : R.string.face_detection_enable) ); } @@ -1648,13 +1870,15 @@ public class MainUI { public void audioControlStarted() { ImageButton view = main_activity.findViewById(R.id.audio_control); - view.setImageResource(R.drawable.ic_mic_red_48dp); + view.setImageResource(R.drawable.ic_mic); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(R.color.color_default_accent))); view.setContentDescription( main_activity.getResources().getString(R.string.audio_control_stop) ); } public void audioControlStopped() { ImageButton view = main_activity.findViewById(R.id.audio_control); - view.setImageResource(R.drawable.ic_mic_white_48dp); + view.setImageResource(R.drawable.ic_mic); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(R.color.white))); view.setContentDescription( main_activity.getResources().getString(R.string.audio_control_start) ); } @@ -2064,7 +2288,8 @@ public class MainUI { final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(main_activity); final Preview preview = main_activity.getPreview(); ImageButton view = main_activity.findViewById(R.id.exposure); - view.setImageResource(R.drawable.ic_exposure_red_48dp); + view.setImageResource(R.drawable.ic_camera_exposure); + view.setImageTintList(ColorStateList.valueOf(main_activity.getResources().getColor(R.color.color_default_accent))); View sliders_container = main_activity.findViewById(R.id.sliders_container); sliders_container.setVisibility(View.VISIBLE); ViewGroup iso_buttons_container = main_activity.findViewById(R.id.iso_buttons); @@ -2233,8 +2458,6 @@ public class MainUI { if( main_activity.getPreview().supportsExposures() ) { exposure_seek_bar.setVisibility(View.VISIBLE); - ZoomControls seek_bar_zoom = main_activity.findViewById(R.id.exposure_seekbar_zoom); - seek_bar_zoom.setVisibility(View.VISIBLE); } else { exposure_seek_bar.setVisibility(View.GONE); @@ -2325,7 +2548,7 @@ public class MainUI { SeekBar zoomSeekBar = main_activity.findViewById(R.id.zoom_seekbar); if( MyDebug.LOG ) Log.d(TAG, "progress was: " + zoomSeekBar.getProgress()); - zoomSeekBar.setProgress(main_activity.getPreview().getMaxZoom()-new_zoom); + zoomSeekBar.setProgress(new_zoom); if( MyDebug.LOG ) Log.d(TAG, "progress is now: " + zoomSeekBar.getProgress()); } @@ -2354,8 +2577,8 @@ public class MainUI { */ public void closeExposureUI() { ImageButton image_button = main_activity.findViewById(R.id.exposure); - image_button.setImageResource(R.drawable.ic_exposure_white_48dp); - + image_button.setImageResource(R.drawable.ic_camera_exposure); + image_button.setImageTintList(ColorStateList.valueOf(Color.WHITE)); clearRemoteControlForExposureUI(); // must be called before we actually close the exposure panel View view = main_activity.findViewById(R.id.sliders_container); view.setVisibility(View.GONE); @@ -2377,25 +2600,25 @@ public class MainUI { if( MyDebug.LOG ) Log.d(TAG, "flash_value: " + flash_value); if( main_activity.getMainUI().showCycleFlashIcon() ) { - popup.setImageResource(R.drawable.popup); + popup.setImageResource(R.drawable.ic_more); } else if( flash_value != null && flash_value.equals("flash_off") ) { - popup.setImageResource(R.drawable.popup_flash_off); + popup.setImageResource(R.drawable.popup_camera_flash_off); } else if( flash_value != null && ( flash_value.equals("flash_torch") || flash_value.equals("flash_frontscreen_torch") ) ) { - popup.setImageResource(R.drawable.popup_flash_torch); + popup.setImageResource(R.drawable.popup_camera_flash_torch); } else if( flash_value != null && ( flash_value.equals("flash_auto") || flash_value.equals("flash_frontscreen_auto") ) ) { - popup.setImageResource(R.drawable.popup_flash_auto); + popup.setImageResource(R.drawable.popup_camera_flash_auto); } else if( flash_value != null && ( flash_value.equals("flash_on") || flash_value.equals("flash_frontscreen_on") ) ) { - popup.setImageResource(R.drawable.popup_flash_on); + popup.setImageResource(R.drawable.popup_camera_flash_on); } else if( flash_value != null && flash_value.equals("flash_red_eye") ) { - popup.setImageResource(R.drawable.popup_flash_red_eye); + popup.setImageResource(R.drawable.popup_camera_flash_red_eye); } else { - popup.setImageResource(R.drawable.popup); + popup.setImageResource(R.drawable.ic_more); } } diff --git a/app/src/main/java/net/sourceforge/opencamera/ui/PopupView.java b/app/src/main/java/net/sourceforge/opencamera/ui/PopupView.java index 6aa0ca0c50951dd6aa616383db66dc66af4015b9..0a70381a8e9b5fdec08fb44431b49d423a4f9db7 100644 --- a/app/src/main/java/net/sourceforge/opencamera/ui/PopupView.java +++ b/app/src/main/java/net/sourceforge/opencamera/ui/PopupView.java @@ -44,6 +44,7 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.ImageView.ScaleType; +import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.SwitchCompat; /** This defines the UI for the "popup" button, that provides quick access to a @@ -356,7 +357,7 @@ public class PopupView extends LinearLayout { LayoutParams.MATCH_PARENT ); final int left_padding = (int) (10 * scale + 0.5f); // convert dps to pixels - params.setMargins(left_padding, 0, 0, 0); + params.setMargins(left_padding, 20, 0, 40); checkBox.setLayoutParams(params); } @@ -1090,6 +1091,8 @@ public class PopupView extends LinearLayout { if( MyDebug.LOG ) Log.d(TAG, "Overall PopupView time: " + (System.nanoTime() - debug_time)); + + setBackgroundColor(getResources().getColor(R.color.color_popup_bg)); } int getTotalWidth() { @@ -1322,7 +1325,7 @@ public class PopupView extends LinearLayout { actual_max_per_row = Math.min(actual_max_per_row, max_buttons_per_row); int button_width_dp = total_width_dp/actual_max_per_row; boolean use_scrollview = false; - final int min_button_width_dp = 48; // needs to be at least 48dp to avoid Google Play pre-launch accessibility report warnings + final int min_button_width_dp = 24; // needs to be at least 48dp to avoid Google Play pre-launch accessibility report warnings if( button_width_dp < min_button_width_dp && max_buttons_per_row == 0 ) { button_width_dp = min_button_width_dp; use_scrollview = true; @@ -1348,6 +1351,13 @@ public class PopupView extends LinearLayout { if( MyDebug.LOG ) Log.d(TAG, "addButtonOptionsToPopup time 2.05: " + (System.nanoTime() - debug_time)); + int padding_dp = 0; + if (button_width_dp > min_button_width_dp) { + padding_dp = button_width_dp - min_button_width_dp; + padding_dp /= 2; + } + final int imageButtonPadding = (int) (padding_dp * scale + 0.5f); + for(int button_indx=0;button_indx"); - next_button.setTextSize(TypedValue.COMPLEX_UNIT_SP, arrow_text_size_dip); - next_button.setTypeface(null, Typeface.BOLD); next_button.setPadding(padding, padding, padding, padding); vg_params = next_button.getLayoutParams(); vg_params.width = arrow_button_w; diff --git a/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..04b8847f09d818ed8c1e4165c9b4c7413c9146cb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..45d0869b8e392a73c1703762043959f192d39604 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..278280e5cd462e7bc3d94a2926becb6f6baf2e2d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f70dc48b5b0280da0a16eba5869a50f4dd2796 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..078194289c564d2e804a790efea30975c61c986c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable/bg_rounded_corner.xml b/app/src/main/res/drawable/bg_rounded_corner.xml new file mode 100644 index 0000000000000000000000000000000000000000..a89f1dc376fff39a0ece7bfcf8e35f2426e04288 --- /dev/null +++ b/app/src/main/res/drawable/bg_rounded_corner.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_zoom_control.xml b/app/src/main/res/drawable/bg_zoom_control.xml new file mode 100644 index 0000000000000000000000000000000000000000..b183ccab147ea3b55272e9db678b4bb3f7ab97b5 --- /dev/null +++ b/app/src/main/res/drawable/bg_zoom_control.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/empty.xml b/app/src/main/res/drawable/empty.xml new file mode 100644 index 0000000000000000000000000000000000000000..c886b6ada8d7c4b15850d3df87fcfedd5aa2f271 --- /dev/null +++ b/app/src/main/res/drawable/empty.xml @@ -0,0 +1,8 @@ + + + diff --git a/app/src/main/res/drawable/ic_album.xml b/app/src/main/res/drawable/ic_album.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d405e1b1b84cd0f2e29a730306c9c2ffea5669e --- /dev/null +++ b/app/src/main/res/drawable/ic_album.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000000000000000000000000000000000000..96e51169ce283dcf4bf5c1b3345cc562458350e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 0000000000000000000000000000000000000000..b6a971563d201f7bb4cba4d507fbd3e7ee47542c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 0000000000000000000000000000000000000000..206dd4fdedab2fce1bc90d13de43d63becd1ddb2 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_bluetooth.xml b/app/src/main/res/drawable/ic_bluetooth.xml new file mode 100644 index 0000000000000000000000000000000000000000..85527b80d251929bf460fd604c5b6be1c9e7b73f --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_book.xml b/app/src/main/res/drawable/ic_book.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c6d45296cdc1d2197e7ac888b4de1437c10b84d --- /dev/null +++ b/app/src/main/res/drawable/ic_book.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_calendar_add_on.xml b/app/src/main/res/drawable/ic_calendar_add_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4cfef3e2749a069dfd986c897fe7d2bc4a6d4e6 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_add_on.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_camera_exposure.xml b/app/src/main/res/drawable/ic_camera_exposure.xml new file mode 100644 index 0000000000000000000000000000000000000000..a8c73ded02ee55c33e4c51ab18cdaedb512656c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_exposure.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_exposure_locked.xml b/app/src/main/res/drawable/ic_camera_exposure_locked.xml new file mode 100644 index 0000000000000000000000000000000000000000..7df52645732c78ad04bed0652f108789a6e56d3b --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_exposure_locked.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_camera_exposure_unlocked.xml b/app/src/main/res/drawable/ic_camera_exposure_unlocked.xml new file mode 100644 index 0000000000000000000000000000000000000000..1bb92e9b19b36d3114e15e6279f055f29a88099f --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_exposure_unlocked.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_camera_flash_auto.xml b/app/src/main/res/drawable/ic_camera_flash_auto.xml new file mode 100644 index 0000000000000000000000000000000000000000..05695cc5f486214746619e7d131c56da0bfe696e --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_flash_auto.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_flash_off.xml b/app/src/main/res/drawable/ic_camera_flash_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..4f9de8fe8753d5941789b82d7758b5c44c5afef0 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_flash_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_flash_on.xml b/app/src/main/res/drawable/ic_camera_flash_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..f730d34875b70e16579f2fe8fb3373b00c392798 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_flash_on.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_highlight.xml b/app/src/main/res/drawable/ic_camera_highlight.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a40bbed8777575f29e0000c426a0121e9e7d115 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_highlight.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_camera_remove_red_eye.xml b/app/src/main/res/drawable/ic_camera_remove_red_eye.xml new file mode 100644 index 0000000000000000000000000000000000000000..b2f4b848b78bda38828783f08c4cac9796717d94 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_remove_red_eye.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_shutter.xml b/app/src/main/res/drawable/ic_camera_shutter.xml new file mode 100644 index 0000000000000000000000000000000000000000..9abbe1d59207dd0ca00edce1879569c00a95035c --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_shutter.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_camera_toggle.xml b/app/src/main/res/drawable/ic_camera_toggle.xml new file mode 100644 index 0000000000000000000000000000000000000000..56a26f6f078204ab9619ce16727543d209d6ed19 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_toggle.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_camera_video.xml b/app/src/main/res/drawable/ic_camera_video.xml new file mode 100644 index 0000000000000000000000000000000000000000..25ba545e7c62510864f7d6c4215d29f3f2eeff30 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_video.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_camera_video_recording.xml b/app/src/main/res/drawable/ic_camera_video_recording.xml new file mode 100644 index 0000000000000000000000000000000000000000..6742eb6208eee0c28942dce071c03bbbe7a9f446 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_video_recording.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000000000000000000000000000000000000..9421788a2cb864fc0d8c3df11067bc4f0fd71041 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_colorize.xml b/app/src/main/res/drawable/ic_colorize.xml new file mode 100644 index 0000000000000000000000000000000000000000..62843bbfa9331638928a47ac9a607a95854455e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_colorize.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_contact_phone.xml b/app/src/main/res/drawable/ic_contact_phone.xml new file mode 100644 index 0000000000000000000000000000000000000000..19d5ef77f62666e5c58354ff2ca5a31216bc2afd --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_phone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_content_copy.xml b/app/src/main/res/drawable/ic_content_copy.xml new file mode 100644 index 0000000000000000000000000000000000000000..66112fee750cdac9134d5ef3a642f50f1ec9c4f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_copy.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000000000000000000000000000000000000..e2425de88f2194f521e327bb7db2341bbbf79e16 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_directions_car.xml b/app/src/main/res/drawable/ic_directions_car.xml new file mode 100644 index 0000000000000000000000000000000000000000..b39f5334369ba2f1f1e5f6b87fad8fdbeb2e33c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_directions_car.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/drawable/ic_done.xml new file mode 100644 index 0000000000000000000000000000000000000000..3b41c7d3bcebf92c9d3fcc6d2c8423a629ac39d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_done.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_drag_handle.xml b/app/src/main/res/drawable/ic_drag_handle.xml new file mode 100644 index 0000000000000000000000000000000000000000..602c9d3bcaa6863984307da9cc89a308342c8999 --- /dev/null +++ b/app/src/main/res/drawable/ic_drag_handle.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 0000000000000000000000000000000000000000..63a9bd89a2121f3451f91fd0f4f269764cb195fd --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_face.xml b/app/src/main/res/drawable/ic_face.xml new file mode 100644 index 0000000000000000000000000000000000000000..237304b838b088d9d9af7f941250b4f1bc86cef5 --- /dev/null +++ b/app/src/main/res/drawable/ic_face.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_focus_mode_auto.xml b/app/src/main/res/drawable/ic_focus_mode_auto.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1e5e09b37dd35193bc208c3b557c827d77a7bd5 --- /dev/null +++ b/app/src/main/res/drawable/ic_focus_mode_auto.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_focus_mode_infinity.xml b/app/src/main/res/drawable/ic_focus_mode_infinity.xml new file mode 100644 index 0000000000000000000000000000000000000000..2aa6de86366383f4386dc850bfc5168810edc38e --- /dev/null +++ b/app/src/main/res/drawable/ic_focus_mode_infinity.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_focus_mode_locked.xml b/app/src/main/res/drawable/ic_focus_mode_locked.xml new file mode 100644 index 0000000000000000000000000000000000000000..2de1215b8740dfc9ecdb0f3513d7ed741798af93 --- /dev/null +++ b/app/src/main/res/drawable/ic_focus_mode_locked.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_focus_mode_macro.xml b/app/src/main/res/drawable/ic_focus_mode_macro.xml new file mode 100644 index 0000000000000000000000000000000000000000..83d8320cbef76bd251066ed1a70cc54dfc29526b --- /dev/null +++ b/app/src/main/res/drawable/ic_focus_mode_macro.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_iso.xml b/app/src/main/res/drawable/ic_iso.xml new file mode 100644 index 0000000000000000000000000000000000000000..91a35703861132ee3ed399b184e42184ecf9ded9 --- /dev/null +++ b/app/src/main/res/drawable/ic_iso.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.png b/app/src/main/res/drawable/ic_launcher_background.png new file mode 100644 index 0000000000000000000000000000000000000000..1f231ab42df764ba6bdf054d9e9b3145d7fe6359 Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_background.png differ diff --git a/app/src/main/res/drawable/ic_lens_background.xml b/app/src/main/res/drawable/ic_lens_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..27e3ec4a47c29cbdb32e9022e15efe6a26cead23 --- /dev/null +++ b/app/src/main/res/drawable/ic_lens_background.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_location_on.xml b/app/src/main/res/drawable/ic_location_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1db4bdbf86a4f80b8d9c76b59965aa0b36b1543 --- /dev/null +++ b/app/src/main/res/drawable/ic_location_on.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_mic.xml b/app/src/main/res/drawable/ic_mic.xml new file mode 100644 index 0000000000000000000000000000000000000000..4f1fe082eeba7e0ebba2eddb09f43fd99bfda901 --- /dev/null +++ b/app/src/main/res/drawable/ic_mic.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000000000000000000000000000000000000..97dc40fe7c142d7b61d81b89096617604470700f --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_network_wifi.xml b/app/src/main/res/drawable/ic_network_wifi.xml new file mode 100644 index 0000000000000000000000000000000000000000..6db18e1a059ed70e5b8294346463e36171b8d077 --- /dev/null +++ b/app/src/main/res/drawable/ic_network_wifi.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_passkey.xml b/app/src/main/res/drawable/ic_passkey.xml new file mode 100644 index 0000000000000000000000000000000000000000..b3c3dbb54104f7b745562ce97c93564f3a5b81df --- /dev/null +++ b/app/src/main/res/drawable/ic_passkey.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 0000000000000000000000000000000000000000..7e719d8e08c0b7c8b14f259bc3c4a2059961cce0 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000000000000000000000000000000000000..ae7ecad9c7d03eb84259f7dc7a712df2d65809d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1997c45034dfd9cfceae72c73c2266e1036e87f --- /dev/null +++ b/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_preference_auto_stabilise.xml b/app/src/main/res/drawable/ic_preference_auto_stabilise.xml new file mode 100644 index 0000000000000000000000000000000000000000..71b5fe40d57e46b3b84f2b123fb6c28cc32e2026 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_auto_stabilise.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_camera_flash_on.xml b/app/src/main/res/drawable/ic_preference_camera_flash_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..58b60758f71ba15d4e7fcf2111fc5509c2f03cbc --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_camera_flash_on.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_preference_camera_preview.xml b/app/src/main/res/drawable/ic_preference_camera_preview.xml new file mode 100644 index 0000000000000000000000000000000000000000..dff67e47f7fc2f4f20e80ba8366562c18ec1d30d --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_camera_preview.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_cog.xml b/app/src/main/res/drawable/ic_preference_cog.xml new file mode 100644 index 0000000000000000000000000000000000000000..394f1a119ab36174dcd2a6c540b26e1bc20d844e --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_cog.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_exposure_unlocked.xml b/app/src/main/res/drawable/ic_preference_exposure_unlocked.xml new file mode 100644 index 0000000000000000000000000000000000000000..847fac1ee7bf70f2601b26d1a288cd60014e0f08 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_exposure_unlocked.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_preference_face.xml b/app/src/main/res/drawable/ic_preference_face.xml new file mode 100644 index 0000000000000000000000000000000000000000..fa07ae306fb9867afb3a286f195546edbdbd03d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_face.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_preference_focus_peaking.xml b/app/src/main/res/drawable/ic_preference_focus_peaking.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac106bbfc736612386e8a05a0b2710952e7b5db9 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_focus_peaking.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_folder_open.xml b/app/src/main/res/drawable/ic_preference_folder_open.xml new file mode 100644 index 0000000000000000000000000000000000000000..7b88044b2926a014c889f7a63d38f9991bc2124e --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_folder_open.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_help.xml b/app/src/main/res/drawable/ic_preference_help.xml new file mode 100644 index 0000000000000000000000000000000000000000..290197c0549131a66e6f81aa28caebf9dd0b3267 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_help.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_info.xml b/app/src/main/res/drawable/ic_preference_info.xml new file mode 100644 index 0000000000000000000000000000000000000000..a9e537021a910ab66bb43fe7f7e9fa320f476702 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_location.xml b/app/src/main/res/drawable/ic_preference_location.xml new file mode 100644 index 0000000000000000000000000000000000000000..a3e877e760086ed524c80fa9c50b1ce4144d2f9e --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_location.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_mic.xml b/app/src/main/res/drawable/ic_preference_mic.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1046645ecf825f03bf37a674881439789ea582b --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_mic.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_preference_more_horizontal.xml b/app/src/main/res/drawable/ic_preference_more_horizontal.xml new file mode 100644 index 0000000000000000000000000000000000000000..b3663c74d011d19bd1782e1f5cbad602f45fc3ae --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_more_horizontal.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_on_screen_gui.xml b/app/src/main/res/drawable/ic_preference_on_screen_gui.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b6796e490503c72ff07eea09ae4e780bd362a16 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_on_screen_gui.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_photo_settings.xml b/app/src/main/res/drawable/ic_preference_photo_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..fae1ec549b4dc787aba04de6ae552d944ad29e85 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_photo_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_photo_size_select.xml b/app/src/main/res/drawable/ic_preference_photo_size_select.xml new file mode 100644 index 0000000000000000000000000000000000000000..5d40ece83c42e40d376841a88544d080be6739f6 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_photo_size_select.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_power.xml b/app/src/main/res/drawable/ic_preference_power.xml new file mode 100644 index 0000000000000000000000000000000000000000..2c4b973fba397c069fe5e96ddf0d3591b832040c --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_power.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_processing.xml b/app/src/main/res/drawable/ic_preference_processing.xml new file mode 100644 index 0000000000000000000000000000000000000000..af29186f025d7d6204c218722d280ad70b39a884 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_processing.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_preference_save.xml b/app/src/main/res/drawable/ic_preference_save.xml new file mode 100644 index 0000000000000000000000000000000000000000..560333a545195b72ffe948eeb6084f35299f02c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_text_format.xml b/app/src/main/res/drawable/ic_preference_text_format.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8ad413e1fef94fb949bb6cea2e865dc9a6ac590 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_text_format.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_text_stamp.xml b/app/src/main/res/drawable/ic_preference_text_stamp.xml new file mode 100644 index 0000000000000000000000000000000000000000..16280799362a01cbff4d1919d7640a3d25ca7a7a --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_text_stamp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_timer.xml b/app/src/main/res/drawable/ic_preference_timer.xml new file mode 100644 index 0000000000000000000000000000000000000000..d71a2d7da35d76784e2507d739a3d074f491916e --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_timer.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_touch_app.xml b/app/src/main/res/drawable/ic_preference_touch_app.xml new file mode 100644 index 0000000000000000000000000000000000000000..30a1ec5aefa00ac778e03fc0b254b75548a4e4ae --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_touch_app.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_preference_video_settings.xml b/app/src/main/res/drawable/ic_preference_video_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..761ecc0fb5b4234baca19f600dc5506ee9c0b6b2 --- /dev/null +++ b/app/src/main/res/drawable/ic_preference_video_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_raw.xml b/app/src/main/res/drawable/ic_raw.xml new file mode 100644 index 0000000000000000000000000000000000000000..7020a3f0735af63d3d908aee25a2fca11c81902a --- /dev/null +++ b/app/src/main/res/drawable/ic_raw.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..5ff6fec08246c8a7049c2aed8034b53778971e3f --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000000000000000000000000000000000000..fcb75da983bf7841a05138c2bdeb88f451c6fb7b --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shopping_cart.xml b/app/src/main/res/drawable/ic_shopping_cart.xml new file mode 100644 index 0000000000000000000000000000000000000000..3cd15b2b032a40f7c3d3fb57721c8ab8501bf619 --- /dev/null +++ b/app/src/main/res/drawable/ic_shopping_cart.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_shutter_speed.xml b/app/src/main/res/drawable/ic_shutter_speed.xml new file mode 100644 index 0000000000000000000000000000000000000000..074469888d29a88837883834ec8e722eb2bb9693 --- /dev/null +++ b/app/src/main/res/drawable/ic_shutter_speed.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sms.xml b/app/src/main/res/drawable/ic_sms.xml new file mode 100644 index 0000000000000000000000000000000000000000..b20d6f027ce45308417dc293e93737ade9f075b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_sms.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_switch_camera.xml b/app/src/main/res/drawable/ic_switch_camera.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c2171f74b02d5b4a1d4aa267d0b6157252fd28c --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_camera.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_switch_multi_camera.xml b/app/src/main/res/drawable/ic_switch_multi_camera.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d17d926693a8f1f45077dc4b5e82bb8105ee2ac --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_multi_camera.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_switch_qrcode.xml b/app/src/main/res/drawable/ic_switch_qrcode.xml new file mode 100644 index 0000000000000000000000000000000000000000..6fa827cf0454ee51ec98e86708e482d60ddb7c12 --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_qrcode.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_switch_video.xml b/app/src/main/res/drawable/ic_switch_video.xml new file mode 100644 index 0000000000000000000000000000000000000000..07b801e2ccdb3d9f1951d0353b693169d313c331 --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_video.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_text_snippet.xml b/app/src/main/res/drawable/ic_text_snippet.xml new file mode 100644 index 0000000000000000000000000000000000000000..137e2893e4022ce6eb4abc461d370dae22faf993 --- /dev/null +++ b/app/src/main/res/drawable/ic_text_snippet.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_text_stamp.xml b/app/src/main/res/drawable/ic_text_stamp.xml new file mode 100644 index 0000000000000000000000000000000000000000..c8116a0863561393d58246f11e9f0505ab88846f --- /dev/null +++ b/app/src/main/res/drawable/ic_text_stamp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_white_balance_unlocked.xml b/app/src/main/res/drawable/ic_white_balance_unlocked.xml new file mode 100644 index 0000000000000000000000000000000000000000..6f2d66a44f0e4da1c72dd4de667eeeefeeeaa823 --- /dev/null +++ b/app/src/main/res/drawable/ic_white_balance_unlocked.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_zoom_in.xml b/app/src/main/res/drawable/ic_zoom_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..3871a062cca0bf28bc89598f7d0d96afd26fc526 --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_in.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_zoom_out.xml b/app/src/main/res/drawable/ic_zoom_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..09772974334cad9a5454f2f403d9c3fe02e16a6b --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_out.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/popup_camera_flash_auto.xml b/app/src/main/res/drawable/popup_camera_flash_auto.xml new file mode 100644 index 0000000000000000000000000000000000000000..b724899fb51e0becbf95d7d13ad70b33866f8e55 --- /dev/null +++ b/app/src/main/res/drawable/popup_camera_flash_auto.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/popup_camera_flash_off.xml b/app/src/main/res/drawable/popup_camera_flash_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..a20462a3aa2758f30a2f187061557e813e2e3f9f --- /dev/null +++ b/app/src/main/res/drawable/popup_camera_flash_off.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/popup_camera_flash_on.xml b/app/src/main/res/drawable/popup_camera_flash_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..c73583d0ba01e9bfeab7aa49e3e0daacc6b5da3c --- /dev/null +++ b/app/src/main/res/drawable/popup_camera_flash_on.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/popup_camera_flash_red_eye.xml b/app/src/main/res/drawable/popup_camera_flash_red_eye.xml new file mode 100644 index 0000000000000000000000000000000000000000..ccbbeb3fca732e3de3b2d27b8c37d99d3de13e5d --- /dev/null +++ b/app/src/main/res/drawable/popup_camera_flash_red_eye.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/popup_camera_flash_torch.xml b/app/src/main/res/drawable/popup_camera_flash_torch.xml new file mode 100644 index 0000000000000000000000000000000000000000..c7be3adbee4658ba814cebe8a06de25f636345d3 --- /dev/null +++ b/app/src/main/res/drawable/popup_camera_flash_torch.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml b/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml new file mode 100644 index 0000000000000000000000000000000000000000..06402726f4c3bdce17ba4b2f33a620392cbae76d --- /dev/null +++ b/app/src/main/res/drawable/qr_bottom_sheet_action_button_divider.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/drawable/scan_area.xml b/app/src/main/res/drawable/scan_area.xml new file mode 100644 index 0000000000000000000000000000000000000000..2486da0806f19991907d6f0422383cef29ae826c --- /dev/null +++ b/app/src/main/res/drawable/scan_area.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/shortcut_gallery.xml b/app/src/main/res/drawable/shortcut_gallery.xml index bfe1492cf349aee70944390e35eb1e36776ce3b2..81c87d31a1e6025c86f94c1196dcc66f86864646 100644 --- a/app/src/main/res/drawable/shortcut_gallery.xml +++ b/app/src/main/res/drawable/shortcut_gallery.xml @@ -1,9 +1,9 @@ - - - + + + - + diff --git a/app/src/main/res/drawable/shortcut_ic_face_white_48dp.xml b/app/src/main/res/drawable/shortcut_ic_face_white_48dp.xml index 61087dd69a55a4c18b83cf98a49ce1e95eeac5aa..b79d90eae0dc2cbd6876a96dd524bcbb360f8f77 100644 --- a/app/src/main/res/drawable/shortcut_ic_face_white_48dp.xml +++ b/app/src/main/res/drawable/shortcut_ic_face_white_48dp.xml @@ -1,9 +1,9 @@ - - - + + + - + diff --git a/app/src/main/res/drawable/shortcut_ic_photo_camera_white_48dp.xml b/app/src/main/res/drawable/shortcut_ic_photo_camera_white_48dp.xml index aa0ca23e15094df874b8ca4cbbb096d82a851791..51f8a1a91ca3b64759e9d1e4df2010aeb072d557 100644 --- a/app/src/main/res/drawable/shortcut_ic_photo_camera_white_48dp.xml +++ b/app/src/main/res/drawable/shortcut_ic_photo_camera_white_48dp.xml @@ -1,9 +1,9 @@ - - - + + + - + diff --git a/app/src/main/res/drawable/shortcut_ic_videocam_white_48dp.xml b/app/src/main/res/drawable/shortcut_ic_videocam_white_48dp.xml index abc44e2aabbd4401f93c4fc51e198e2dcf642118..683456743cf7fb405f11add654dbbc7456a08414 100644 --- a/app/src/main/res/drawable/shortcut_ic_videocam_white_48dp.xml +++ b/app/src/main/res/drawable/shortcut_ic_videocam_white_48dp.xml @@ -1,9 +1,9 @@ - - - + + + diff --git a/app/src/main/res/drawable/shortcut_settings.xml b/app/src/main/res/drawable/shortcut_settings.xml index 244cf6533a1621d39992bd94e03a99e84c971437..9bcdd00f267837d8165f923642dbdb633d4ec92d 100644 --- a/app/src/main/res/drawable/shortcut_settings.xml +++ b/app/src/main/res/drawable/shortcut_settings.xml @@ -1,9 +1,9 @@ - - - + + + - + diff --git a/app/src/main/res/drawable/take_photo_selector.xml b/app/src/main/res/drawable/take_photo_selector.xml index 09f412724f242cd0bc5bca5e79d3db135c857d40..4b01164401c667c9fb86fafbeb6b30c67a7b9a78 100644 --- a/app/src/main/res/drawable/take_photo_selector.xml +++ b/app/src/main/res/drawable/take_photo_selector.xml @@ -1,6 +1,6 @@ - + android:drawable="@drawable/ic_camera_shutter" /> + diff --git a/app/src/main/res/drawable/take_photo_when_video_recording.xml b/app/src/main/res/drawable/take_photo_when_video_recording.xml new file mode 100644 index 0000000000000000000000000000000000000000..8e740588fcc02ba01c9770bd4a1d876fc09f2280 --- /dev/null +++ b/app/src/main/res/drawable/take_photo_when_video_recording.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/take_video_recording.xml b/app/src/main/res/drawable/take_video_recording.xml index de5143a638268dfc8531313c0550ad1f24ca7b6f..91c73c165e8bb0f2d43f5a886d913a21bb76da18 100644 --- a/app/src/main/res/drawable/take_video_recording.xml +++ b/app/src/main/res/drawable/take_video_recording.xml @@ -1,11 +1,4 @@ - - - - - + + + diff --git a/app/src/main/res/drawable/take_video_selector.xml b/app/src/main/res/drawable/take_video_selector.xml index e49fd08e6b9441abf3290ab9bc6974728e942bff..4b01164401c667c9fb86fafbeb6b30c67a7b9a78 100644 --- a/app/src/main/res/drawable/take_video_selector.xml +++ b/app/src/main/res/drawable/take_video_selector.xml @@ -1,6 +1,6 @@ - + android:drawable="@drawable/ic_camera_shutter" /> + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c548ae5c8d0b21612038c8c57a6b091e43543779..835ba282a5283e5ed0e1f8328ef6cb933a2231c6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,142 +11,246 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" /> - - - + + + + + + + + + + + + + + + + + + + + + - + + + +