0

I have foreground service which takes picture after some delay time without preview. It captures images(work properly) when: 1.application is currently on my UI(foreground). 2.and what curious it captures images when I use another applications, but only when phone IS PLUGGED IN INTO THE CHARGER. I want to make my app able to capture images even when its not plugged in into the charger(its soo wired that it works over another apps, but only while being charging).. It seems like issue with battery consumption. But i couldn't figure it out.. Application isn't killed by OS, because when service is ON, I see notification, and when i plug charging cable to the device, its starts taking pictures even when while being over another apps. Its seems like service freeze itself..

Generally my intention is to capture image with user awareness, but only when device is not LOCKED. After loong research I couldn't find solution on the internet. I hope so much, that there is some person who is able to help me..

I need to have solution for working my app (capturing images) constantly over another apps but not while being charged...
I use Android 9.


There will be a bit of working code, but not as I want.. Here is my simplified Code:

MainActivity

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_WRITE_STORAGE_CAMERA_REQUEST_CODE = 1;
    private static final int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 5469;
    Intent serviceIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startBtn = findViewById(R.id.buttonStart);
        Button stopBtn = findViewById(R.id.buttonStop);

        FileManager fileManager = FileManager.getInstance();
        fileManager.initFileManager(this.getResources());

        setUpPrivileges();
        startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                serviceIntent = new Intent(MainActivity.this, CameraService.class);
                serviceIntent.setPackage("com.example.mytestinz");
                serviceIntent.setAction(CameraService.ACTION_START);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(serviceIntent);
                } else {
                    startService(serviceIntent);
                }
            }
        });

        stopBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(new Intent(MainActivity.this, CameraService.class));
            }
        });
    }
    public void setUpPrivileges() {
        ActivityCompat.requestPermissions(MainActivity.this, new String[] {
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.CAMERA,
                        Manifest.permission.SYSTEM_ALERT_WINDOW
                },
                REQUEST_WRITE_STORAGE_CAMERA_REQUEST_CODE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(MainActivity.this)) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        Uri.parse("package:" + MainActivity.this.getPackageName()));
                MainActivity.this.startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
            }
        }
    }
}

CameraServie

public class CameraService extends Service {
    Context context = this;

    private static final String TAG = "CameraService";

    static final String ACTION_START = "com.example.mytestinz.START";
    static final String ACTION_START_WITH_PREVIEW = "om.example.mytestinz.START_WITH_PREVIEW";

    private static final Integer ONGOING_NOTIFICATION_ID = 6660;
    private static final String CHANNEL_ID = "cam_service_channel_id";
    private static final String CHANNEL_NAME = "cam_service_channel_name";

    private CameraManager cameraManager;
    private String cameraId;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private int cameraFacing;
    private CameraDevice cameraDevice;

    private ImageReader imageReader;

    int currentPictureID = 0;
    int pictureTimer = 0;
    ScheduledExecutorService executor =
            Executors.newSingleThreadScheduledExecutor();
    Handler handler;
    int pictureDelay = 5;

    private boolean cameraClosed;

    PowerManager.WakeLock wakeLock;

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        Log.i(TAG, "onStartCommand(): action = " + action);

            Log.d(TAG, "onStartCommand: *** action = " + action);
            switch (action) {
                case ACTION_START:
                    start();
                    break;
                case ACTION_START_WITH_PREVIEW:
                    break;
            }
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        cameraFacing = CameraCharacteristics.LENS_FACING_FRONT;
        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        handler = new Handler();
        openBackgroundThread();

