Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08a0c1e9 authored by Romain Hunault's avatar Romain Hunault 💻 Committed by Mohammed Althaf T
Browse files

camera: Barcode scanner

parent f1c8ba6b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'

android {
    compileSdk 35
@@ -70,6 +71,9 @@ dependencies {

    implementation 'androidx.exifinterface:exifinterface:1.4.1'

    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
+71 −7
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import net.sourceforge.opencamera.cameracontroller.CameraControllerManager;
import net.sourceforge.opencamera.cameracontroller.CameraControllerManager2;
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.DrawPreview;
import net.sourceforge.opencamera.ui.FolderChooserDialog;
@@ -47,6 +48,8 @@ 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.display.DisplayManager;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
@@ -117,6 +120,12 @@ import android.widget.TextView;

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 {
@@ -246,6 +255,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 {
@@ -282,6 +296,16 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen
        //EdgeToEdge.enable(this, SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT), SystemBarStyle.dark(Color.TRANSPARENT)); // test edge-to-edge on pre-Android 15
        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 )
@@ -2864,6 +2888,44 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen
            Log.d(TAG, "clickedSwitchMultiCamera: total time: " + (System.currentTimeMillis() - debug_time));
    }

    /*  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;
    }

    /**
     * Toggles Photo/Video mode
     */
@@ -6835,7 +6897,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 = "";
@@ -6869,7 +6931,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;
+130 −0
Original line number Diff line number Diff line
/*
 * 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()
+63 −0
Original line number Diff line number Diff line
/*
 * 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()
+17 −0
Original line number Diff line number Diff line
/*
 * 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
}
Loading