How to continuously mock a GPS location on Android? - java

How do I continuously mock a location on Android in 2023? The following code does not work. Only thing that changed is that the blue dot in Google Maps becomes grey. I added all the needed permissions and selected my app as mock location in Developer Settings. Other answers on SO are now outdated due to Google introducing new updates.
import android.location.Location
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.location.LocationServices
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val locationProvider = LocationServices.getFusedLocationProviderClient(applicationContext)
locationProvider.setMockMode(true)
val loc = Location("gps")
val mockLocation = Location("gps") // a string
mockLocation.latitude = 52.520901 // double
mockLocation.longitude = 13.370790
mockLocation.altitude = loc.altitude
mockLocation.time = System.currentTimeMillis()
mockLocation.accuracy = 1f
mockLocation.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mockLocation.bearingAccuracyDegrees = 0.1f
mockLocation.verticalAccuracyMeters = 0.1f
mockLocation.speedAccuracyMetersPerSecond = 0.01f
}
locationProvider.setMockLocation(mockLocation)
}
}
In Manifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
Did I miss anything else?
The code is from this answer: https://stackoverflow.com/a/59549290/19729496

Related

Foreground service getting killed

I have a service, that updates my backend from time to time, with the newest location of the user, the problem is, some users are reporting that the Foreground notification sometimes disappears, but it doesn't happen to all the users, there might be users using the app 8 hours a day, and it doesn't disappear, but no crash appears on Crashlytics.
Here is my ServiceClass
package com.xxxxxxx.xxxxxxxx.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_ONGOING_EVENT
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.Service
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.graphics.Color
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import android.os.IBinder
import android.util.Base64
import android.util.Base64.DEFAULT
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.xxxx.commons.XXXXXXCommons
import com.xxxxxxx.xxxxxxxx.R
import com.xxxxxxx.xxxxxxxx.di.DataSourceModule
import com.xxxxxxx.xxxxxxxx.domain.GetAppStateUseCase.Companion.LOCATION_DELIMITER
import com.xxxxxxx.xxxxxxxx.model.local.CachedLocation
import com.xxxxxxx.xxxxxxxx.model.local.DriverLocation
import com.xxxxxxx.xxxxxxxx.utils.LOCATION_PERMISSIONS
import com.xxxxxxx.xxxxxxxx.utils.OnGoingTrip
import com.xxxxxxx.xxxxxxxx.utils.checkPermissions
import com.xxxxxxx.xxxxxxxx.view.activity.SplashActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.text.Charsets.UTF_8
class LocationTrackingService : Service() {
private companion object {
const val NOTIFICATION_ID = 856
const val FOREGROUND_SERVICE_ID = 529
const val LOCATION_UPDATE_INTERVAL = 10000L
const val LOCATION_UPDATE_FASTEST_INTERVAL = 5000L
}
private val updateJob: Job = Job()
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
XXXXXXCommons.crashHandler?.log(throwable)
}
private val locationTrackingCoroutineScope =
CoroutineScope(Dispatchers.IO + updateJob + errorHandler)
private val rideApiService by lazy { DataSourceModule.provideRideApiService() }
private val locationDao by lazy { DataSourceModule.provideLocationDao(XXXXXXCommons.provideApplicationContext()) }
private val locationRequest = LocationRequest().apply {
interval = LOCATION_UPDATE_INTERVAL
fastestInterval = LOCATION_UPDATE_FASTEST_INTERVAL
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
private val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) = onLocationReceived(result)
}
#SuppressLint("MissingPermission")
private fun requestLocationUpdates() {
if (checkPermissions(*LOCATION_PERMISSIONS)) {
LocationServices.getFusedLocationProviderClient(this)
.requestLocationUpdates(locationRequest, callback, null)
}
}
private fun onLocationReceived(result: LocationResult?) = result?.lastLocation?.run {
postDriverLocation(this)
} ?: Unit
private fun postDriverLocation(location: Location) {
val driverLocation = DriverLocation(
location.latitude.toFloat(),
location.longitude.toFloat(),
location.bearing,
OnGoingTrip.rideData.rideId,
OnGoingTrip.orderData.orderId
)
OnGoingTrip.currentLocation = LatLng(location.latitude, location.longitude)
OnGoingTrip.reportLocationUpdate()
locationTrackingCoroutineScope.launch(Dispatchers.IO) {
cacheDriverLocation(driverLocation)
rideApiService.postCurrentDriverLocation(driverLocation)
}
}
private suspend fun cacheDriverLocation(driverLocation: DriverLocation) {
val bytes = "${driverLocation.latitude}$LOCATION_DELIMITER${driverLocation.longitude}"
.toByteArray(UTF_8)
val safeLocation = Base64.encode(bytes, DEFAULT).toString(UTF_8)
locationDao.createOrUpdate(CachedLocation(location = safeLocation))
}
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
buildNotification(
if (SDK_INT >= O) {
createNotificationChannel(getString(R.string.app_name))
} else {
NOTIFICATION_ID.toString()
}
)
requestLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
if (locationTrackingCoroutineScope.isActive) {
locationTrackingCoroutineScope.cancel()
}
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(callback)
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.cancel(NOTIFICATION_ID)
}
#RequiresApi(O)
private fun createNotificationChannel(channelName: String): String {
val chan = NotificationChannel(
NOTIFICATION_ID.toString(),
channelName,
NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.CYAN
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return NOTIFICATION_ID.toString()
}
private fun buildNotification(channelId: String) {
val ctx = this
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.run {
val intent = Intent(ctx, SplashActivity::class.java).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent =
PendingIntent.getActivity(ctx, 0, intent, FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(ctx, channelId)
.setSmallIcon(R.drawable.ic_xxxxxx_icon_background)
.setColor(ContextCompat.getColor(ctx, R.color.xxxxxxTurquoise))
.setContentTitle(ctx.getString(R.string.app_name))
.setOngoing(true)
.setContentText(ctx.getString(R.string.usage_message))
.setContentIntent(resultPendingIntent)
.build().apply { flags = FLAG_ONGOING_EVENT or FLAG_NO_CLEAR }
startForeground(FOREGROUND_SERVICE_ID, notification)
}
}
}
Here is how I start the service:
startService(Intent(this, LocationTrackingService::class.java));
Here are the permissions I have and how I declared it on the AndroidManifest.xml:
<service
android:name=".service.LocationTrackingService"
android:enabled="true"
android:exported="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Edit
Most of the devices run Android 9 and 10, a few from 5 to 8, and almost no 11
More information I would be happy to share.
There have been several latest restrictions with Android 11. It's difficult to provide an exact solution. Here is couple of things you should recheck that might resolve your problem:
Disable battery optimization for your app in the device.
Check if the device is not operating in doze mode.
Specify android:foregroundServiceType in manifest .
There are a couple of things here. I cannot see <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> in your manifest. Also make sure about foregroundServiceType="location" in your service attributes inside manifest. In addition, if you have a foreground service for location, ACCESS_BACKGROUND_LOCATION is not required.
On the other hand, Xiaomi devices will kill foreground services when user closes the application from app switcher (Xiaomi is the worst) and you should ask Xiaomi users to manually exclude your application from being killed in battery saver or extra permissions in app details (and even if they do that, still there won't be a guarantee, also different MiUI versions have different rules)
Moreover, star your service using ContextCompat.startForegroundService and in your service's onStartCommand callstartForeground

Android Camera X ImageAnalysis image plane buffers size (limit) does not match image size

Issue
This is a general question regarding ImageAnalysis use case of camera-x but I will use a slightly modified version of this codelab as an example to illustrate the issue I'm seeing. I'm seeing a mismatch between the image dimensions (image.height * image.width) and the associated ByteBuffer size as measured by its limit and or capacity. I would expect them to be the same and mapping one pixel of the image to a single value in the ByteBuffer. This does not appear to be the case. Hoping someone can clarify if this is a bug, and if not, how to interpret this mismatch.
Details
On step 6 (Image Analysis) of the codelab they provide a subclass for their luminosity analyzer:
package jp.oist.cameraxcodelab
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.util.Log
import android.util.Size
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.util.concurrent.Executors
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
typealias LumaListener = (luma: Double) -> Unit
class MainActivity : AppCompatActivity() {
private var imageCapture: ImageCapture? = null
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
// Set up the listener for take photo button
camera_capture_button.setOnClickListener { takePhoto() }
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.createSurfaceProvider())
}
imageCapture = ImageCapture.Builder()
.build()
val imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(Size(480, 640)) // I added this line
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
// Log.d(TAG, "Average luminosity: $luma")
})
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}
private fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
return if (mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
}
}
private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
override fun analyze(image: ImageProxy) {
val buffer = image.planes[0].buffer
for ((index,plane) in image.planes.withIndex()){
Log.i("analyzer", "Plane: $index" + " H: " + image.height + " W: " +
image.width + " HxW: " + image.height * image.width + " buffer.limit: " +
buffer.limit() + " buffer.cap: " + buffer.capacity() + buffer.get())
}
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
val luma = pixels.average()
listener(luma)
image.close()
}
}
}
I am trying to pass the array data from the planes externally, so I'm interested in the var data defined in LuminosityAnalyzer.analyze().
Expectation
I wanted to check the dimensions of the data array and so added the log statement you see after the init of val buffer. For the default resolution I'm getting this in the log:
Plane: 0 H: 480 W: 640 HxW: 307200 buffer.limit: 307200 buffer.cap: 3072005
Plane: 1 H: 480 W: 640 HxW: 307200 buffer.limit: 307200 buffer.cap: 3072003
Plane: 2 H: 480 W: 640 HxW: 307200 buffer.limit: 307200 buffer.cap: 3072003
This is what I would expect. The H*W = buffer.limit. The buffer represents the pixels of the image in the particular plane.
Unexpected Result
If I change the resolution by setting up the imageAnalyzer with the setTargetResolution() method, I get strange results. For example, if I set this to setTargetResolution(144, 176) I get the following log:
Plane: 0 H: 144 W: 176 HxW: 25344 buffer.limit: 27632 buffer.cap: 276324
Plane: 1 H: 144 W: 176 HxW: 25344 buffer.limit: 27632 buffer.cap: 276324
Plane: 2 H: 144 W: 176 HxW: 25344 buffer.limit: 27632 buffer.cap: 276322
Note the different size of the image vs the buffer limit and capacity.
A few other examples for Plane 0 (for brevity):
Plane: 0 H: 288 W: 352 HxW: 101376 buffer.limit: 110560 buffer.cap: 1105604
Plane: 0 H: 600 W: 800 HxW: 480000 buffer.limit: 499168 buffer.cap: 4991685
Plane: 0 H: 960 W: 1280 HxW: 1228800 buffer.limit: 1228800 buffer.cap: 12288004
Does this have something to do with sensor size vs standard image sizes not matching up? Should I expect the remaining entries in the buffer to be zeros or otherwise meaningless?
I was originally not running this in Kotlin, but in Java, and was getting even stranger results there. If you log the image size and the buffer limit for each of the three planes, you get limits both larger and smaller than the image size for different layers:
Plane: 0 width: 176 height: 144 WxH: 25344 buffer.limit: 27632
Plane: 1 width: 176 height: 144 WxH: 25344 buffer.limit: 13807
Plane: 2 width: 176 height: 144 WxH: 25344 buffer.limit: 13807
For whatever reason in Kotlin the limit remains the same between planes.
How should I interpret this? Is the image being padded in Plane 0 and cropped in planes 1 and 2? Or is this a bug?
For reference the manifest, layout file, and build.gradle files are copied below (should be same as CodeLab) At the end I've also included the Java version of the MainActivity that produces the mismatch in buffer limits between planes. Bonus points if you can tell me why ByteBuffer.array() hangs and why I have to use ByteBuffer.get() instead:
AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.oist.cameraxcodelab">
<uses-feature android:name="android.hardware.camera.any" />
<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:theme="#style/Theme.CameraXCodeLab">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="#+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="50dp"
android:scaleType="fitCenter"
android:text="Take Photo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="2dp" />
<androidx.camera.view.PreviewView
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
build.gradle(:app)
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "jp.oist.cameraxcodelab"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
def camerax_version = "1.0.0-beta07"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha14"
}
Java version of MainActivity
package jp.oist.abcvlib.camera;
import android.Manifest;
import android.content.pm.PackageManager;
import android.media.Image;
import android.os.Bundle;
import android.util.Log;
import android.util.Size;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
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 com.google.common.util.concurrent.ListenableFuture;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class MainActivity extends AppCompatActivity implements LifecycleOwner {
private static final int REQUEST_CODE_PERMISSIONS = 10;
private static final String[] REQUIRED_PERMISSIONS = { Manifest.permission.CAMERA };
private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
private PreviewView mPreviewView;
private ExecutorService analysisExecutor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPreviewView = findViewById(R.id.preview_view);
// Request camera permissions
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
int threadPoolSize = 8;
analysisExecutor = new ScheduledThreadPoolExecutor(threadPoolSize);
}
private void bindAll(#NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build();
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(10, 10))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(analysisExecutor, new ImageAnalysis.Analyzer() {
#Override
#androidx.camera.core.ExperimentalGetImage
public void analyze(#NonNull ImageProxy imageProxy) {
Image image = imageProxy.getImage();
if (image != null) {
int width = image.getWidth();
int height = image.getHeight();
byte[] frame = new byte[width * height];
Image.Plane[] planes = image.getPlanes();
int idx = 0;
for (Image.Plane plane : planes){
ByteBuffer frameBuffer = plane.getBuffer();
int n = frameBuffer.capacity();
Log.i("analyzer", "Plane: " + idx + " width: " + width + " height: " + height + " WxH: " + width*height + " buffer.limit: " + n);
frameBuffer.rewind();
frame = new byte[n];
frameBuffer.get(frame);
idx++;
}
}
imageProxy.close();
}
});
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis);
preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
}
private void startCamera() {
mPreviewView.post(() -> {
mCameraProviderFuture = ProcessCameraProvider.getInstance(this);
mCameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = mCameraProviderFuture.get();
bindAll(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, ContextCompat.getMainExecutor(this));
});
}
/**
* Process result from permission request dialog box, has the request
* been granted? If yes, start Camera. Otherwise display a toast
*/
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera();
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show();
finish();
}
}
}
/**
* Check if all permission specified in the manifest have been granted
*/
private boolean allPermissionsGranted() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(getBaseContext(), permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
Please take a look at the details of the ImageProxy.PlaneProxy class; they planes are not just packed image data. They may have both row and pixel stride.
Row stride is padding between two adjacent rows of image data.
Pixel stride is padding between two adjacent pixels.
Also, planes 1 and 2 in a YUV_420_888 image have half the pixel count of plane 0; the reason you're getting the same size is likely because of pixel stride being 2.
For some resolutions, the stride may be equal to width (usually the processing hardware has some constraint like the row stride has to be a multiple of 16 or 32 bytes), but it may not for all.

google maps activity some kind of a bug

I'm using android studio to create an app, and so I implemented the google maps into my application and everything is working fine, all the implementation are there I triple checked, everything needed for it to operate is there, but my problem is that when I close my application for a long time and reopen the maps activity the fragment showing to me is a random one, no markers no locate button no objects just an empty map with the name of the countries. So I open the google maps application(official application) and close it, then try to reopen my google maps activity and everything reverts to normal, markers showing images are displayed, all the functions operate normally can someone tell me what may be the problem keep in mind everything is added, from an API key to the implementations in the graddle.app and the graddle.app.appname , and in the manifest the metadata is added and so on...thank you !
this is my map activity code where I added some NGOs locations and their images on the map:
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.os.Bundle;
import android.widget.Toast;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.internal.ICameraUpdateFactoryDelegate;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
public class map_act extends FragmentActivity implements OnMapReadyCallback {
Location currentLocation;
FusedLocationProviderClient fusedLocationProviderClient;
private static final int REQUEST_CODE = 101;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
fetchLastLocation();
}
private void fetchLastLocation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]
{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE);
return;
}
Task<Location> task = fusedLocationProviderClient.getLastLocation();
task.addOnSuccessListener(new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
if (location != null) {
currentLocation = location;
Toast.makeText(getApplicationContext(), "Your current location", Toast.LENGTH_SHORT).show();
SupportMapFragment supportMapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.google_map);
supportMapFragment.getMapAsync(map_act.this);
}
}
});
}
#Override
public void onMapReady(GoogleMap googleMap) {
LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
googleMap.setMyLocationEnabled(true);
googleMap.getUiSettings().setMyLocationButtonEnabled(true);
googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng));
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng,12));
LatLng Unicef1 = new LatLng(33.887837297007835, 35.50965848465925 );
googleMap.addMarker(new MarkerOptions().position(Unicef1).title("Unicef")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.unicef1)));
LatLng Unicef2 = new LatLng(33.8865888125746, 35.48744276242378 );
googleMap.addMarker(new MarkerOptions().position(Unicef2).title("Unicef")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.unicef1)));
LatLng Unicef3 = new LatLng( 34.42414282398606, 35.82587499823564 );
googleMap.addMarker(new MarkerOptions().position(Unicef3).title("Unicef")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.unicef1)));
LatLng Unicef4 = new LatLng(33.27906495185468, 35.215073956249924 );
googleMap.addMarker(new MarkerOptions().position(Unicef4).title("Unicef")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.unicef1)));
LatLng Wvision1 = new LatLng(33.86913267946954, 35.576261865395736 );
googleMap.addMarker(new MarkerOptions().position(Wvision1).title("World Vision")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.worldvision1)));
LatLng Wvision2 = new LatLng(33.852201737887476, 35.57339217195826 );
googleMap.addMarker(new MarkerOptions().position(Wvision2).title("World vision")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.worldvision1)));
LatLng Drc = new LatLng(33.892777808313056, 35.52051074415333 );
googleMap.addMarker(new MarkerOptions().position(Drc).title("Danish Refugee Council")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.drc1)));
LatLng Nrc = new LatLng(33.897930365217256, 35.493069449639805 );
googleMap.addMarker(new MarkerOptions().position(Nrc).title("Norwegian Refugee Council")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.nrc1)));
LatLng Oxfam1 = new LatLng(33.88743623998913, 35.50865875340152 );
googleMap.addMarker(new MarkerOptions().position(Oxfam1).title("OXFAM")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.oxfam1)));
LatLng Oxfam2 = new LatLng(33.8484475255134, 35.91956548906049 );
googleMap.addMarker(new MarkerOptions().position(Oxfam2).title("OXFAM")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.oxfam1)));
LatLng Oxfam3 = new LatLng(34.42224203429102, 35.827192870916654);
googleMap.addMarker(new MarkerOptions().position(Oxfam3).title("OXFAM")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.oxfam1)));
LatLng Wfp = new LatLng(33.89447500501417, 35.50479228624766);
googleMap.addMarker(new MarkerOptions().position(Wfp).title("World Food Programme")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.wfp1)));
LatLng Unhcr = new LatLng(33.897930365217256, 35.501776697895714);
googleMap.addMarker(new MarkerOptions().position(Unhcr).title("United Nations High Commissioner for Refugees")
.icon(BitmapFromVector(getApplicationContext(), R.drawable.unhcr1)));
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
switch (requestCode){
case REQUEST_CODE:
if(grantResults.length>0 && grantResults[0]== PackageManager.PERMISSION_GRANTED){
fetchLastLocation();
}
break;
}
}
private BitmapDescriptor BitmapFromVector(Context context, int vectorResId) {
// below line is use to generate a drawable.
Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorResId);
// below line is use to set bounds to our vector drawable.
vectorDrawable.setBounds(1, 1, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
// below line is use to create a bitmap for our
// drawable which we have added.
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
// below line is use to add bitmap in our canvas.
Canvas canvas = new Canvas(bitmap);
// below line is use to draw our
// vector drawable in canvas.
vectorDrawable.draw(canvas);
// after generating our bitmap we are returning our bitmap.
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
}
here's my manifest code related to maps:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
In my build.graddle code I added :
google()
jcenter()
and here's my build.graddle.appname code:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
implementation "com.google.android.gms:play-services-maps:$googlePlayVersion"
implementation "com.google.android.gms:play-services-location:$googlePlayVersion"
So it turns out everything's coded right but there's one step that I didn't know of, I should've went into the google cloud platform and enabled the google maps SDK for my API key, so I went and did that and then redownloaded the google-services.json from the firebase (back-end I'm using) and replaced the old one in my app's folder with the new .json file. What was happening is that my API key added was not enabled for external use so when I was opening my google maps application(the official app) my App's API key was substituted by the google maps official one, and that's why my application's app wasn't functioning unless I open the google maps official application and then close it. So I did that and everything's working fine.

Bluetooth low energy security exception

I'm creating an application to scan for Bluetooth low energy devices and have implemented the scan functionality, however, am getting the error:
fail to start le scan - SecurityException thrown: java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results
I have the required permissions in my manifest, however, am still getting this error. I have done some research and read that SDK versions > 23 require some kind of manual checking for permissions, is this the correct solution to this problem or is there a simpler alternative?
package com.example.jake.bluetooth;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ENABLE_BT = 1;
private String ble_not_supported = "BLE not supported";
private BluetoothManager bluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private Button startScanBtn;
private boolean mScanning;
private Handler mHandler;
private BluetoothAdapter.LeScanCallback mLeScanCallBack;
private static final long SCAN_PERIOD = 10000;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
startScanBtn = findViewById(R.id.start_scan);
mHandler = new Handler();
mScanning = true;
mLeScanCallBack = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) {
}
};
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
Toast.makeText(this, ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
startScanBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
scanLeDevice(mScanning);
}
});
}
private void scanLeDevice(final boolean enable){
if(enable){
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallBack);
}
},SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallBack);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallBack);
}
}
}
No it's not enough to just have it in your manifest. You must also explicitly ask the user at runtime. See https://developer.android.com/training/permissions/requesting for more info.
It's 2020, but I respond to this question anyway so maybe it will be helpfull for someone. I encauntered this problem and I resolved it with adding this line of code in onCreate call: requestPermissions(new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);. You're just asking to the user to allow access location. Obvisully PERMISSION_REQUEST_COARSE_LOCATION is defined like: private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;.
I've a app to use BLE and this is my AndroidManifest.xml file, all works fine.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bluetoothlowenergy_gatt_sendreceive">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<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>
<activity android:name=".ServiceActivity"></activity>
</application>
</manifest>
You should uninstall previous build:
Run > Edit Configurations > Application > Before launch section(on the right)
Create a gradle-aware Make add :app:uninstallAll

