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>