        startWithForeground();
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        stopService();
    }

    public void stopService(){
        stopSelf();
        closeBackgroundThread();

        if(executor != null && handler != null) {
            handler.removeCallbacksAndMessages(null);
            executor.shutdownNow();
        }

        // (START)Edited to work without charging
        if (wakeLock.isHeld()) {
            Log.d(TAG, "stopMyService: Release LOCK");
            wakeLock.release();
        }
        // (END)Edited to work without charging 

        Log.d(TAG, "stopService: Stopping service");
    }

    public void start(){
        Log.d(TAG, "start: Run Service with NO PREVIEW.");

        // (START)Edited to work without charging
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "MyApp::MyWakelockTag");
        wakeLock.acquire();
        // (END)Edited to work without charging 

        setUpCamera();
        runApp();
    }

    private void startWithForeground(){
        Intent notificationIntent = new Intent(CameraService.this, context.getClass());
        PendingIntent pendingIntent = PendingIntent.getActivity(context,0, notificationIntent, 0);

        //ONLY FOR API 26 and HIGHER!
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE);
            channel.setLightColor(Color.BLUE);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        }

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle(getText(R.string.app_name))
                .setContentText(getText(R.string.app_name))
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent)
                .setTicker(getText(R.string.app_name))
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .build();

        startForeground(ONGOING_NOTIFICATION_ID, notification);
    }

    private void setUpCamera() {
        try {
            for (String cameraId : cameraManager.getCameraIdList()) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == cameraFacing) {
                    this.cameraId = cameraId;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        try {
            if (ActivityCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                cameraManager.openCamera(cameraId, stateCallback, null);
            }
        } catch (CameraAccessException e) {
            Log.i(TAG,":MissingPermission - CAMERA");
            e.printStackTrace();
        }
    }

    private final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                       @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
            Log.i(TAG, "done taking picture from camera " + cameraDevice.getId());
            closeCamera();
        }
    };


    public void takePhoto() throws CameraAccessException {
        if (null == cameraDevice) {
            Log.e(TAG, "cameraDevice is null");
            return;
        }
        final CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraDevice.getId());
        Size[] jpegSizes = null;
        StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (streamConfigurationMap != null) {
            jpegSizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
        }
        final boolean jpegSizesNotEmpty = jpegSizes != null && 0 < jpegSizes.length;
        int width = jpegSizesNotEmpty ? jpegSizes[0].getWidth() : 640;
        int height = jpegSizesNotEmpty ? jpegSizes[0].getHeight() : 480;
        final ImageReader reader = ImageReader.newInstance(640, 480, ImageFormat.JPEG, 2);
        final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(reader.getSurface());
        captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(2)/*getOrientation()*/);
        reader.setOnImageAvailableListener(onImageAvailableListener, null);
        cameraDevice.createCaptureSession(Arrays.asList(reader.getSurface()), new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                try {
                    session.capture(captureBuilder.build(), captureListener, null);
                } catch (final CameraAccessException e) {
                    Log.e(TAG, " exception occurred while accessing " + cameraDevice.getId(), e);
                }
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                Log.d(TAG, "onConfigureFailed: Faild to create cameraCaptureSession");
            }
        },null);
    }

    ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.d(TAG, "onImageAvailableListener: Image Available to save.");

            final Image image = reader.acquireNextImage();
            new ImageSaver(image);
        }
    };

    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraClosed = false;
            cameraDevice = camera;
            Log.d(TAG, "onOpened: Camera " + camera.getId() + " is opened.");

            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    try {
                        takePhoto();
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.d(TAG, "onDisconnected: Camera " + camera.getId() + " is Disconnected");
            if (cameraDevice != null && !cameraClosed) {
                cameraClosed = true;
                cameraDevice.close();
            }
        }
        @Override
        public void onError(CameraDevice camera, int error) {
            Log.e(TAG, "onError: Camera "+ camera.getId() +" has error, code: " + error);
            if (cameraDevice != null && !cameraClosed)
                cameraDevice.close();
            CameraService.this.cameraDevice = null;
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            cameraClosed = true;
        }
    };

    private void openBackgroundThread() {
        backgroundThread = new HandlerThread("camera_background_thread");
        backgroundThread.start();
        backgroundHandler = new Handler(backgroundThread.getLooper());
    }

    private void closeBackgroundThread() {
        if (backgroundHandler != null) {
            backgroundThread.quitSafely();
            backgroundThread = null;
            backgroundHandler = null;
        }
    }

    public void runApp(){
            currentPictureID = 0;
            executor = Executors.newSingleThreadScheduledExecutor();
            executor.scheduleAtFixedRate(periodicTask, 0, pictureDelay + 3, TimeUnit.SECONDS);
    }

    public void decrementTimer(){
        boolean takePicture = (pictureTimer == 1);
        pictureTimer--;
        if (takePicture) {
            savePictureNow();
        } else if (pictureTimer > 0) {

            System.out.println("Count Down: "+pictureTimer);
            handler.postDelayed(makeDecrementTimerFunction(), 1000);
        }
    }

    Runnable periodicTask = new Runnable() {
        public void run() {
            savePicture();
        }
    };

    public void savePicture(){
        if (this.pictureDelay == 0) {
            savePictureNow();
        } else {
            savePictureAfterDelay(this.pictureDelay);
        }
    }

    void savePictureAfterDelay(int delay) {

        pictureTimer = delay;
        handler.postDelayed(makeDecrementTimerFunction(), 1000);
    }

    Runnable makeDecrementTimerFunction() {
        return new Runnable() {
            public void run() {
                decrementTimer();
            }
        };
    }

    public void savePictureNow(){
        openCamera();
    }

    private void closeCamera() {
        Log.d(TAG, "closing camera " + cameraDevice.getId());
        if (null != cameraDevice && !cameraClosed) {
            Log.d(TAG, "closing camera inside " + cameraDevice.getId());
            cameraDevice.close();
            cameraDevice = null;
        }
        if (null != imageReader) {
            imageReader.close();
            imageReader = null;
        }
    }
}

