Bild aus CameraX Anwendung per FileProvider speichern

B

Braesident

Ambitioniertes Mitglied
1
Hallo,

Ich habe 2 Apps die den selben Speichplatz nutzten. Unter storage/emulated/0 wurde ein Verzeichnis erstellt. In diesem für jeden Arbeitsvorgang ein Unterverzeichnis. App 1 hat Bilder in dieses Unterverzeichnis abgelegt und App 2 json Dateien. Mit den neueren Richtlinien von Google funktioniert das ja nicht mehr.

Jetzt würde ich gern App 2 den Speichervorgang beider Daten übernehmen lassen. Das Json File ist kein Problem... ab in den eigenen Speicher. Doch die Bilder bekomme ich dort einfach nicht hin. Ich benutze in App 1 CameraX um die Bilder aufzunehmen. Sowie ein File Provider in App 2. Doch beim speichern des Bildes bekomme ich immer die Meldung Failed to write temp file... was für mich auf fehlende Permission deutet. An der Stelle komm ich einfach nicht weiter. Vielleicht hat jemand von euch eine Idee warum das nicht funktioniert bzw. wie es funktionieren könnte. Habe ich das mit dem File Provider falsch verstanden ?

Beide Apps haben als minsdk 26 und target sowie compilesdkversion 33

getestet mit Emulator Android 11 und auf Gerät mit Android 10
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="my.package.b">

    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:requestLegacyExternalStorage="true"
        android:theme="@style/AppTheme">
       
        <activity android:name=".App2"
            android:exported="true">

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
    </application>
</manifest>
XML:
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
    <external-files-path
        name="external_files"
        path="." />
    <files-path
        name="files"
        path="." />
</paths>
Java:
public class App2 extends AppCompatActivity {

    ActivityResultLauncher<Intent> cameraLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {}
            }
    );

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(view -> handleImagesDialog());
    }

    private void handleImagesDialog() {
        File imgDir = new File(getFilesDir().getPath().concat("/JOB_ID/pictures"));
        imgDir.mkdirs();
        File[] imgs = imgDir.listFiles();
        ImageButton btn = dialog.getWindow().findViewById(R.id.trigger);

        btn.setOnClickListener(view -> {
            Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", imgDir);
            Intent cameraIntent = new Intent("my.package.a.intent.CAMERA");
            cameraIntent.putExtra("path", imgDir.getPath());
            cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            cameraLauncher.launch(cameraIntent);
        });
    }
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

    <application
        android:name=".App1Application"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:requestLegacyExternalStorage="true"
        tools:ignore="LockedOrientationActivity">
        <activity
            android:name=".ActivityCamera"
            android:exported="true"
            android:label="@string/title_activity_camera">

            <intent-filter>
                <action android:name="my.package.a.intent.CAMERA" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>
Java:
public class ActivityCamera extends AppCompatActivity {

    private ImageButton trigger;

    private PreviewView previewView;

    private String imageDefaultPath = "";
    private String imageDestinationPath = "";
    private Uri imageUri;

    int facing = CameraSelector.LENS_FACING_BACK;

