Android-Programmierung: Video und Kamera
Willemers Informatik-Ecke

Prüfung auf Kamera

Es ist nicht gesagt, dass jedes Android-Gerät eine Kamera besitzt. Das kann das Programm über den Aufruf von hasSystemFeature prüfen.
if (getPackageManager().hasSystemFeature(
        PackageManager.FEATURE_CAMERA)){
    // Kamera vorhanden!
}

Berechtigungen

Die App darf nicht einfach auf die Kamera zugreifen. Dazu muss sie später sogar den Anwender um Erlaubnis bitten. Für das Speichern der Bilder oder des Videos benötigt die App die Berechtigung, auf den externen Speicher zuzugreifen. Datei AndroidManifest.xml hinterlegt werden.
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Die Berechtigungen sind allesamt kritisch, müssen also in der App vom Benutzer bestätigt werden.

Die Schreib- und Leserechte auf den externen Speicher sind erforderlich, weil die fotografierten Bilder nicht im internen Speicher abgelegt werden können.

Anfrage der kritischen Rechte

Der Anwender muss befragt werden, ob die Berechtigungen erteilt werden. Ohne diese Berechtigungen führt eine Verwendung der Ressourcen zur Fehlfunktion, wenn nicht gar zum Absturz der App.
private String[] permissions = {
        Manifest.permission.CAMERA,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE};
Eine private boolesche Methode hilft bei der Prüfung, ob alle Berechtigungen vom Anwender erteilt worden sind.
private boolean pruefeRechteLage(String[] rechte) {
    boolean ok = true;
    for (String recht : rechte) {
        if (ContextCompat.checkSelfPermission(this, recht)
                !=PackageManager.PERMISSION_GRANTED) {
            ok = false;
        }
    }
    return ok;
}
Nun kann in onCreate der Aufruf für die Anwenderbefragung für die Berechtigungen gestartet werden.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if ( ! pruefeRechteLage(permissions)) {
        ActivityCompat.requestPermissions(MainActivity.this,
                permissions, 123);
    }
    // ...
Wenn mindestens eine der Berechtigungen fehlt, erscheint ein nichtmodaler Dialog, das heißt der Programmablauf wartet nicht auf die Antwort des Dialogs. Wurde der Dialog beantwortet, ruft Android die Methode onRequestPermissionsResult. Falls Sie auf die Ergebnisse neugierig sind, müssen Sie diese überschreiben.
@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
Da in diesem Fall vor jeder Verwendung geprüft wird, ob die Berechtigungen vorliegen, kann auf die Implementierung von onRequestPermissionsResult verzichtet werden.

Beispiel-App

Zur Demonstration wird eine App erstellt. Sie soll einen Button haben, der die Aufnahme auslöst. Daraufhin wird die System-Activity zum Schießen eines Fotos aufgerufen, der mitgeteilt wird, wo das Foto liegen soll. Das geschossene Foto wird dann in einem ImageView dargestellt.

