8

I've been struggling to implement a camera function into my app in a way that doesn't generate the below error:

E/ContextImpl: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@385f002 Visual services, such as WindowManager, WallpaperService or LayoutInflater should be accessed from Activity or other visual Context. Use an Activity or a Context created with Context#createWindowContext(int, Bundle), which are adjusted to the configuration and visual bounds of an area on screen. java.lang.IllegalAccessException: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@385f002

That error is triggered by this line:

final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

I looked at implementing createWindowContext as the error suggests, but some of the target devices are older and not eligible for upgrade to Android 11, thus createWindowContext is not an option.

The first time around, I followed one of the CodeLabs for implementing CameraX. The camera behaved as expected, but triggered the exception. So I found a different example of implementing CameraX, but I get the same IllegalAccessException exception.

Any suggestions?

package com.camtest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class CamTest extends AppCompatActivity {

    private Executor executor = Executors.newSingleThreadExecutor();
    private int REQUEST_CODE_PERMISSIONS = 9001;
    private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};

    PreviewView mPreviewView;
    ImageView captureImage;

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

        mPreviewView = findViewById(R.id.camera);//was previewView
        captureImage = findViewById(R.id.captureImg);

        if(allPermissionsGranted()){
            startCamera(); //start camera if permission has been granted by user
        } else{
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }
    }

    private void startCamera() {

        final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); //This line triggers `E/ContextImpl: Tried to access visual service WindowManager from a non-visual Context`

        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {

                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    bindPreview(cameraProvider);

                } catch (ExecutionException | InterruptedException e) {
                    // No errors need to be handled for this Future.
                    // This should never be reached.
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

    void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
        Preview preview = new Preview.Builder().build();

        ImageCapture imageCapture = new ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build();

        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        Camera camera = cameraProvider.bindToLifecycle(
                ((LifecycleOwner) this),
                cameraSelector,
                preview,
                imageCapture);

        preview.setSurfaceProvider(
                mPreviewView.getSurfaceProvider());


        captureImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
                File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ ".jpg");

                ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback () {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(CamTest.this, "Image Saved successfully", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    @Override
                    public void onError(@NonNull ImageCaptureException error) {
                        error.printStackTrace();
                    }
                });
            }
        });
    }

    public String getBatchDirectoryName() {

        String app_folder_path = "";
        app_folder_path = Environment.getExternalStorageDirectory().toString() + "/images";
        File dir = new File(app_folder_path);
        if (!dir.exists() && !dir.mkdirs()) {

        }

        return app_folder_path;
    }

    private boolean allPermissionsGranted(){

        for(String permission : REQUIRED_PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if(requestCode == REQUEST_CODE_PERMISSIONS){
            if(allPermissionsGranted()){
                startCamera();
            } else{
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
                this.finish();
            }
        }
    }
}

And this activity is started by the below code within onCreate of MainActivity:

Button button_test = findViewById(R.id.button_test);
button_test.setOnClickListener(view -> {
     Intent intent = new Intent(MainActivity.this, CamTest.class);
     startActivityForResult(intent,0);
});

EDIT: full stack trace-

E/ContextImpl: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@dd90e6b Visual services, such as WindowManager, WallpaperService or LayoutInflater should be accessed from Activity or other visual Context. Use an Activity or a Context created with Context#createWindowContext(int, Bundle), which are adjusted to the configuration and visual bounds of an area on screen.
    java.lang.IllegalAccessException: Tried to access visual service WindowManager from a non-visual Context:com.camtest.App@dd90e6b
        at android.app.ContextImpl.getSystemService(ContextImpl.java:1914)
        at android.content.ContextWrapper.getSystemService(ContextWrapper.java:803)
        at androidx.camera.camera2.internal.Camera2UseCaseConfigFactory.<init>(Camera2UseCaseConfigFactory.java:50)
        at androidx.camera.camera2.Camera2Config.lambda$defaultConfig$1(Camera2Config.java:60)
        at androidx.camera.camera2.-$$Lambda$Camera2Config$g_hY10kZhqC56um0PalOLTzuFlU.newInstance(Unknown Source:0)
        at androidx.camera.core.CameraX.lambda$initAndRetryRecursively$9$CameraX(CameraX.java:575)
        at androidx.camera.core.-$$Lambda$CameraX$u-Xx2b6YXY5GXNXRh-mDiDnHdpQ.run(Unknown Source:10)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

EDIT #2: In order to reproduce this error, the StrictMode for VmPolicy must be enabled. The below code is added to MainActivity.onCreate:

if( BuildConfig.BUILD_TYPE.contentEquals( "debug" ) ){
    /*StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyLog()
            .build());*/
    StrictMode.setVmPolicy( new StrictMode.VmPolicy.Builder()
            .detectAll()//.detectNonSdkApiUsage()
            .penaltyLog()
            .build());
}

EDIT #3: Updating from CameraX version 1.0.0-beta12 to 1.0.0-rc1 (current version as of today) had no effect

CragMonkey
  • 808
  • 1
  • 11
  • 22
  • 2
    The error suggests that you are using `Application` context, as that is my guess as to what `com.camtest.App` is. Could you post the entire stack trace? – CommonsWare Jan 02 '21 at 18:56
  • Added the stack trace above. – CragMonkey Jan 02 '21 at 23:22
  • 1
    That is a very strange stack trace. I'm not certain where the `Context` is coming from. If you're not on the latest CameraX, you might try upgrading. Otherwise, if you are in position to create a sample project that reproduces the problem, you might file an issue on the issue tracker. Off the cuff, it doesn't feel like you're doing anything wrong. – CommonsWare Jan 02 '21 at 23:31
  • 1
    Your "this" as provided context is not right. That's why it gives you this error. Did you try with getApplicationContext() or getBaseContext()? Or try creating global variable in CamText.class named Context context; and in OnCreate assign context = getApplicationContext() or getBaseContext(), what ever works. – SlothCoding Jan 02 '21 at 23:37
  • Yes, I've tried getApplicationContext() and getBaseContext(), both as local to the method and as a global instantiated in onCreate. – CragMonkey Jan 02 '21 at 23:53
  • I was using 1.0.0-beta12, but updating to 1.0.0-rc1 (and the other current package versions as published on https://developer.android.com/jetpack/androidx/releases/camera ) seems to have had no effect – CragMonkey Jan 03 '21 at 00:00
  • 1
    @CragMonkey my last bet is to move your startCamera() and everything else to MainActivity and start it from there. Do it at least for the test and see what will happen. That's only I can suggest, for now, maybe I'll think of something tomorrow. – SlothCoding Jan 03 '21 at 00:49
  • Same result when running within MainActivity, so long as StrictMode is enabled. – CragMonkey Jan 03 '21 at 18:02
  • @CragMonkey have you considered creating an issue on the official issue tracker? I have this problem too using the latest CameraX dependencies. – G00fY Jan 14 '21 at 22:06
  • still happens in 'androidx.camera:camera-core:1.1.0-alpha09', because of `surfaceManagerProvider.newInstance(mAppContext,` – Pnemonic Sep 30 '21 at 16:26

1 Answers1

2

Pass the Context from the Activity rather than the Application.

The stack trace indicates you're passing an instance of com.camtest.App. Since you're just passing it from your Activity.this, I imagine the library you're using is calling Context.getApplicationContext() incorrectly. You'll need to chase this up with the library maintainers.

mhansen
  • 1,102
  • 1
  • 12
  • 18