FileManager

public class FileManager {
    private static final String TAG = "FileManager";
    private static FileManager fileManager = null;
    private File galleryFolder;
    private File gallerySourceFolder;
    private Resources resources;
    private Boolean init;

    private File currentTakenPhotoFile;

    private FileManager() {
        init = false;
    }

    public static FileManager getInstance(){
        if(fileManager == null) {
            synchronized (FileManager.class) {
                fileManager = new FileManager();
            }
        }
        return fileManager;
    }

    public void initFileManager(Resources resources){
        if (!init){
            this.resources = resources;
            init = true;
            createImageGallery();
        }
    }

    public File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        String imageFileName = "image_" + timeStamp;
        String suffix = ".jpg";
        Log.i(TAG, "createdImageFile:" + galleryFolder + "/" + imageFileName + suffix);
        return File.createTempFile(imageFileName, suffix, galleryFolder);
    }

    public void createImageGallery() {
        File storageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        if(resources == null){
            Log.i(TAG,"Resources is null.");
            return;
        }

        galleryFolder = new File(storageDirectory, resources.getString(R.string.app_name)+"/targetFolder");
        if (!galleryFolder.exists()) {
            boolean wasCreated = galleryFolder.mkdirs();
            if (!wasCreated) {
                Log.i(TAG, "Failed to create target gallery directory");
            }
        }

        gallerySourceFolder = new File(storageDirectory, resources.getString(R.string.app_name)+"/sourceFolder");
        if (!gallerySourceFolder.exists()) {
            boolean wasCreated = gallerySourceFolder.mkdirs();
            if (!wasCreated) {
                Log.i(TAG, "Failed to create source gallery directory");
            }
        }
    }

    public File getCurrentTakenPhotoFile() {
        return currentTakenPhotoFile;
    }

    public void setCurrentTakenPhotoFile(File currentTakenPhotoFile) {
        this.currentTakenPhotoFile = currentTakenPhotoFile;
    }
}

ImageSaver


public class ImageSaver{
    public static final String TAG = "ImageSaver";
    private Image image;

    public ImageSaver(Image image) {
        this.image = image;
        saveImage();
    }