Why is my ACTION_SEND working on API 25+ perfectly, but messes up on previous APIs?

So I have a Share button that will share an image and a body of text.
On Nougat (API 25) and Oreo (API 26), it works absolutely perfectly. But when it comes to a couple older version it doesn't work as expected.
Marshmallow (API 23): Inserts the image just fine but no body of text.
Lollipop (API 22): Crashes when you push the Share button with popup error "Unfortunately, Messaging has stopped." LOGCAT isn't showing me any errors when this happens.
Here is my share button code:
if (id == R.id.action_shareWine) {
Intent intentShare = new Intent(Intent.ACTION_SEND);
intentShare.putExtra(intentShare.EXTRA_STREAM, imageURI);
intentShare.setType("image/*");
intentShare.putExtra(Intent.EXTRA_TEXT, "body of text goes here");
if (intentShare.resolveActivity(getPackageManager()) != null) {
startActivity(intentShare);
}
return true;
}
Here is a picture to give a visual idea of whats going on:
Anyone have any ideas what could be going on here?
UPDATE 1
Here is the crash log for the Lollipop emulator:
FATAL EXCEPTION: Mms-1
Process: com.android.mms, PID: 7570
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
Which I'm not sure why it is happening because the cursor is loading the image just fine in an ImageView in that same activity.
Call addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) on the ACTION_SEND Intent, to allow third parties the ability to read the content identified by the Uri.
Also, since the image would appear to be a JPEG (based on the file extension), the MIME type is image/jpeg, not image/* or image/png.
For More Details
working in Oreo and Marshmallow Version Read this
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
Source Code
https://drive.google.com/open?id=1vfO43dMSt096CMp6nrOJNl3fJAf6MPwG
create xml folder inside providers_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.holostik.sharescreenshotexample">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA"></uses-permission>
<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>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.holostik.sharescreenshotexample.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
</application>
</manifest>
package com.holostik.sharescreenshotexample;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.holostik.sharescreenshotexample.share.ScreenshotType;
import com.holostik.sharescreenshotexample.share.ScreenshotUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
int n;
String photoPath;
LinearLayout rootContent;
ImageView iv_share;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootContent = (LinearLayout) findViewById(R.id.rootContent);
iv_share = (ImageView) findViewById(R.id.iv_share);
iv_share.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int permissionCheck = ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.CAMERA);
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Log.e("MainActivity ", "P granted");
takeScreenshot(ScreenshotType.FULL);
} else {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
}
} else {
Log.e("MainActivity", "Lower Than MarshMallow");
takeScreenshot(ScreenshotType.FULL);
}
}
});
}
/* Method which will take screenshot on Basis of Screenshot Type ENUM */
private void takeScreenshot(ScreenshotType screenshotType) {
Bitmap b = null;
switch (screenshotType) {
case FULL:
b = ScreenshotUtils.getScreenShot(rootContent);
break;
case CUSTOM:
//If Screenshot type is CUSTOM
break;
}
//If bitmap is not null
if (b != null) {
// showScreenShotImage(b);//show bitmap over imageview
Log.e("keshav", "bitmap is -- > " + b);
SaveImage(b);
shareScreenshot();
/* File saveFile = ScreenshotUtils.getMainDirectoryName(MainActivity.this);//get the path to save screenshot
File file = ScreenshotUtils.store(b, "screenshot" + screenshotType + ".jpg", saveFile);//save the screenshot to selected path
shareScreenshot(file);//finally share screenshot
Log.e("file Path", String.valueOf(file));
*/
} else
//If bitmap is null show toast message
Toast.makeText(MainActivity.this, R.string.screenshot_take_failed, Toast.LENGTH_SHORT).show();
}
private void SaveImage(Bitmap finalBitmap)
{
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();
n = 10000;
n = generator.nextInt(n);
String fname = "Image-" + n + ".jpg";
File file = new File(myDir, fname);
if (file.exists()) file.delete();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/* TODO Show screenshot Bitmap */
// private void showScreenShotImage(Bitmap b) {
// imageView.setImageBitmap(b);
// }
private void shareScreenshot()
{
photoPath = Environment.getExternalStorageDirectory() + "/saved_images" + "/Image-" + n + ".jpg";
File F = new File(photoPath);
//Uri U = Uri.fromFile(F);
// Uri U = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".my.package.name.provider", F);
// TODO your package name as well add .fileprovider
Uri U = FileProvider.getUriForFile(MainActivity.this.getApplicationContext(), "com.holostik.sharescreenshotexample.fileprovider", F);
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("image/png");
i.putExtra(Intent.EXTRA_STREAM, U);
startActivityForResult(Intent.createChooser(i, "Email:"), 1);
}
// TODO Share Screen SHOT End ..............
}

Categories