    private SimpleDateFormat dateFormatTimestamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());

    private final ActivityResultLauncher<String> cameraPermissionResultLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
        @Override
        public void onActivityResult(Boolean result) {
            startCamera(facing);
        }
    });


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        imageDefaultPath = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath();
//            imageDefaultPath = Environment.getExternalStorageDirectory().getPath().concat("/MyApp");

        if (getIntent().hasExtra("path")) {
            Bundle bundle = this.getIntent().getExtras();
            imageDestinationPath = bundle.getString("path", getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath());
        }

        if (getIntent().hasExtra(MediaStore.EXTRA_OUTPUT)) {
            Bundle bundle = this.getIntent().getExtras();
            imageUri = (Uri) bundle.get(MediaStore.EXTRA_OUTPUT);
        }

        previewView = findViewById(R.id.preview);
        trigger = findViewById(R.id.trigger);

        if (ContextCompat.checkSelfPermission(ActivityCamera.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            cameraPermissionResultLauncher.launch(Manifest.permission.CAMERA);
        } else {
            startCamera(facing);
        }
    }

    private void startCamera(int facing) {
        int aspectRatio = aspectRatio(previewView.getWidth(), previewView.getHeight());
        ListenableFuture listenableFuture = ProcessCameraProvider.getInstance(this);

        listenableFuture.addListener(() -> {
            try {
                ProcessCameraProvider processCameraProvider = (ProcessCameraProvider) listenableFuture.get();
                Preview preview = new Preview.Builder().setTargetAspectRatio(aspectRatio).build();
                ImageCapture imageCapture = new ImageCapture.Builder()
                        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                        .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();
                CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(facing).build();
                processCameraProvider.unbindAll();
                Camera camera = processCameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture);

                trigger.setOnClickListener(view -> {
                    if (ContextCompat.checkSelfPermission(ActivityCamera.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        cameraPermissionResultLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
                    } else {
                        triggerPicture(imageCapture);
                    }
                });

                preview.setSurfaceProvider(previewView.getSurfaceProvider());
            } catch (ExecutionException | InterruptedException e) {
                throw new RuntimeException(e);
            }

        }, ContextCompat.getMainExecutor(this));
    }

    private void triggerPicture(ImageCapture imageCapture) {
        String filename = dateFormatTimestamp.format(new Date()) + ".jpg";
        Uri extendedUri = Uri.withAppendedPath(imageUri, filename);
        final File file = new File("" + extendedUri);
        Log.d("AC", "Take PICTURE TO: " + file.getPath());
        // content:/my.package.b.provider/external_files/Documents/MyApp/202309062236/pictures/20230907224154.jpg
        ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(file).build();
        imageCapture.takePicture(options, Executors.newCachedThreadPool(), new ImageCapture.OnImageSavedCallback() {
            @Override
            public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                Log.d("AC", getString(R.string.image_saved) + ": " + file.getPath());
                startCamera(facing);
            }

            @Override
            public void onError(@NonNull ImageCaptureException exception) {
                Log.d("AC", getString(R.string.failed) + ": " + exception.getMessage());
                exception.printStackTrace();
                startCamera(facing);
            }
        });
    }

    private int aspectRatio(int width, int height) {
        double previewRatio = (double) Math.max(width, height) / Math.min(width, height);
        if (Math.abs(previewRatio - 4.0 / 3.0) <= Math.abs(previewRatio - 16.0 / 9.0)) {
            return AspectRatio.RATIO_4_3;
        }
        return AspectRatio.RATIO_16_9;
    }
}
 
Zuletzt bearbeitet:
Bearbeitet von: Braesident - Grund: Code angepasst
Hallo reden wir hier wirklich von zwei getrennten eigenständigen Apps?

Also wenn ein file provider etwas freigeben soll. Dann muss der in der app sein wo die Daten erstellt werden. Also in der app die due bilder macht.

Schein mir bei dir nicht so zu sein.

Auch merkwürdig die zweite app hat keine Activity und intent filter im manifest.
 
Der FileProvider ist auch nicht dazu gedacht um in der Kamera App auf den externen Speicher zuzugreifen.
Der Provider kann einen App eignen Speicherbereich für andere Apps zum lesen zur Verfügung stellen.

Wenn du in der Kamera App auf den externen Speicher zureifen willst dann schaue dir Mal Storage Access Framework (SAF) an .
oder
Media Store

Damit kannst du dann auch in der anderen App zureifen.

Storage updates in Android 11 | Android Developers
 
Zuletzt bearbeitet:
Hallo jogimuc,

erstmal danke für die Infos. Ich hab versucht die Snippets der Lesbarkeit wegen auf das wichtigste zu reduzieren. Im Manifest der App2 ist sonst ein ganz einfacher Activity Block drin. Es sind auch 2 einzelne Apps. Wobei App 1 halt bestimmte Funktion bereitstellt wie Fotos machen. App 2 kann eigenständig laufen aber halt per Intent auf App 1 Activities zugreifen.