    private void saveImage() {
        Long tsLong = System.currentTimeMillis()/1000;
        String timeStampStart = tsLong.toString();

        Log.d(TAG, "saveImage: Start at: "+ timeStampStart);
        FileOutputStream outputPhoto = null;
        if (image == null)
            return;
        try {
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] ImageBytes = new byte[buffer.capacity()];
            buffer.get(ImageBytes);

            final Bitmap bmp = BitmapFactory.decodeByteArray(ImageBytes, 0, ImageBytes.length);

            FileManager fileManager = FileManager.getInstance();
            fileManager.setCurrentTakenPhotoFile(fileManager.createImageFile());

            outputPhoto = new FileOutputStream(fileManager.getCurrentTakenPhotoFile());

            bmp.compress(Bitmap.CompressFormat.PNG, 100, outputPhoto);

            tsLong = System.currentTimeMillis()/1000;
            String timeStampEnd = tsLong.toString();
            Log.d(TAG, "saveImage: End at: "+ timeStampEnd);
            Log.d(TAG, "saveImage: Has taken: "+ (Long.parseLong(timeStampEnd )- Long.parseLong(timeStampStart)));

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (image != null) {
                image.close();
            }
            if (outputPhoto != null) {
                try {
                    outputPhoto.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Manifest


 <uses-permission
        android:name="android.permission.CAMERA"
        android:required="true" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
    <!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
    <!-- Permission require to retrieve View, over other apps. -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <!-- Since android Pie (API level 28), need to add the FOREGROUND_SERVICE permission.-->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- For Wake Lock -->
    <uses-permission android:name="android.permission.WAKE_LOCK" tools:node="replace" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".CameraService" >
            <intent-filter>
                <action android:name="com.example.mytestinz.START"/>
            </intent-filter>
        </service>

    </application>
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Raba_Ababa
  • 53
  • 6
  • "only when phone IS PLUGGED IN"... Sounds like your device is going to sleep. You can fix that with a [`WakeLock`](https://developer.android.com/training/scheduling/wakelock). – greeble31 Dec 03 '19 at 00:13
  • Unfortunately i did some research, but nothing help. Still I see, that service work when is actually on foreground(UI), or while charging... I'm so hopeless.. I struggle this issue like 4-5 days and can't find solution... – Raba_Ababa Dec 03 '19 at 20:41
  • No need to give up hope. Let's see your `WakeLock` code. – greeble31 Dec 03 '19 at 20:58
  • Accordingly to the documentation: "you should never need to use a wake lock in an activity", so i've made changes in my CameraService. I've changed stopService() method and start(). To see changes look at edited code above. It;s not complicated code, but it doesnt help. Also i wont use WakefulBroadcastRevcier, because its deprecated in api lvl 26. I try to do my best with this issue, but its hard to believe after such long research.. – Raba_Ababa Dec 03 '19 at 22:53
  • Code looks pretty good. What device/API level is this on? If it turns out this doesn't work, you've got at least 2 more options: 1.) Use `AlarmManager` to wake the device every few seconds, and 2.) Keep the camera open all the time (but just snap a picture once every 5 seconds). – greeble31 Dec 03 '19 at 23:31
  • As i noticed andorid 9, api lvl 28. I read about restrictions introduced in android O, but its related mainly with background services.. So it shouldn't have impact on my implementation. I will try another approach related with WorkManager and also with JobIntentService. Maybe some implementation with this classes would help. I am still looking for solution. btw. 2 option seems to be not optimal, and it doesnt work. Thanks for interest. – Raba_Ababa Dec 05 '19 at 14:45
  • Possibly [related](https://stackoverflow.com/questions/32627342/how-to-whitelist-app-in-doze-mode-android-6-0) – greeble31 Dec 05 '19 at 14:51
  • So, I am working on Huawei Mate 20 pro, it's quite new device, and my solution was related with specific restrictions with new device from this manufacture. My solution is described here: https://dontkillmyapp.com/huawei. I felt relieve when i found this. Thanks for interest btw. – Raba_Ababa Dec 13 '19 at 10:33
  • For Clarity. Solution in previous comment( https://dontkillmyapp.com/huawei ) only work by: Phone settings > Battery > App launch > "Set manual managing on Application". Solution for Devs in this link didn't work. – Raba_Ababa Jan 25 '20 at 14:34

0 Answers0