Dazu ist ein Layout mit einem Button und einer ImageView erforderlich. Die id soll originellerweise button und imageView heißen.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // Berechtigungen prüfen und hinterfragen
    if ( ! pruefeRechteLage(permissions)) {
        ActivityCompat.requestPermissions(MainActivity.this,
                permissions, 123);
    }
    // Stelle den Button scharf
    Button bt = (Button) findViewById(R.id.button);
    bt.setOnClickListener(this);
}
Dazu muss die Activity das Interface OnClickListener und die abstrakte Methode onClick implementieren.
public class MainActivity extends AppCompatActivity
         implements View.OnClickListener {
Die Methode onClick wird eine Datei für das Foto generieren. Es wird eine Intent generiert, mit dem die System-Activity für das Fotografieren angesprochen wird.
@Override
public void onClick(View view) {
    if (pruefeRechteLage(permissions)) {
        File file = new File(dateiname);
        Uri uri = Uri.fromFile(file);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        startActivityForResult(intent, 123);
    }
}
Der Dateiname muss auf den externen Speicherbereich weisen. Dazu wird ein Attribut angelegt, das einen solchen Namen erzeugt.
public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {

    private String dateiname 
            = Environment
              .getExternalStorageDirectory()+"/bild.png";
Der Aufruf durch startActivityForResult führt dazu, dass die App erfährt, wenn das Foto fertig ist. Um das mitzubekommen, muss die Activity allerdings die Methode onActivityResult überschreiben.
@Override
protected void onActivityResult(int requestCode,
        int resultCode, Intent intent) {
    if (resultCode==RESULT_OK) {
        Bitmap bm = BitmapFactory.decodeFile(dateiname);
        if (bm==null) {
            Toast.makeText(this, "Kein Bild vorhanden!",
                    Toast.LENGTH_LONG).show();
        } else {
            ImageView iv = (ImageView) findViewById(
                    R.id.imageView);
            iv.setImageBitmap(bm);
        }
    }
    super.onActivityResult(requestCode, resultCode, intent);
}

Auslaufmodell startActivityForResult

Obwohl der Aufruf funktioniert, streicht Android-Studio den Aufruf von startActivityForResult einfach durch. Der Hintergrund ist, dass Google diese Methode als deprecated abgekündigt hat. Man kann sie noch benutzen, aber in den nächsten Android-Versionen soll sie irgendwann nicht mehr unterstützt werden.

Die Alternative ist, dass man eine eigene Methode onActivityForResult registriert. Der Aufruf von startActivityForResult wird durch den Aufruf der Methode launch eines eigenen ActivityResultLauncher-Objekts ersetzt. Ich habe dieses rufeKamera genannt.

if (pruefeRechteLage(permissions)) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    rufeKamera.launch(intent);
    // startActivityForResult(intent, 123);
}
Der heftigere Teil ist die Definition des Objekts rufeKamera. Dieses Objekt habe ich als privates Attribut der Activity angelegt.

Wenn Sie genau hinschauen, sehen Sie die wohlvertraute Methode onActivityResult. Dieses Mal liegt sie allerdings als abstrakte Methode von ActivityResultCallback vor.

ActivityResultLauncher<Intent> rufeKamera
    = registerForActivityResult(
       new ActivityResultContracts.StartActivityForResult(),
       new ActivityResultCallback<ActivityResult>() {
         @Override
         public void onActivityResult(ActivityResult result) {
           if (result.getResultCode()==Activity.RESULT_OK) {
              Bitmap bm = BitmapFactory.decodeFile(dateiname);
              ImageView iv = (ImageView) findViewById(
                      R.id.imageView);
              iv.setImageBitmap(bm);
           }
         }
});
Bei Betrachtung dieser Lösung werden Sie mir vielleicht nachsehen, dass ich Ihnen zunächst die abgesagte Methode startActivityForResult vorgestellt habe. Erstens ist ohne deren Vorkenntnis das Verständnis des ActivityResultLauncher nicht ganz einfach. Zweitens findet man im Internet noch reichlich Beispiele, die auf der Basis von startActivityForResult funktionieren. Da sollten Sie wissen, dass diese API abgelöst wird.

Video

Um mit der Kamera ein Video statt eines Fotos aufzunehmen, muss nur die Anforderung geändert werden. Statt MediaStore.ACTION_IMAGE_CAPTURE wird MediaStore.ACTION_VIDEO_CAPTURE verwendet.
// Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
Der Dateiname sollte nun auf mp4 und nicht auf png enden.

Die Anzeige muss da schon weiter verändert werden. Die ImageView kann nur Bilder und keine Videos anschauen. Darum benötigen wir in der Layout-Datei ein VideoView statt einer ImageView, die die id videoView bekommt.

Zu guter Letzt muss natürlich in der Methode onActivityResult der Code für das Darstellen des Fotos in der ImageView durch die Darstellung des Videos in der VideoView geändert werden.

MediaController mediaController = new MediaController(MainActivity.this);
VideoView vv = (VideoView) findViewById(R.id.videoView);
vv.setMediaController(mediaController);
vv.setVideoPath(dateiname);
vv.start();

Links