Es soll auch der eigene Speicher sein. Ich hab die Zeile wie folgt angepasst:
Java:
File imgDir = new File(getFilesDir().getPath().concat("/JOB_ID/pictures"));
Der Log beim Speichern wirft jetzt folgenden Pfad:
content:/my.package.b.provider/files/202309081558/pictures/20230908160105.jpg
Und der Fehler ist geblieben.

Ich hab das so verstanden das der FileProvider in der App definiert werden muss, die zwar das Bild nicht selbst macht aber im eigenen Speicher abgelegt haben möchte. Über diesen erstelle ich dann die Uri aus dem Path zu meinem Speicher. Und diese sende ich per Intent an die (Kamera) App 1

Das SAF hatte ich mir auch schon angesehen. Das war leider nicht zufriedenstellen. Der Benutzer müsste sonst andauernd ein Speicherort auswählen.
Storage Updates hatte mal kurzeitig funktioniert aber nur in App 2 die ein Json abgelegt hat. Mittlerweile bringt auch das nichts mehr.
Beim MediaStore bin ich noch nicht ganz durchgestiegen. Aber ich denke hier wird jedes Dateiformat woanders gespeichert. Ich brauch aber halt das json File und die Bilder in einem Vorgangs Verzeichnis.
 
Ich hab das so verstanden das der FileProvider in der App definiert werden muss, die zwar das Bild nicht selbst macht aber im eigenen Speicher abgelegt haben möchte.

andersrum

Serving Files with FileProvider
Beiträge automatisch zusammengeführt:

Die App die die Bilder macht und in ihrem App eignen Speicherbereich ablegt.
Kann mit Hilfe den Provider einer anderen App die Möglichkeit geben auf ihren geschürzten Bereich zuzugreifen.

Die fremde App hat dann mittels eines Intent die Möglichkeit zuzugreifen.
Beiträge automatisch zusammengeführt:

Braesident schrieb:
Das SAF hatte ich mir auch schon angesehen. Das war leider nicht zufriedenstellen. Der Benutzer müsste sonst andauernd ein Speicherort auswählen.
wieso das du kannst dir doch den virtuellen path speichern. zb in einer Shpref


Share Files Using FileProvider on Android
natürlich hier nicht v4 benutzen sondern androidx
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: Braesident
Ich blick es nicht mehr... In jedem Beispiel wird doch der File Provider in der eigenen App erstellt. Mit diesem dann die Uri zum Verzeichnis oder Wunschdatei erstellt und per Intent an die Kamera App übergeben (Es ist ja leider immer die HausApp des Gerätes). An der Stelle bin ich davon ausgegangen das die Kamera App nicht nur das Bild macht sondern auch speichert. Zumal sie ja Berechtigungen per Intent Flags bekommt.

Ich danke dir für Heute. Dein Link muss ich mir später anschauen... muss erstmal auf die Autobahn
Schön Abend noch.
 
Es ist leider so das die meisten beispiele im netz so sind das sie einer anderen App die rechte geben auf ihten app speicher bereich zuzugreifen.

Zb du hast ein Foto gemacht in dem geschützten Bereich abgelegt.
Und nun willst du einen Intent machen zb zu einer Email App und das Bild mitgeben.
Das würde nicht gehen ohne den Fileprovider denn die fremde App könnte nicht auf das Bild zugreifen.

Fast alle Beispiele beschreiben das Prinzip. So nun musst du schauen wie du in der fremden App also zb der Email App auf den Provider zugreifst.
 
Zuletzt bearbeitet:

Ähnliche Themen

R
  • Robby1950
2
Antworten
23
Aufrufe
1.025
Robby1950
R
Jansenwilson
Antworten
1
Aufrufe
763
swa00
swa00
A
Antworten
10
Aufrufe
1.929
swa00
swa00
Zurück
Oben Unten